still in a wip state
This commit is contained in:
parent
61263dcd68
commit
26491ae5cd
|
@ -151,6 +151,18 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co
|
||||||
return c.address, tx, c, nil
|
return c.address, tx, c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeployContractRaw(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, packedParams []byte) (common.Address, *types.Transaction, *BoundContract, error) {
|
||||||
|
// Otherwise try to deploy the contract
|
||||||
|
c := NewBoundContract(common.Address{}, abi, backend, backend, backend)
|
||||||
|
|
||||||
|
tx, err := c.transact(opts, nil, append(bytecode, packedParams...))
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, nil, err
|
||||||
|
}
|
||||||
|
c.address = crypto.CreateAddress(opts.From, tx.Nonce())
|
||||||
|
return c.address, tx, c, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Call invokes the (constant) contract method with params as input values and
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
// sets the output to result. The result type might be a single field for simple
|
// sets the output to result. The result type might be a single field for simple
|
||||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
|
@ -179,6 +191,10 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri
|
||||||
return c.abi.UnpackIntoInterface(res[0], method, output)
|
return c.abi.UnpackIntoInterface(res[0], method, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *BoundContract) CallRaw(opts *CallOpts, input []byte) ([]byte, error) {
|
||||||
|
return c.call(opts, input)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *BoundContract) call(opts *CallOpts, input []byte) ([]byte, error) {
|
func (c *BoundContract) call(opts *CallOpts, input []byte) ([]byte, error) {
|
||||||
// Don't crash on a lazy user
|
// Don't crash on a lazy user
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
|
|
|
@ -312,17 +312,19 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
}
|
}
|
||||||
|
|
||||||
contracts[types[i]] = &tmplContract{
|
contracts[types[i]] = &tmplContract{
|
||||||
Type: capitalise(types[i]),
|
Type: capitalise(types[i]),
|
||||||
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
|
InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""),
|
||||||
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
|
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
|
||||||
Constructor: evmABI.Constructor,
|
Constructor: evmABI.Constructor,
|
||||||
Calls: calls,
|
Calls: calls,
|
||||||
Transacts: transacts,
|
Transacts: transacts,
|
||||||
Fallback: fallback,
|
Fallback: fallback,
|
||||||
Receive: receive,
|
Receive: receive,
|
||||||
Events: events,
|
Events: events,
|
||||||
Libraries: make(map[string]string),
|
Libraries: make(map[string]string),
|
||||||
|
AllLibraries: make(map[string]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function 4-byte signatures are stored in the same sequence
|
// Function 4-byte signatures are stored in the same sequence
|
||||||
// as types, if available.
|
// as types, if available.
|
||||||
if len(fsigs) > i {
|
if len(fsigs) > i {
|
||||||
|
@ -340,14 +342,54 @@ func bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
|
||||||
if _, ok := isLib[name]; !ok {
|
if _, ok := isLib[name]; !ok {
|
||||||
isLib[name] = struct{}{}
|
isLib[name] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check if that type has already been identified as a library
|
||||||
|
for i := 0; i < len(types); i++ {
|
||||||
|
_, ok := isLib[types[i]]
|
||||||
|
contracts[types[i]].Library = ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively traverse the library dependency graph
|
||||||
|
// of the contract, flattening it into a list.
|
||||||
|
//
|
||||||
|
// For abigenv2, we do not generate contract deploy
|
||||||
|
// methods (which in v1 recursively deploy their
|
||||||
|
// library dependencies). So, the entire set of
|
||||||
|
// library dependencies is required, and we will
|
||||||
|
// the order to deploy and link them at runtime.
|
||||||
|
var findDeps func(contract *tmplContract) map[string]struct{}
|
||||||
|
findDeps = func(contract *tmplContract) map[string]struct{} {
|
||||||
|
// 1) match all libraries that this contract depends on
|
||||||
|
re, err := regexp.Compile("__\\$([a-f0-9]+)\\$__")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
libBin := contracts[contract.Type].InputBin
|
||||||
|
matches := re.FindAllStringSubmatch(libBin, -1)
|
||||||
|
var result map[string]struct{}
|
||||||
|
|
||||||
|
// 2) recurse, gathering nested library dependencies
|
||||||
|
for _, match := range matches {
|
||||||
|
pattern := match[1]
|
||||||
|
result[pattern] = struct{}{}
|
||||||
|
depContract := contracts[pattern]
|
||||||
|
for subPattern, _ := range findDeps(depContract) {
|
||||||
|
result[subPattern] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
// take the set of library patterns, convert it to a map of pattern -> type
|
||||||
|
deps := findDeps(contracts[types[i]])
|
||||||
|
contracts[types[i]].AllLibraries = make(map[string]string)
|
||||||
|
for contractPattern, _ := range deps {
|
||||||
|
contractType := libs[contractPattern]
|
||||||
|
contracts[types[i]].AllLibraries[contractType] = contractPattern
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check if that type has already been identified as a library
|
|
||||||
for i := 0; i < len(types); i++ {
|
|
||||||
_, ok := isLib[types[i]]
|
|
||||||
contracts[types[i]].Library = ok
|
|
||||||
}
|
|
||||||
// Generate the contract template data content and render it
|
// Generate the contract template data content and render it
|
||||||
data := &tmplData{
|
data := &tmplData{
|
||||||
Package: pkg,
|
Package: pkg,
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package bind
|
package bind
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,10 +29,5 @@ type ContractInstance interface {
|
||||||
|
|
||||||
type ContractInstanceV2 interface {
|
type ContractInstanceV2 interface {
|
||||||
Address() common.Address
|
Address() common.Address
|
||||||
}
|
Backend() ContractBackend
|
||||||
|
|
||||||
func CallRaw(instance ContractInstance, opts *CallOpts, input []byte) ([]byte, error) {
|
|
||||||
backend := instance.Backend()
|
|
||||||
c := NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
|
|
||||||
return c.call(opts, input)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,18 +32,19 @@ type tmplData struct {
|
||||||
|
|
||||||
// tmplContract contains the data needed to generate an individual contract binding.
|
// tmplContract contains the data needed to generate an individual contract binding.
|
||||||
type tmplContract struct {
|
type tmplContract struct {
|
||||||
Type string // Type name of the main contract binding
|
Type string // Type name of the main contract binding
|
||||||
InputABI string // JSON ABI used as the input to generate the binding from
|
InputABI string // JSON ABI used as the input to generate the binding from
|
||||||
InputBin string // Optional EVM bytecode used to generate deploy code from
|
InputBin string // Optional EVM bytecode used to generate deploy code from
|
||||||
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
|
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
|
||||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||||
Fallback *tmplMethod // Additional special fallback function
|
Fallback *tmplMethod // Additional special fallback function
|
||||||
Receive *tmplMethod // Additional special receive function
|
Receive *tmplMethod // Additional special receive function
|
||||||
Events map[string]*tmplEvent // Contract events accessors
|
Events map[string]*tmplEvent // Contract events accessors
|
||||||
Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs
|
Libraries map[string]string // Same as tmplData, but filtered to only keep direct deps that the contract needs
|
||||||
Library bool // Indicator whether the contract is a library
|
AllLibraries map[string]string // same as Libraries, but all direct/indirect library dependencies
|
||||||
|
Library bool // Indicator whether the contract is a library
|
||||||
}
|
}
|
||||||
|
|
||||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||||
|
|
|
@ -38,6 +38,12 @@ var (
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
{{range $contract := .Contracts}}
|
||||||
|
var {{$contract.Type}}LibraryDeps = map[string]*bind.MetaData{
|
||||||
|
{{range $pattern, $name := .Libraries}}
|
||||||
|
"{{$pattern}}": &{{$name}}MetaData,
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
// {{.Type}}MetaData contains all meta data concerning the {{.Type}} contract.
|
||||||
var {{.Type}}MetaData = &bind.MetaData{
|
var {{.Type}}MetaData = &bind.MetaData{
|
||||||
ABI: "{{.InputABI}}",
|
ABI: "{{.InputABI}}",
|
||||||
|
@ -52,20 +58,14 @@ var (
|
||||||
{{end}}
|
{{end}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// {{.Type}}Instance represents a deployed instance of the {{.Type}} contract.
|
|
||||||
type {{.Type}}Instance struct {
|
|
||||||
{{.Type}}
|
|
||||||
address common.Address // consider removing this, not clear what it's used for now (and why did we need custom deploy method on previous abi?)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New{{.Type}}Instance(c *{{.Type}}, address common.Address) *{{.Type}}Instance {
|
|
||||||
return &{{.Type}}Instance{ {{$contract.Type}}: *c, address: address}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *{{$contract.Type}}Instance) Address() common.Address {
|
func (i *{{$contract.Type}}Instance) Address() common.Address {
|
||||||
return i.address
|
return i.address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *{{$contract.Type}}Instance) Backend() bind.ContractBackend {
|
||||||
|
return i.backend
|
||||||
|
}
|
||||||
|
|
||||||
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||||
type {{.Type}} struct {
|
type {{.Type}} struct {
|
||||||
abi abi.ABI
|
abi abi.ABI
|
||||||
|
@ -86,7 +86,8 @@ var (
|
||||||
return _{{$contract.Type}}.deployCode
|
return _{{$contract.Type}}.deployCode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_{{$contract.Type}} *{{$contract.Type}}) PackConstructor({{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ([]byte, error) {
|
// TODO: test constructor with inputs
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}) PackConstructor({{range .Constructor.Inputs}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ([]byte, error) {
|
||||||
return _{{$contract.Type}}.abi.Pack("" {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
return _{{$contract.Type}}.abi.Pack("" {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
|
library RecursiveDep {
|
||||||
|
function AddOne(uint256 val) public pure returns (uint256 ret) {
|
||||||
|
return val + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array function to delete element at index and re-organize the array
|
||||||
|
// so that there are no gaps between the elements.
|
||||||
|
library Array {
|
||||||
|
using RecursiveDep for uint256;
|
||||||
|
|
||||||
|
function remove(uint256[] storage arr, uint256 index) public {
|
||||||
|
// Move the last element into the place to delete
|
||||||
|
require(arr.length > 0, "Can't remove from empty array");
|
||||||
|
arr[index] = arr[arr.length - 1];
|
||||||
|
arr[index] = arr[index].AddOne();
|
||||||
|
arr.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract TestArray {
|
||||||
|
using Array for uint256[];
|
||||||
|
|
||||||
|
uint256[] public arr;
|
||||||
|
|
||||||
|
function testArrayRemove(uint256 value) public {
|
||||||
|
for (uint256 i = 0; i < 3; i++) {
|
||||||
|
arr.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.remove(1);
|
||||||
|
|
||||||
|
assert(arr.length == 2);
|
||||||
|
assert(arr[0] == 0);
|
||||||
|
assert(arr[1] == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(uint256 foobar) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
package v2
|
package v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
@ -10,12 +8,63 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"math/big"
|
"regexp"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FilterLogs[T any](instance bind.ContractInstance, opts *bind.FilterOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) {
|
type ContractInstance struct {
|
||||||
backend := instance.Backend()
|
Address common.Address
|
||||||
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
|
Backend bind.ContractBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeployContracts(auth *bind.TransactOpts, backend bind.ContractBackend, constructorInput []byte, contracts map[string]*bind.MetaData) {
|
||||||
|
// match if the contract has dynamic libraries that need to be linked
|
||||||
|
hasDepsMatcher, err := regexp.Compile("__\\$.*\\$__")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deps we are linking
|
||||||
|
wipDeps := make(map[string]string)
|
||||||
|
for id, metadata := range contracts {
|
||||||
|
wipDeps[id] = metadata.Bin
|
||||||
|
}
|
||||||
|
|
||||||
|
// nested iteration: find contracts without library dependencies first,
|
||||||
|
// deploy them, link them into any other contracts that depend on them.
|
||||||
|
// repeat this until there are no more contracts to link/deploy
|
||||||
|
for {
|
||||||
|
for id, contractBin := range wipDeps {
|
||||||
|
if !hasDepsMatcher.MatchString(contractBin) {
|
||||||
|
// this library/contract doesn't depend on any others
|
||||||
|
// it can be deployed as-is.
|
||||||
|
abi, err := contracts[id].GetAbi()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
addr, _, _, err := bind.DeployContractRaw(auth, *abi, []byte(contractBin), backend, constructorInput)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
delete(wipDeps, id)
|
||||||
|
|
||||||
|
// embed the address of the deployed contract into any
|
||||||
|
// libraries/contracts that depend on it.
|
||||||
|
for id, contractBin := range wipDeps {
|
||||||
|
contractBin = strings.ReplaceAll(contractBin, fmt.Sprintf("__$%s%__", id), fmt.Sprintf("__$%s$__", addr.String()))
|
||||||
|
wipDeps[id] = contractBin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(wipDeps) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterLogs[T any](instance *ContractInstance, opts *bind.FilterOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), topics ...[]any) (*EventIterator[T], error) {
|
||||||
|
backend := instance.Backend
|
||||||
|
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend)
|
||||||
logs, sub, err := c.FilterLogs(opts, eventID.String(), topics...)
|
logs, sub, err := c.FilterLogs(opts, eventID.String(), topics...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -23,44 +72,10 @@ func FilterLogs[T any](instance bind.ContractInstance, opts *bind.FilterOpts, ev
|
||||||
return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil
|
return &EventIterator[T]{unpack: unpack, logs: logs, sub: sub}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchOpts is the collection of options to fine tune subscribing for events
|
func WatchLogs[T any](instance *ContractInstance, opts *bind.WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) {
|
||||||
// within a bound contract.
|
backend := instance.Backend
|
||||||
type WatchOpts struct {
|
c := bind.NewBoundContract(instance.Address, abi.ABI{}, backend, backend, backend)
|
||||||
Start *uint64 // Start of the queried range (nil = latest)
|
logs, sub, err := c.WatchLogs(opts, eventID.String(), topics...)
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchLogs(backend V2Backend, address common.Address, opts *WatchOpts, eventID common.Hash, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
|
||||||
// Don't crash on a lazy user
|
|
||||||
if opts == nil {
|
|
||||||
opts = new(WatchOpts)
|
|
||||||
}
|
|
||||||
// Append the event selector to the query parameters and construct the topic set
|
|
||||||
query = append([][]interface{}{{eventID}}, query...)
|
|
||||||
|
|
||||||
topics, err := abi.MakeTopics(query...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// Start the background filtering
|
|
||||||
logs := make(chan types.Log, 128)
|
|
||||||
|
|
||||||
config := ethereum.FilterQuery{
|
|
||||||
Addresses: []common.Address{address},
|
|
||||||
Topics: topics,
|
|
||||||
}
|
|
||||||
if opts.Start != nil {
|
|
||||||
config.FromBlock = new(big.Int).SetUint64(*opts.Start)
|
|
||||||
}
|
|
||||||
sub, err := backend.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return logs, sub, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func WatchLogs[T any](address common.Address, backend V2Backend, opts *WatchOpts, eventID common.Hash, unpack func(*types.Log) (*T, error), sink chan<- *T, topics ...[]any) (event.Subscription, error) {
|
|
||||||
logs, sub, err := watchLogs(backend, address, opts, eventID, topics...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -166,167 +181,14 @@ func Transact(instance bind.ContractInstance, opts *bind.TransactOpts, input []b
|
||||||
return c.RawTransact(opts, input)
|
return c.RawTransact(opts, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureContext is a helper method to ensure a context is not nil, even if the
|
|
||||||
// user specified it as such.
|
|
||||||
func ensureContext(ctx context.Context) context.Context {
|
|
||||||
if ctx == nil {
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignerFn is a signer function callback when a contract requires a method to
|
|
||||||
// sign the transaction before submission.
|
|
||||||
type SignerFn func(common.Address, *types.Transaction) (*types.Transaction, error)
|
|
||||||
|
|
||||||
// TransactOpts is the collection of authorization data required to create a
|
|
||||||
// valid Ethereum transaction.
|
|
||||||
type TransactOpts struct {
|
|
||||||
From common.Address // Ethereum account to send the transaction from
|
|
||||||
Nonce *big.Int // Nonce to use for the transaction execution (nil = use pending state)
|
|
||||||
Signer SignerFn // Method to use for signing the transaction (mandatory)
|
|
||||||
|
|
||||||
Value *big.Int // Funds to transfer along the transaction (nil = 0 = no funds)
|
|
||||||
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
|
||||||
GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
|
||||||
GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
|
|
||||||
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
|
|
||||||
AccessList types.AccessList // Access list to set for the transaction execution (nil = no access list)
|
|
||||||
|
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
|
||||||
|
|
||||||
NoSend bool // Do all transact steps but do not send the transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
func estimateGasLimit(backend V2Backend, address common.Hash, opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) {
|
|
||||||
if contract != nil {
|
|
||||||
// Gas estimation cannot succeed without code for method invocations.
|
|
||||||
if code, err := backend.PendingCodeAt(ensureContext(opts.Context), address); err != nil {
|
|
||||||
return 0, err
|
|
||||||
} else if len(code) == 0 {
|
|
||||||
return 0, ErrNoCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg := ethereum.CallMsg{
|
|
||||||
From: opts.From,
|
|
||||||
To: contract,
|
|
||||||
GasPrice: gasPrice,
|
|
||||||
GasTipCap: gasTipCap,
|
|
||||||
GasFeeCap: gasFeeCap,
|
|
||||||
Value: value,
|
|
||||||
Data: input,
|
|
||||||
}
|
|
||||||
return backend.EstimateGas(ensureContext(opts.Context), msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNonce(backend V2Backend, opts *TransactOpts) (uint64, error) {
|
|
||||||
if opts.Nonce == nil {
|
|
||||||
return backend.PendingNonceAt(ensureContext(opts.Context), opts.From)
|
|
||||||
} else {
|
|
||||||
return opts.Nonce.Uint64(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createLegacyTx(backend V2Backend, address common.Hash, opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
|
|
||||||
if opts.GasFeeCap != nil || opts.GasTipCap != nil || opts.AccessList != nil {
|
|
||||||
return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas or accessList specified but london is not active yet")
|
|
||||||
}
|
|
||||||
// Normalize value
|
|
||||||
value := opts.Value
|
|
||||||
if value == nil {
|
|
||||||
value = new(big.Int)
|
|
||||||
}
|
|
||||||
// Estimate GasPrice
|
|
||||||
gasPrice := opts.GasPrice
|
|
||||||
if gasPrice == nil {
|
|
||||||
price, err := backend.SuggestGasPrice(ensureContext(opts.Context))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
gasPrice = price
|
|
||||||
}
|
|
||||||
// Estimate GasLimit
|
|
||||||
gasLimit := opts.GasLimit
|
|
||||||
if opts.GasLimit == 0 {
|
|
||||||
var err error
|
|
||||||
gasLimit, err = estimateGasLimit(backend, address, opts, contract, input, gasPrice, nil, nil, value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create the transaction
|
|
||||||
nonce, err := getNonce(backend, opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
baseTx := &types.LegacyTx{
|
|
||||||
To: contract,
|
|
||||||
Nonce: nonce,
|
|
||||||
GasPrice: gasPrice,
|
|
||||||
Gas: gasLimit,
|
|
||||||
Value: value,
|
|
||||||
Data: input,
|
|
||||||
}
|
|
||||||
return types.NewTx(baseTx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const basefeeWiggleMultiplier = 2
|
|
||||||
|
|
||||||
func createDynamicTx(backend V2Backend, opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) {
|
|
||||||
// Normalize value
|
|
||||||
value := opts.Value
|
|
||||||
if value == nil {
|
|
||||||
value = new(big.Int)
|
|
||||||
}
|
|
||||||
// Estimate TipCap
|
|
||||||
gasTipCap := opts.GasTipCap
|
|
||||||
if gasTipCap == nil {
|
|
||||||
tip, err := backend.SuggestGasTipCap(ensureContext(opts.Context))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
gasTipCap = tip
|
|
||||||
}
|
|
||||||
// Estimate FeeCap
|
|
||||||
gasFeeCap := opts.GasFeeCap
|
|
||||||
if gasFeeCap == nil {
|
|
||||||
gasFeeCap = new(big.Int).Add(
|
|
||||||
gasTipCap,
|
|
||||||
new(big.Int).Mul(head.BaseFee, big.NewInt(basefeeWiggleMultiplier)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if gasFeeCap.Cmp(gasTipCap) < 0 {
|
|
||||||
return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", gasFeeCap, gasTipCap)
|
|
||||||
}
|
|
||||||
// Estimate GasLimit
|
|
||||||
gasLimit := opts.GasLimit
|
|
||||||
if opts.GasLimit == 0 {
|
|
||||||
var err error
|
|
||||||
gasLimit, err = c.estimateGasLimit(opts, contract, input, nil, gasTipCap, gasFeeCap, value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create the transaction
|
|
||||||
nonce, err := c.getNonce(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
baseTx := &types.DynamicFeeTx{
|
|
||||||
To: contract,
|
|
||||||
Nonce: nonce,
|
|
||||||
GasFeeCap: gasFeeCap,
|
|
||||||
GasTipCap: gasTipCap,
|
|
||||||
Gas: gasLimit,
|
|
||||||
Value: value,
|
|
||||||
Data: input,
|
|
||||||
AccessList: opts.AccessList,
|
|
||||||
}
|
|
||||||
return types.NewTx(baseTx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Transfer(instance bind.ContractInstance, opts *bind.TransactOpts) (*types.Transaction, error) {
|
func Transfer(instance bind.ContractInstance, opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
backend := instance.Backend()
|
backend := instance.Backend()
|
||||||
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
|
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
|
||||||
return c.Transfer(opts)
|
return c.Transfer(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CallRaw(instance bind.ContractInstance, opts *bind.CallOpts, input []byte) ([]byte, error) {
|
||||||
|
backend := instance.Backend()
|
||||||
|
c := bind.NewBoundContract(instance.Address(), abi.ABI{}, backend, backend, backend)
|
||||||
|
return c.CallRaw(opts, input)
|
||||||
|
}
|
||||||
|
|
|
@ -75,28 +75,52 @@ func TestV2(t *testing.T) {
|
||||||
Backend: backend,
|
Backend: backend,
|
||||||
Client: backend.Client(),
|
Client: backend.Client(),
|
||||||
}
|
}
|
||||||
address, _, boundContract, err := bind.DeployContract(&opts, contractABI, common.Hex2Bytes(v2_generated_testcase.V2GeneratedTestcaseMetaData.Bin), &bindBackend)
|
address, tx, _, err := bind.DeployContract(&opts, contractABI, common.Hex2Bytes(v2_generated_testcase.V2GeneratedTestcaseMetaData.Bin), &bindBackend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = bind.WaitDeployed(context.Background(), &bindBackend, tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error deploying bound contract: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
contract, err := v2_generated_testcase.NewV2GeneratedTestcase()
|
contract, err := v2_generated_testcase.NewV2GeneratedTestcase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err) // can't happen here with the example used. consider removing this block
|
t.Fatal(err) // can't happen here with the example used. consider removing this block
|
||||||
}
|
}
|
||||||
contractInstance := v2_generated_testcase.NewV2GeneratedTestcaseInstance(contract, address)
|
//contractInstance := v2_generated_testcase.NewV2GeneratedTestcaseInstance(contract, address, bindBackend)
|
||||||
sinkCh := make(chan *v2_generated_testcase.V2GeneratedTestcase)
|
contractInstance := ContractInstance{
|
||||||
|
Address: address,
|
||||||
|
Backend: bindBackend,
|
||||||
|
}
|
||||||
|
sinkCh := make(chan *v2_generated_testcase.V2GeneratedTestcaseStruct)
|
||||||
// q: what extra functionality is given by specifying this as a custom method, instead of catching emited methods
|
// q: what extra functionality is given by specifying this as a custom method, instead of catching emited methods
|
||||||
// from the sync channel?
|
// from the sync channel?
|
||||||
unpackStruct := func(log *types.Log) (v2_generated_testcase.V2GeneratedTestcaseStruct, error) {
|
unpackStruct := func(log *types.Log) (*v2_generated_testcase.V2GeneratedTestcaseStruct, error) {
|
||||||
res, err := contract.UnpackStructEvent(log)
|
res, err := contract.UnpackStructEvent(log)
|
||||||
return *res, err
|
return res, err
|
||||||
|
}
|
||||||
|
watchOpts := bind.WatchOpts{
|
||||||
|
Start: nil,
|
||||||
|
Context: context.Background(),
|
||||||
}
|
}
|
||||||
// TODO: test using various topics
|
// TODO: test using various topics
|
||||||
// q: does nil topics mean to accept any?
|
// q: does nil topics mean to accept any?
|
||||||
sub, err := WatchLogs[v2_generated_testcase.V2GeneratedTestcaseStruct](contractInstance, v2_generated_testcase.V2GeneratedTestcaseStructEventID(), unpackStruct, sinkCh)
|
sub, err := WatchLogs[v2_generated_testcase.V2GeneratedTestcaseStruct](&contractInstance, &watchOpts, v2_generated_testcase.V2GeneratedTestcaseStructEventID(), unpackStruct, sinkCh, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer sub.Unsubscribe()
|
defer sub.Unsubscribe()
|
||||||
// send a balance to our contract (contract must accept ether by default)
|
// send a balance to our contract (contract must accept ether by default)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeployment(t *testing.T) {
|
||||||
|
DeployContracts
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test-cases that should be extracted from v1 tests
|
||||||
|
|
||||||
|
* EventChecker
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
Loading…
Reference in New Issue