diff --git a/accounts/abi/abigen/source2.go.tpl b/accounts/abi/abigen/source2.go.tpl index db96c66be2..80cbd28d13 100644 --- a/accounts/abi/abigen/source2.go.tpl +++ b/accounts/abi/abigen/source2.go.tpl @@ -69,8 +69,8 @@ var ( // Instance creates a wrapper for a deployed contract instance at the given address. // Use this to create the instance object passed to abigen v2 library functions Call, Transact, etc. - func (c *{{.Type}}) Instance(backend bind.ContractBackend, addr common.Address) bind.BoundContract { - return bind.NewBoundContract(backend, addr, c.abi) + func (c *{{.Type}}) Instance(backend bind.ContractBackend, addr common.Address) *bind.BoundContract { + return bind.NewBoundContract(addr, c.abi, backend, backend, backend) } {{ if .Constructor.Inputs }} diff --git a/accounts/abi/bind/old.go b/accounts/abi/bind/old.go index d5e657be9f..b09f5f3c7a 100644 --- a/accounts/abi/bind/old.go +++ b/accounts/abi/bind/old.go @@ -220,10 +220,10 @@ type FilterOpts = bind2.FilterOpts type WatchOpts = bind2.WatchOpts -type BoundContract = bind2.BoundContractV1 +type BoundContract = bind2.BoundContract func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract { - return bind2.NewBoundContractV1(address, abi, caller, transactor, filterer) + return bind2.NewBoundContract(address, abi, caller, transactor, filterer) } func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) { @@ -235,8 +235,8 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co if err != nil { return common.Address{}, nil, nil, err } - boundContract := NewBoundContract(addr, abi, backend, backend, backend) - return addr, tx, boundContract, nil + contract := NewBoundContract(addr, abi, backend, backend, backend) + return addr, tx, contract, nil } // MetaData collects all metadata for a bound contract. diff --git a/accounts/abi/bind/v2/base.go b/accounts/abi/bind/v2/base.go index ab35e79872..16e7101e7f 100644 --- a/accounts/abi/bind/v2/base.go +++ b/accounts/abi/bind/v2/base.go @@ -19,10 +19,13 @@ package bind import ( "context" "errors" + "fmt" "math/big" "strings" "sync" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -123,34 +126,453 @@ func (m *MetaData) ParseABI() (*abi.ABI, error) { return m.parsedABI, nil } -// BoundContract represents a contract deployed on-chain. It does not export any methods, and is used in the low-level -// contract interaction API methods provided in the v2 package. -type BoundContract interface { - filterLogs(opts *FilterOpts, name string, query ...[]any) (chan types.Log, event.Subscription, error) - watchLogs(opts *WatchOpts, name string, query ...[]any) (chan types.Log, event.Subscription, error) - rawCreationTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) - call(opts *CallOpts, input []byte) ([]byte, error) - transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) - addr() common.Address +// BoundContract is the base wrapper object that reflects a contract on the +// Ethereum network. It contains a collection of methods that are used by the +// higher level contract bindings to operate. +type BoundContract struct { + address common.Address // Deployment address of the contract on the Ethereum blockchain + abi abi.ABI // Reflect based ABI to access the correct Ethereum methods + caller ContractCaller // Read interface to interact with the blockchain + transactor ContractTransactor // Write interface to interact with the blockchain + filterer ContractFilterer // Event filtering to interact with the blockchain } -// NewBoundContract creates a new BoundContract instance. -func NewBoundContract(backend ContractBackend, address common.Address, abi abi.ABI) BoundContract { - return NewBoundContractV1(address, abi, backend, backend, backend) +// NewBoundContract creates a low level contract interface through which calls +// and transactions may be made through. +func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract { + return &BoundContract{ + address: address, + abi: abi, + caller: caller, + transactor: transactor, + filterer: filterer, + } } -func (c *BoundContractV1) filterLogs(opts *FilterOpts, name string, query ...[]any) (chan types.Log, event.Subscription, error) { - return c.FilterLogs(opts, name, query...) +// 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 +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (c *BoundContract) Call(opts *CallOpts, results *[]any, method string, params ...any) error { + if results == nil { + results = new([]any) + } + // Pack the input, call and unpack the results + input, err := c.abi.Pack(method, params...) + if err != nil { + return err + } + + output, err := c.call(opts, input) + if err != nil { + return err + } + + if len(*results) == 0 { + res, err := c.abi.Unpack(method, output) + *results = res + return err + } + res := *results + return c.abi.UnpackIntoInterface(res[0], method, output) } -func (c *BoundContractV1) watchLogs(opts *WatchOpts, name string, query ...[]any) (chan types.Log, event.Subscription, error) { - return c.WatchLogs(opts, name, query...) +// CallRaw executes an eth_call against the contract with the raw calldata as +// input. It returns the call's return data or an error. +func (c *BoundContract) CallRaw(opts *CallOpts, input []byte) ([]byte, error) { + return c.call(opts, input) } -func (c *BoundContractV1) rawCreationTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { - return c.RawCreationTransact(opts, calldata) +func (c *BoundContract) call(opts *CallOpts, input []byte) ([]byte, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(CallOpts) + } + var ( + msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input} + ctx = ensureContext(opts.Context) + code []byte + output []byte + err error + ) + if opts.Pending { + pb, ok := c.caller.(PendingContractCaller) + if !ok { + return nil, ErrNoPendingState + } + output, err = pb.PendingCallContract(ctx, msg) + if err != nil { + return nil, err + } + if len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = pb.PendingCodeAt(ctx, c.address); err != nil { + return nil, err + } else if len(code) == 0 { + return nil, ErrNoCode + } + } + } else if opts.BlockHash != (common.Hash{}) { + bh, ok := c.caller.(BlockHashContractCaller) + if !ok { + return nil, ErrNoBlockHashState + } + output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash) + if err != nil { + return nil, err + } + if len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil { + return nil, err + } else if len(code) == 0 { + return nil, ErrNoCode + } + } + } else { + output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) + if err != nil { + return nil, err + } + if len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { + return nil, err + } else if len(code) == 0 { + return nil, ErrNoCode + } + } + } + return output, nil } -func (c *BoundContractV1) addr() common.Address { - return c.address +// Transact invokes the (paid) contract method with params as input values. +func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...any) (*types.Transaction, error) { + // Otherwise pack up the parameters and invoke the contract + input, err := c.abi.Pack(method, params...) + if err != nil { + return nil, err + } + return c.transact(opts, &c.address, input) +} + +// RawTransact initiates a transaction with the given raw calldata as the input. +// It's usually used to initiate transactions for invoking **Fallback** function. +func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { + return c.transact(opts, &c.address, calldata) +} + +// RawTransact initiates a contract-creation transaction with the given raw calldata as the input. +func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { + return c.transact(opts, nil, calldata) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { + // todo(rjl493456442) check the payable fallback or receive is defined + // or not, reject invalid transaction at the first place + return c.transact(opts, &c.address, nil) +} + +func (c *BoundContract) createDynamicTx(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 := c.transactor.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 (c *BoundContract) createLegacyTx(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 := c.transactor.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 = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value) + if err != nil { + return nil, err + } + } + // create the transaction + nonce, err := c.getNonce(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 +} + +func (c *BoundContract) estimateGasLimit(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 := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.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 c.transactor.EstimateGas(ensureContext(opts.Context), msg) +} + +func (c *BoundContract) getNonce(opts *TransactOpts) (uint64, error) { + if opts.Nonce == nil { + return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) + } else { + return opts.Nonce.Uint64(), nil + } +} + +// transact executes an actual transaction invocation, first deriving any missing +// authorization fields, and then scheduling the transaction for execution. +func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { + if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) { + return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + } + // Create the transaction + var ( + rawTx *types.Transaction + err error + ) + if opts.GasPrice != nil { + rawTx, err = c.createLegacyTx(opts, contract, input) + } else if opts.GasFeeCap != nil && opts.GasTipCap != nil { + rawTx, err = c.createDynamicTx(opts, contract, input, nil) + } else { + // Only query for basefee if gasPrice not specified + if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil { + return nil, errHead + } else if head.BaseFee != nil { + rawTx, err = c.createDynamicTx(opts, contract, input, head) + } else { + // Chain is not London ready -> use legacy transaction + rawTx, err = c.createLegacyTx(opts, contract, input) + } + } + if err != nil { + return nil, err + } + // Sign the transaction and schedule it for execution + if opts.Signer == nil { + return nil, errors.New("no signer to authorize the transaction with") + } + signedTx, err := opts.Signer(opts.From, rawTx) + if err != nil { + return nil, err + } + if opts.NoSend { + return signedTx, nil + } + if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { + return nil, err + } + return signedTx, nil +} + +// FilterLogs filters contract logs for past blocks, returning the necessary +// channels to construct a strongly typed bound iterator on top of them. +func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]any) (chan types.Log, event.Subscription, error) { + // Don't crash on a lazy user + if opts == nil { + opts = new(FilterOpts) + } + // Append the event selector to the query parameters and construct the topic set + query = append([][]any{{c.abi.Events[name].ID}}, 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{c.address}, + Topics: topics, + FromBlock: new(big.Int).SetUint64(opts.Start), + } + if opts.End != nil { + config.ToBlock = new(big.Int).SetUint64(*opts.End) + } + /* TODO(karalabe): Replace the rest of the method below with this when supported + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + */ + buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) + if err != nil { + return nil, nil, err + } + sub := event.NewSubscription(func(quit <-chan struct{}) error { + for _, log := range buff { + select { + case logs <- log: + case <-quit: + return nil + } + } + return nil + }) + + return logs, sub, nil +} + +// WatchLogs filters subscribes to contract logs for future blocks, returning a +// subscription object that can be used to tear down the watcher. +func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]any) (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([][]any{{c.abi.Events[name].ID}}, 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{c.address}, + Topics: topics, + } + if opts.Start != nil { + config.FromBlock = new(big.Int).SetUint64(*opts.Start) + } + sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) + if err != nil { + return nil, nil, err + } + return logs, sub, nil +} + +// UnpackLog unpacks a retrieved log into the provided output structure. +func (c *BoundContract) UnpackLog(out any, event string, log types.Log) error { + // Anonymous events are not supported. + if len(log.Topics) == 0 { + return errNoEventSignature + } + if log.Topics[0] != c.abi.Events[event].ID { + return errEventSignatureMismatch + } + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return abi.ParseTopics(out, indexed, log.Topics[1:]) +} + +// UnpackLogIntoMap unpacks a retrieved log into the provided map. +func (c *BoundContract) UnpackLogIntoMap(out map[string]any, event string, log types.Log) error { + // Anonymous events are not supported. + if len(log.Topics) == 0 { + return errNoEventSignature + } + if log.Topics[0] != c.abi.Events[event].ID { + return errEventSignatureMismatch + } + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) +} + +// 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 } diff --git a/accounts/abi/bind/v2/base_test.go b/accounts/abi/bind/v2/base_test.go index 6e12323645..80d0f22f2c 100644 --- a/accounts/abi/bind/v2/base_test.go +++ b/accounts/abi/bind/v2/base_test.go @@ -142,7 +142,7 @@ func TestPassingBlockNumber(t *testing.T) { }, } - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), abi.ABI{ + bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{ Methods: map[string]abi.Method{ "something": { Name: "something", @@ -197,7 +197,7 @@ func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) expectedReceivedMap := map[string]interface{}{ "name": hash, @@ -214,7 +214,7 @@ func TestUnpackAnonymousLogIntoMap(t *testing.T) { abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) var received map[string]interface{} err := bc.UnpackLogIntoMap(received, "received", mockLog) @@ -241,7 +241,7 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) expectedReceivedMap := map[string]interface{}{ "names": hash, @@ -267,7 +267,7 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) expectedReceivedMap := map[string]interface{}{ "addresses": hash, @@ -294,7 +294,7 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { mockLog := newMockLog(topics, common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42")) abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) expectedReceivedMap := map[string]interface{}{ "function": functionTy, @@ -317,7 +317,7 @@ func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) expectedReceivedMap := map[string]interface{}{ "content": hash, @@ -335,7 +335,7 @@ func TestTransactGasFee(t *testing.T) { // GasTipCap and GasFeeCap // When opts.GasTipCap and opts.GasFeeCap are nil mt := &mockTransactor{baseFee: big.NewInt(100), gasTipCap: big.NewInt(5)} - bc := bind.NewBoundContractV1(common.Address{}, abi.ABI{}, nil, mt, nil) + bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil) opts := &bind.TransactOpts{Signer: mockSign} tx, err := bc.Transact(opts, "") assert.Nil(err) @@ -357,7 +357,7 @@ func TestTransactGasFee(t *testing.T) { // GasPrice // When opts.GasPrice is nil mt = &mockTransactor{gasPrice: big.NewInt(5)} - bc = bind.NewBoundContractV1(common.Address{}, abi.ABI{}, nil, mt, nil) + bc = bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil) opts = &bind.TransactOpts{Signer: mockSign} tx, err = bc.Transact(opts, "") assert.Nil(err) @@ -374,7 +374,7 @@ func TestTransactGasFee(t *testing.T) { assert.True(mt.suggestGasPriceCalled) } -func unpackAndCheck(t *testing.T, bc *bind.BoundContractV1, expected map[string]interface{}, mockLog types.Log) { +func unpackAndCheck(t *testing.T, bc *bind.BoundContract, expected map[string]interface{}, mockLog types.Log) { received := make(map[string]interface{}) if err := bc.UnpackLogIntoMap(received, "received", mockLog); err != nil { t.Error(err) @@ -551,7 +551,7 @@ func TestCall(t *testing.T) { wantErr: true, }} for _, test := range tests { - bc := bind.NewBoundContractV1(common.HexToAddress("0x0"), abi.ABI{ + bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{ Methods: map[string]abi.Method{ method: { Name: method, diff --git a/accounts/abi/bind/v2/boundcontractv1.go b/accounts/abi/bind/v2/boundcontractv1.go deleted file mode 100644 index f7859c68f5..0000000000 --- a/accounts/abi/bind/v2/boundcontractv1.go +++ /dev/null @@ -1,466 +0,0 @@ -package bind - -import ( - "context" - "errors" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// BoundContractV1 is the base wrapper object that reflects a contract on the -// Ethereum network. It contains a collection of methods that are used by the -// higher level contract bindings to operate. -type BoundContractV1 struct { - address common.Address // Deployment address of the contract on the Ethereum blockchain - abi abi.ABI // Reflect based ABI to access the correct Ethereum methods - caller ContractCaller // Read interface to interact with the blockchain - transactor ContractTransactor // Write interface to interact with the blockchain - filterer ContractFilterer // Event filtering to interact with the blockchain -} - -// NewBoundContractV1 creates a low level contract interface through which calls -// and transactions may be made through. -func NewBoundContractV1(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContractV1 { - return &BoundContractV1{ - address: address, - abi: abi, - caller: caller, - transactor: transactor, - filterer: filterer, - } -} - -// 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 returns, a slice of interfaces -// for anonymous returns and a struct for named returns. -func (c *BoundContractV1) Call(opts *CallOpts, results *[]any, method string, params ...any) error { - if results == nil { - results = new([]any) - } - // Pack the input, call and unpack the results - input, err := c.abi.Pack(method, params...) - if err != nil { - return err - } - output, err := c.call(opts, input) - if err != nil { - return err - } - if len(*results) == 0 { - res, err := c.abi.Unpack(method, output) - if err != nil { - return err - } - *results = res - return nil - } - res := *results - return c.abi.UnpackIntoInterface(res[0], method, output) -} - -// CallRaw executes an eth_call against the contract with the raw call data as -// input. It returns the call's return data or an error. -func (c *BoundContractV1) CallRaw(opts *CallOpts, input []byte) ([]byte, error) { - return c.call(opts, input) -} - -func (c *BoundContractV1) call(opts *CallOpts, input []byte) ([]byte, error) { - // Don't crash on a lazy user - if opts == nil { - opts = new(CallOpts) - } - var ( - msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input} - ctx = ensureContext(opts.Context) - code []byte - output []byte - err error - ) - if opts.Pending { - pb, ok := c.caller.(PendingContractCaller) - if !ok { - return nil, ErrNoPendingState - } - output, err = pb.PendingCallContract(ctx, msg) - if err != nil { - return nil, err - } - if len(output) == 0 { - // Make sure we have a contract to operate on, and bail out otherwise. - if code, err = pb.PendingCodeAt(ctx, c.address); err != nil { - return nil, err - } else if len(code) == 0 { - return nil, ErrNoCode - } - } - } else if opts.BlockHash != (common.Hash{}) { - bh, ok := c.caller.(BlockHashContractCaller) - if !ok { - return nil, ErrNoBlockHashState - } - output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash) - if err != nil { - return nil, err - } - if len(output) == 0 { - // Make sure we have a contract to operate on, and bail out otherwise. - if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil { - return nil, err - } else if len(code) == 0 { - return nil, ErrNoCode - } - } - } else { - output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) - if err != nil { - return nil, err - } - if len(output) == 0 { - // Make sure we have a contract to operate on, and bail out otherwise. - if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil { - return nil, err - } else if len(code) == 0 { - return nil, ErrNoCode - } - } - } - return output, nil -} - -// Transact invokes the (paid) contract method with params as input values. -func (c *BoundContractV1) Transact(opts *TransactOpts, method string, params ...any) (*types.Transaction, error) { - // Otherwise pack up the parameters and invoke the contract - input, err := c.abi.Pack(method, params...) - if err != nil { - return nil, err - } - return c.transact(opts, &c.address, input) -} - -// RawTransact initiates a transaction with the given raw call data as the input. -func (c *BoundContractV1) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { - return c.transact(opts, &c.address, calldata) -} - -// RawCreationTransact initiates a contract-creation transaction with the given -// raw call data as the input. -func (c *BoundContractV1) RawCreationTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { - return c.transact(opts, nil, calldata) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (c *BoundContractV1) Transfer(opts *TransactOpts) (*types.Transaction, error) { - // todo(rjl493456442) check the payable fallback or receive is defined - // or not, reject invalid transaction at the first place - return c.transact(opts, &c.address, nil) -} - -func (c *BoundContractV1) createDynamicTx(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 := c.transactor.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 (c *BoundContractV1) createLegacyTx(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 := c.transactor.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 = c.estimateGasLimit(opts, contract, input, gasPrice, nil, nil, value) - if err != nil { - return nil, err - } - } - // create the transaction - nonce, err := c.getNonce(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 -} - -func (c *BoundContractV1) estimateGasLimit(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 := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.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 c.transactor.EstimateGas(ensureContext(opts.Context), msg) -} - -func (c *BoundContractV1) getNonce(opts *TransactOpts) (uint64, error) { - if opts.Nonce == nil { - return c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From) - } - return opts.Nonce.Uint64(), nil -} - -// transact executes an actual transaction invocation, first deriving any missing -// authorization fields, and then scheduling the transaction for execution. -func (c *BoundContractV1) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) { - if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) { - return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") - } - // Create the transaction - var ( - rawTx *types.Transaction - err error - ) - if opts.GasPrice != nil { - rawTx, err = c.createLegacyTx(opts, contract, input) - } else if opts.GasFeeCap != nil && opts.GasTipCap != nil { - rawTx, err = c.createDynamicTx(opts, contract, input, nil) - } else { - // Only query for basefee if gasPrice not specified - if head, errHead := c.transactor.HeaderByNumber(ensureContext(opts.Context), nil); errHead != nil { - return nil, errHead - } else if head.BaseFee != nil { - rawTx, err = c.createDynamicTx(opts, contract, input, head) - } else { - // Chain is not London ready -> use legacy transaction - rawTx, err = c.createLegacyTx(opts, contract, input) - } - } - if err != nil { - return nil, err - } - // Sign the transaction and schedule it for execution - if opts.Signer == nil { - return nil, errors.New("no signer to authorize the transaction with") - } - signedTx, err := opts.Signer(opts.From, rawTx) - if err != nil { - return nil, err - } - if opts.NoSend { - return signedTx, nil - } - if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil { - return nil, err - } - return signedTx, nil -} - -// FilterLogs filters contract logs for past blocks, returning the necessary -// channels to construct a strongly typed bound iterator on top of them. -func (c *BoundContractV1) FilterLogs(opts *FilterOpts, name string, query ...[]any) (chan types.Log, event.Subscription, error) { - // Don't crash on a lazy user - if opts == nil { - opts = new(FilterOpts) - } - // Append the event selector to the query parameters and construct the topic set - query = append([][]any{{c.abi.Events[name].ID}}, 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{c.address}, - Topics: topics, - FromBlock: new(big.Int).SetUint64(opts.Start), - } - if opts.End != nil { - config.ToBlock = new(big.Int).SetUint64(*opts.End) - } - /* TODO(karalabe): Replace the rest of the method below with this when supported - sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) - */ - buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config) - if err != nil { - return nil, nil, err - } - sub := event.NewSubscription(func(quit <-chan struct{}) error { - for _, log := range buff { - select { - case logs <- log: - case <-quit: - return nil - } - } - return nil - }) - - return logs, sub, nil -} - -// WatchLogs filters subscribes to contract logs for future blocks, returning a -// subscription object that can be used to tear down the watcher. -func (c *BoundContractV1) WatchLogs(opts *WatchOpts, name string, query ...[]any) (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([][]any{{c.abi.Events[name].ID}}, 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{c.address}, - Topics: topics, - } - if opts.Start != nil { - config.FromBlock = new(big.Int).SetUint64(*opts.Start) - } - sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs) - if err != nil { - return nil, nil, err - } - return logs, sub, nil -} - -// UnpackLog unpacks a retrieved log into the provided output structure. -func (c *BoundContractV1) UnpackLog(out any, event string, log types.Log) error { - // Anonymous events are not supported. - if len(log.Topics) == 0 { - return errNoEventSignature - } - if log.Topics[0] != c.abi.Events[event].ID { - return errEventSignatureMismatch - } - if len(log.Data) > 0 { - if err := c.abi.UnpackIntoInterface(out, event, log.Data); err != nil { - return err - } - } - var indexed abi.Arguments - for _, arg := range c.abi.Events[event].Inputs { - if arg.Indexed { - indexed = append(indexed, arg) - } - } - return abi.ParseTopics(out, indexed, log.Topics[1:]) -} - -// UnpackLogIntoMap unpacks a retrieved log into the provided map. -func (c *BoundContractV1) UnpackLogIntoMap(out map[string]any, event string, log types.Log) error { - // Anonymous events are not supported. - if len(log.Topics) == 0 { - return errNoEventSignature - } - if log.Topics[0] != c.abi.Events[event].ID { - return errEventSignatureMismatch - } - if len(log.Data) > 0 { - if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { - return err - } - } - var indexed abi.Arguments - for _, arg := range c.abi.Events[event].Inputs { - if arg.Indexed { - indexed = append(indexed, arg) - } - } - return abi.ParseTopicsIntoMap(out, indexed, log.Topics[1:]) -} - -// 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 -} diff --git a/accounts/abi/bind/v2/dep_tree.go b/accounts/abi/bind/v2/dep_tree.go index 6121103123..e07a82f3cd 100644 --- a/accounts/abi/bind/v2/dep_tree.go +++ b/accounts/abi/bind/v2/dep_tree.go @@ -64,7 +64,7 @@ type DeploymentResult struct { } // DeployFn deploys a contract given a deployer and optional input. It returns -// the address of the deployed contract and the deployment transaction, or an error if the deployment failed. +// the address and a pending transaction, or an error if the deployment failed. type DeployFn func(input, deployer []byte) (common.Address, *types.Transaction, error) // depTreeDeployer is responsible for taking a dependency, deploying-and-linking diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index c7895b9141..c9c4da5547 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -33,9 +33,9 @@ type ContractEvent interface { } // FilterEvents returns an EventIterator instance for filtering historical events based on the event id and a block range. -func FilterEvents[Ev ContractEvent](c BoundContract, opts *FilterOpts, unpack func(*types.Log) (*Ev, error), topics ...[]any) (*EventIterator[Ev], error) { +func FilterEvents[Ev ContractEvent](c *BoundContract, opts *FilterOpts, unpack func(*types.Log) (*Ev, error), topics ...[]any) (*EventIterator[Ev], error) { var e Ev - logs, sub, err := c.filterLogs(opts, e.ContractEventName(), topics...) + logs, sub, err := c.FilterLogs(opts, e.ContractEventName(), topics...) if err != nil { return nil, err } @@ -46,9 +46,9 @@ func FilterEvents[Ev ContractEvent](c BoundContract, opts *FilterOpts, unpack fu // contract to be intercepted, unpacked, and forwarded to sink. If // unpack returns an error, the returned subscription is closed with the // error. -func WatchEvents[Ev ContractEvent](c BoundContract, opts *WatchOpts, unpack func(*types.Log) (*Ev, error), sink chan<- *Ev, topics ...[]any) (event.Subscription, error) { +func WatchEvents[Ev ContractEvent](c *BoundContract, opts *WatchOpts, unpack func(*types.Log) (*Ev, error), sink chan<- *Ev, topics ...[]any) (event.Subscription, error) { var e Ev - logs, sub, err := c.watchLogs(opts, e.ContractEventName(), topics...) + logs, sub, err := c.WatchLogs(opts, e.ContractEventName(), topics...) if err != nil { return nil, err } @@ -156,9 +156,9 @@ func (it *EventIterator[T]) Close() error { // // To call a function that doesn't return any output, pass nil as the unpack function. // This can be useful if you just want to check that the function doesn't revert. -func Call[T any](c BoundContract, opts *CallOpts, packedInput []byte, unpack func([]byte) (T, error)) (T, error) { +func Call[T any](c *BoundContract, opts *CallOpts, packedInput []byte, unpack func([]byte) (T, error)) (T, error) { var defaultResult T - packedOutput, err := c.call(opts, packedInput) + packedOutput, err := c.CallRaw(opts, packedInput) if err != nil { return defaultResult, err } @@ -175,17 +175,12 @@ func Call[T any](c BoundContract, opts *CallOpts, packedInput []byte, unpack fun return res, err } -// Transact initiates a transaction with the given raw calldata as the input. -func Transact(c BoundContract, opt *TransactOpts, packedInput []byte) (*types.Transaction, error) { - addr := c.addr() - return c.transact(opt, &addr, packedInput) -} - // DeployContract deploys a contract onto the Ethereum blockchain and binds the -// deployment address with a Go wrapper. It expects its parameters to be abi-encoded +// deployment address with a Go wrapper. It expects its parameters to be abi-encoded // bytes. func DeployContract(opts *TransactOpts, bytecode []byte, backend ContractBackend, packedParams []byte) (common.Address, *types.Transaction, error) { - c := NewBoundContractV1(common.Address{}, abi.ABI{}, backend, backend, backend) + c := NewBoundContract(common.Address{}, abi.ABI{}, backend, backend, backend) + tx, err := c.RawCreationTransact(opts, append(bytecode, packedParams...)) if err != nil { return common.Address{}, nil, err diff --git a/accounts/abi/bind/v2/lib_test.go b/accounts/abi/bind/v2/lib_test.go index 5cbc0374b5..295b718ed1 100644 --- a/accounts/abi/bind/v2/lib_test.go +++ b/accounts/abi/bind/v2/lib_test.go @@ -236,7 +236,7 @@ func TestEvents(t *testing.T) { defer sub2.Unsubscribe() packedInput := c.PackEmitMulti() - tx, err := bind.Transact(instance, defaultTxAuth(), packedInput) + tx, err := instance.RawTransact(defaultTxAuth(), packedInput) if err != nil { t.Fatalf("failed to send transaction: %v", err) }