diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index c7efca440b1f..1cb6280c3d50 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -387,7 +387,8 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM if err != nil { return nil, err } - res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB) + // We can pass the state here since it will be dropped eventually. + res, _, err := b.callContract(ctx, call, &callContext{state: stateDB, header: b.blockchain.CurrentBlock().Header()}) if err != nil { return nil, err } @@ -402,9 +403,10 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { b.mu.Lock() defer b.mu.Unlock() - defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) - res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) + // It's necessary to pass the copied state here, the state will be modified + // during the call. + res, _, err := b.callContract(ctx, call, &callContext{state: b.pendingState.Copy(), header: b.pendingBlock.Header()}) if err != nil { return nil, err } @@ -430,12 +432,19 @@ func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error return big.NewInt(1), nil } -// EstimateGas executes the requested code against the currently pending block/state and -// returns the used amount of gas. -func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { +// estimateGas is the internal version of EstimateGas. It accepts the call message as +// well as the call context. It can be used to execute a single call or a serial calls +// with the supplied call context(the intermediate state can be saved). +func (b *SimulatedBackend) estimateGas(ctx context.Context, call ethereum.CallMsg, callctx *callContext) (uint64, *callContext, error) { b.mu.Lock() defer b.mu.Unlock() + if callctx == nil { + callctx = &callContext{ + state: b.pendingState, + header: b.pendingBlock.Header(), + } + } // Determine the lowest and highest possible gas limits to binary search in between var ( lo uint64 = params.TxGas - 1 @@ -453,7 +462,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs available := new(big.Int).Set(balance) if call.Value != nil { if call.Value.Cmp(available) >= 0 { - return 0, errors.New("insufficient funds for transfer") + return 0, nil, errors.New("insufficient funds for transfer") } available.Sub(available, call.Value) } @@ -471,61 +480,100 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs cap = hi // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, error) { + executable := func(gas uint64) (bool, *core.ExecutionResult, *callContext, error) { call.Gas = gas - - snapshot := b.pendingState.Snapshot() - res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) - b.pendingState.RevertToSnapshot(snapshot) - + res, after, err := b.callContract(ctx, call, callctx.copy()) // only pass the copy if err != nil { if err == core.ErrIntrinsicGas { - return true, nil, nil // Special case, raise gas limit + return true, nil, nil, nil // Special case, raise gas limit } - return true, nil, err // Bail out + return true, nil, nil, err // Bail out } - return res.Failed(), res, nil + return res.Failed(), res, after, nil } // Execute the binary search and hone in on an executable gas limit + var after *callContext for lo+1 < hi { mid := (hi + lo) / 2 - failed, _, err := executable(mid) + failed, _, callctx, err := executable(mid) // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is // assigned. Return the error directly, don't struggle any more if err != nil { - return 0, err + return 0, nil, err } if failed { lo = mid } else { - hi = mid + hi, after = mid, callctx } } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - failed, result, err := executable(hi) + failed, result, callctx, err := executable(hi) if err != nil { - return 0, err + return 0, nil, err } if failed { if result != nil && result.Err != vm.ErrOutOfGas { if len(result.Revert()) > 0 { - return 0, newRevertError(result) + return 0, nil, newRevertError(result) } - return 0, result.Err + return 0, nil, result.Err } // Otherwise, the specified gas cap is too low - return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) + return 0, nil, fmt.Errorf("gas required exceeds allowance (%d)", cap) } + after = callctx + } + return hi, after, nil +} + +// EstimateGas executes the requested code against the currently pending block/state and +// returns the used amount of gas. +func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + res, _, err := b.estimateGas(ctx, call, nil) + return res, err +} + +// EstimateGasList returns an estimate of the amount of gas needed to execute list of +// given transactions against the current pending block. +func (b *SimulatedBackend) EstimateGasList(ctx context.Context, calls []ethereum.CallMsg) ([]uint64, error) { + var ( + gas uint64 + err error + callctx *callContext + ) + res := make([]uint64, len(calls)) + for idx, call := range calls { + gas, callctx, err = b.estimateGas(ctx, call, callctx) + if err != nil { + return nil, err + } + res[idx] = gas + } + return res, nil +} + +// callContext is the environment for one call or a serial calls execution +type callContext struct { + state *state.StateDB // The state used to execute call + header *types.Header // The associated header, it should be **immutable** +} + +// copy returns the copied instance of call environment. +func (callctx *callContext) copy() *callContext { + return &callContext{ + state: callctx.state.Copy(), + header: callctx.header, } - return hi, nil } // callContract implements common code between normal and pending contract calls. -// state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, stateDB *state.StateDB) (*core.ExecutionResult, error) { +// State in the callctx is modified during execution, make sure to copy it if +// necessary. The callctx is always assumed not nil. +func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, callctx *callContext) (*core.ExecutionResult, *callContext, error) { // Ensure message is initialized properly. if call.GasPrice == nil { call.GasPrice = big.NewInt(1) @@ -537,18 +585,24 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM call.Value = new(big.Int) } // Set infinite balance to the fake caller account. - from := stateDB.GetOrNewStateObject(call.From) + from := callctx.state.GetOrNewStateObject(call.From) from.SetBalance(math.MaxBig256) // Execute the call. msg := callMsg{call} - evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil) + evmContext := core.NewEVMContext(msg, callctx.header, b.blockchain, nil) // Create a new environment which holds all relevant information // about the transaction and calling mechanisms. - vmEnv := vm.NewEVM(evmContext, stateDB, b.config, vm.Config{}) - gasPool := new(core.GasPool).AddGas(math.MaxUint64) + vmenv := vm.NewEVM(evmContext, callctx.state, b.config, vm.Config{}) + gaspool := new(core.GasPool).AddGas(math.MaxUint64) - return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb() + res, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb() + if err != nil { + return nil, nil, err + } + // It's necessary to call finalise if we are executing a batch of calls. + callctx.state.Finalise(b.blockchain.Config().IsEIP158(callctx.header.Number)) + return res, callctx, nil } // SendTransaction updates the pending block to include the given transaction. diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index e2597cca0163..e41fb0b29af3 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -581,6 +581,64 @@ func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) { } } +func TestSimulatedBackend_EstimateGasList(t *testing.T) { + /* + pragma solidity >=0.6.0 <0.7.0; + + contract HelloWorld { + uint256 value; + function setValue(uint256 _value) public { + value = _value; + } + function getValue() public view returns (uint256) { + if (value == 0) { + revert(); + } + return value; + } + } + */ + const contractAbi = "[{\"inputs\":[],\"name\":\"getValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"setValue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + const contractBin = "0x608060405234801561001057600080fd5b5060d68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80632096525514603757806355241077146053575b600080fd5b603d607e565b6040518082815260200191505060405180910390f35b607c60048036036020811015606757600080fd5b81019080803590602001909291905050506096565b005b6000806000541415608e57600080fd5b600054905090565b806000819055505056fea264697066735822122076346f88e003f2d4fd66d3f372e7ac9862064d78f35048169307a88d9778b10364736f6c634300060a0033" + + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + opts := bind.NewKeyedTransactor(key) + + sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000) + defer sim.Close() + + parsed, _ := abi.JSON(strings.NewReader(contractAbi)) + contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) + sim.Commit() + + var ( + calls []ethereum.CallMsg + gas []uint64 + ) + calls = append(calls, ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Data: common.Hex2Bytes("552410770000000000000000000000000000000000000000000000000000000000000064"), + }) + gas = append(gas, 41446) + + calls = append(calls, ethereum.CallMsg{ + From: addr, + To: &contractAddr, + Data: common.Hex2Bytes("20965255"), + }) + gas = append(gas, 22881) + + res, err := sim.EstimateGasList(context.Background(), calls) + if err != nil { + t.Fatalf("Failed to estimate gas: %v", err) + } + if !reflect.DeepEqual(res, gas) { + t.Fatalf("Estimated gas mismatch, want: %v, got: %v", gas, res) + } +} + func TestSimulatedBackend_HeaderByHash(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) diff --git a/graphql/graphql.go b/graphql/graphql.go index 559da8aaaa7a..cff657befa32 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -803,7 +803,7 @@ func (b *Block) Call(ctx context.Context, args struct { return nil, err } } - result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) + result, _, err := ethapi.DoCall(ctx, b.backend, args.Data, nil, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) if err != nil { return nil, err } @@ -828,7 +828,7 @@ func (b *Block) EstimateGas(ctx context.Context, args struct { return hexutil.Uint64(0), err } } - gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap()) + gas, _, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, nil, *b.numberOrHash, b.backend.RPCGasCap()) return gas, err } @@ -873,7 +873,7 @@ func (p *Pending) Call(ctx context.Context, args struct { Data ethapi.CallArgs }) (*CallResult, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) + result, _, err := ethapi.DoCall(ctx, p.backend, args.Data, nil, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) if err != nil { return nil, err } @@ -893,7 +893,8 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.CallArgs }) (hexutil.Uint64, error) { pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - return ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap()) + gas, _, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, nil, pendingBlockNr, p.backend.RPCGasCap()) + return gas, err } // Resolver is the top-level object in the GraphQL hierarchy. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c7d1e0020f77..31a38d08f390 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -814,39 +815,66 @@ type account struct { StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` } -func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) +// callContext is the environment for one call or a serial calls execution +type callContext struct { + state *state.StateDB // The state used to execute call + header *types.Header // The associated header, it should be **immutable** + overridden bool // Indicator whether the call state has been overriden +} - state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if state == nil || err != nil { - return nil, err +// copy returns the copied instance of call environment. +func (callctx *callContext) copy() *callContext { + return &callContext{ + state: callctx.state.Copy(), + header: callctx.header, + overridden: callctx.overridden, } - // Override the fields of specified contracts before execution. - for addr, account := range overrides { - // Override account nonce. - if account.Nonce != nil { - state.SetNonce(addr, uint64(*account.Nonce)) - } - // Override account(contract) code. - if account.Code != nil { - state.SetCode(addr, *account.Code) - } - // Override account balance. - if account.Balance != nil { - state.SetBalance(addr, (*big.Int)(*account.Balance)) - } - if account.State != nil && account.StateDiff != nil { - return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) +} + +func DoCall(ctx context.Context, b Backend, args CallArgs, callctx *callContext, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, *callContext, error) { + defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + + if callctx == nil { + state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return nil, nil, err } - // Replace entire state if caller requires. - if account.State != nil { - state.SetStorage(addr, *account.State) + callctx = &callContext{ + state: state, + header: header, } - // Apply state diff into specified accounts. - if account.StateDiff != nil { - for key, value := range *account.StateDiff { - state.SetState(addr, key, value) + } + // Override the fields of specified contracts before execution. + // Note if the call context is not nil(we are executing a serial + // of calls), don't apply the diff multi-time. + if overrides != nil && !callctx.overridden { + for addr, account := range overrides { + // Override account nonce. + if account.Nonce != nil { + callctx.state.SetNonce(addr, uint64(*account.Nonce)) + } + // Override account(contract) code. + if account.Code != nil { + callctx.state.SetCode(addr, *account.Code) + } + // Override account balance. + if account.Balance != nil { + callctx.state.SetBalance(addr, (*big.Int)(*account.Balance)) } + if account.State != nil && account.StateDiff != nil { + return nil, nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) + } + // Replace entire state if caller requires. + if account.State != nil { + callctx.state.SetStorage(addr, *account.State) + } + // Apply state diff into specified accounts. + if account.StateDiff != nil { + for key, value := range *account.StateDiff { + callctx.state.SetState(addr, key, value) + } + } + callctx.overridden = true } } // Setup context so it may be cancelled the call has completed @@ -863,9 +891,9 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo // Get a new instance of the EVM. msg := args.ToMessage(globalGasCap) - evm, vmError, err := b.GetEVM(ctx, msg, state, header) + evm, vmError, err := b.GetEVM(ctx, msg, callctx.state, callctx.header) if err != nil { - return nil, err + return nil, nil, err } // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -879,16 +907,17 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo gp := new(core.GasPool).AddGas(math.MaxUint64) result, err := core.ApplyMessage(evm, msg, gp) if err := vmError(); err != nil { - return nil, err + return nil, nil, err } // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { - return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) + return nil, nil, fmt.Errorf("execution aborted (timeout = %v)", timeout) } if err != nil { - return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas()) + return result, nil, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas()) } - return result, nil + callctx.state.Finalise(b.ChainConfig().IsEIP158(callctx.header.Number)) + return result, callctx, err } func newRevertError(result *core.ExecutionResult) *revertError { @@ -932,7 +961,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr if overrides != nil { accounts = *overrides } - result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) + result, _, err := DoCall(ctx, s.b, args, nil, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) if err != nil { return nil, err } @@ -943,7 +972,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr return result.Return(), result.Err } -func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, callctx *callContext, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, *callContext, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( lo uint64 = params.TxGas - 1 @@ -961,10 +990,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash // Retrieve the block to act as the gas ceiling block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash) if err != nil { - return 0, err + return 0, nil, err } if block == nil { - return 0, errors.New("block not found") + return 0, nil, errors.New("block not found") } hi = block.GasLimit() } @@ -972,13 +1001,13 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash if args.GasPrice != nil && args.GasPrice.ToInt().BitLen() != 0 { state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if err != nil { - return 0, err + return 0, nil, err } balance := state.GetBalance(*args.From) // from can't be nil available := new(big.Int).Set(balance) if args.Value != nil { if args.Value.ToInt().Cmp(available) >= 0 { - return 0, errors.New("insufficient funds for transfer") + return 0, nil, errors.New("insufficient funds for transfer") } available.Sub(available, args.Value.ToInt()) } @@ -1003,53 +1032,60 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash cap = hi // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, error) { + executable := func(gas uint64) (bool, *core.ExecutionResult, *callContext, error) { args.Gas = (*hexutil.Uint64)(&gas) - result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) + var callctxcopy *callContext + if callctx != nil { + callctxcopy = callctx.copy() + } + result, after, err := DoCall(ctx, b, args, callctxcopy, blockNrOrHash, nil, vm.Config{}, 0, gasCap) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { - return true, nil, nil // Special case, raise gas limit + return true, nil, nil, nil // Special case, raise gas limit } - return true, nil, err // Bail out + return true, nil, nil, err // Bail out } - return result.Failed(), result, nil + return result.Failed(), result, after, nil } // Execute the binary search and hone in on an executable gas limit + var after *callContext + for lo+1 < hi { mid := (hi + lo) / 2 - failed, _, err := executable(mid) + failed, _, callctx, err := executable(mid) // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is // assigned. Return the error directly, don't struggle any more. if err != nil { - return 0, err + return 0, nil, err } if failed { lo = mid } else { - hi = mid + hi, after = mid, callctx } } // Reject the transaction as invalid if it still fails at the highest allowance if hi == cap { - failed, result, err := executable(hi) + failed, result, callctx, err := executable(hi) if err != nil { - return 0, err + return 0, nil, err } if failed { if result != nil && result.Err != vm.ErrOutOfGas { if len(result.Revert()) > 0 { - return 0, newRevertError(result) + return 0, nil, newRevertError(result) } - return 0, result.Err + return 0, nil, result.Err } // Otherwise, the specified gas cap is too low - return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) + return 0, nil, fmt.Errorf("gas required exceeds allowance (%d)", cap) } + after = callctx } - return hexutil.Uint64(hi), nil + return hexutil.Uint64(hi), after, nil } // EstimateGas returns an estimate of the amount of gas needed to execute the @@ -1059,7 +1095,28 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, bl if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } - return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) + gas, _, err := DoEstimateGas(ctx, s.b, args, nil, bNrOrHash, s.b.RPCGasCap()) + return gas, err +} + +// EstimateGasList returns an estimate of the amount of gas needed to execute list of +// given transactions against the current pending block. +func (s *PublicBlockChainAPI) EstimateGasList(ctx context.Context, argsList []CallArgs) ([]hexutil.Uint64, error) { + blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) + var ( + gas hexutil.Uint64 + err error + callctx *callContext + ) + res := make([]hexutil.Uint64, len(argsList)) + for idx, args := range argsList { + gas, callctx, err = DoEstimateGas(ctx, s.b, args, callctx, blockNrOrHash, s.b.RPCGasCap()) + if err != nil { + return nil, err + } + res[idx] = gas + } + return res, nil } // ExecutionResult groups all structured logs emitted by the EVM @@ -1530,7 +1587,7 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error { Data: input, } pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) + estimated, _, err := DoEstimateGas(ctx, b, callArgs, nil, pendingBlockNr, b.RPCGasCap()) if err != nil { return err }