diff --git a/.gitignore b/.gitignore index ac0f4efdfb4a..7000fedd25cd 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ profile.cov # IdeaIDE .idea +*.iml # VS Code .vscode diff --git a/graphql/graphql.go b/graphql/graphql.go index 57dfd14ecdb1..6e364de6d9c9 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -1208,7 +1208,7 @@ func (b *Block) Call(ctx context.Context, args struct { func (b *Block) EstimateGas(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (hexutil.Uint64, error) { - return ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCGasCap()) + return ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, b.r.backend.RPCGasCap()) } type Pending struct { @@ -1272,7 +1272,7 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (hexutil.Uint64, error) { latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) - return ethapi.DoEstimateGas(ctx, p.r.backend, args.Data, latestBlockNr, nil, p.r.backend.RPCGasCap()) + return ethapi.DoEstimateGas(ctx, p.r.backend, args.Data, latestBlockNr, nil, nil, p.r.backend.RPCGasCap()) } // Resolver is the top-level object in the GraphQL hierarchy. diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bea615bf445d..4a80942162b4 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -40,7 +40,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/gasestimator" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" @@ -973,19 +972,27 @@ func (api *BlockChainAPI) SimulateV1(ctx context.Context, opts simOpts, blockNrO // successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if // there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil & // non-zero) and `gasCap` (if non-zero). -func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides, gasCap uint64) (hexutil.Uint64, error) { // Retrieve the base state and mutate it with any overrides state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return 0, err } - if err := overrides.Apply(state, nil); err != nil { + chainContext := NewChainContext(ctx, b) + blockCtx := core.NewEVMBlockContext(header, chainContext, nil) + if blockOverrides != nil { + blockOverrides.Apply(&blockCtx) + } + + rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time) + precompiles := maps.Clone(vm.ActivePrecompiledContracts(rules)) + if err := overrides.Apply(state, precompiles); err != nil { return 0, err } // Construct the gas estimator option from the user input - opts := &gasestimator.Options{ + opts := &Options{ Config: b.ChainConfig(), - Chain: NewChainContext(ctx, b), + Chain: chainContext, Header: header, State: state, ErrorRatio: estimateGasErrorRatio, @@ -1001,7 +1008,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr call := args.ToMessage(header.BaseFee, true, true) // Run the gas estimation and wrap any revertals into a custom return - estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap) + estimate, revert, err := Estimate(ctx, call, opts, gasCap, blockOverrides) if err != nil { if len(revert) > 0 { return 0, newRevertError(revert) @@ -1017,12 +1024,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr // value is capped by both `args.Gas` (if non-nil & non-zero) and the backend's RPCGasCap // configuration (if non-zero). // Note: Required blob gas is not computed in this method. -func (api *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) { +func (api *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Uint64, error) { bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } - return DoEstimateGas(ctx, api.b, args, bNrOrHash, overrides, api.b.RPCGasCap()) + return DoEstimateGas(ctx, api.b, args, bNrOrHash, overrides, blockOverrides, api.b.RPCGasCap()) } // RPCMarshalHeader converts the given header to the RPC output . diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 758638cb16f1..c008096be574 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -34,6 +34,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -641,6 +643,17 @@ func TestEstimateGas(t *testing.T) { signer = types.HomesteadSigner{} randomAccounts = newAccounts(2) ) + packRevert := func(revertMessage string) []byte { + var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] + stringType, _ := abi.NewType("string", "", nil) + args := abi.Arguments{ + {Type: stringType}, + } + encodedMessage, _ := args.Pack(revertMessage) + + return append(revertSelector, encodedMessage...) + } + api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei @@ -649,14 +662,16 @@ func TestEstimateGas(t *testing.T) { b.AddTx(tx) b.SetPoS() })) + var testSuite = []struct { - blockNumber rpc.BlockNumber - call TransactionArgs - overrides StateOverride - expectErr error - want uint64 + blockNumber rpc.BlockNumber + call TransactionArgs + overrides StateOverride + blockOverrides BlockOverrides + expectErr error + want uint64 }{ - // simple transfer on latest block + //simple transfer on latest block { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ @@ -759,16 +774,65 @@ func TestEstimateGas(t *testing.T) { }, want: 21000, }, + // // SPDX-License-Identifier: GPL-3.0 + //pragma solidity >=0.8.2 <0.9.0; + // + //contract BlockOverridesTest { + // function call() public view returns (uint256) { + // return block.number; + // } + // + // function estimate() public view { + // revert(string.concat("block ", uint2str(block.number))); + // } + // + // function uint2str(uint256 _i) internal pure returns (string memory str) { + // if (_i == 0) { + // return "0"; + // } + // uint256 j = _i; + // uint256 length; + // while (j != 0) { + // length++; + // j /= 10; + // } + // bytes memory bstr = new bytes(length); + // uint256 k = length; + // j = _i; + // while (j != 0) { + // bstr[--k] = bytes1(uint8(48 + (j % 10))); + // j /= 10; + // } + // str = string(bstr); + // } + //} + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Data: hex2Bytes("0x3592d016"), //estimate + }, + overrides: StateOverride{ + accounts[1].addr: OverrideAccount{ + Code: hex2Bytes("608060405234801561000f575f5ffd5b5060043610610034575f3560e01c806328b5e32b146100385780633592d0161461004b575b5f5ffd5b4360405190815260200160405180910390f35b610053610055565b005b61005e4361009d565b60405160200161006e91906101a5565b60408051601f198184030181529082905262461bcd60e51b8252610094916004016101cd565b60405180910390fd5b6060815f036100c35750506040805180820190915260018152600360fc1b602082015290565b815f5b81156100ec57806100d681610216565b91506100e59050600a83610242565b91506100c6565b5f8167ffffffffffffffff81111561010657610106610255565b6040519080825280601f01601f191660200182016040528015610130576020820181803683370190505b508593509050815b831561019c57610149600a85610269565b61015490603061027c565b60f81b8261016183610295565b92508281518110610174576101746102aa565b60200101906001600160f81b03191690815f1a905350610195600a85610242565b9350610138565b50949350505050565b650313637b1b5960d51b81525f82518060208501600685015e5f920160060191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b634e487b7160e01b5f52601160045260245ffd5b5f6001820161022757610227610202565b5060010190565b634e487b7160e01b5f52601260045260245ffd5b5f826102505761025061022e565b500490565b634e487b7160e01b5f52604160045260245ffd5b5f826102775761027761022e565b500690565b8082018082111561028f5761028f610202565b92915050565b5f816102a3576102a3610202565b505f190190565b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220a253cad1e2e3523b8c053c1d0cd1e39d7f3bafcedd73440a244872701f05dab264736f6c634300081c0033"), + }, + }, + blockOverrides: BlockOverrides{Number: (*hexutil.Big)(big.NewInt(11))}, + expectErr: newRevertError(packRevert("block 11")), + }, } for i, tc := range testSuite { - result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides) + result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) continue } if !errors.Is(err, tc.expectErr) { - t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + if !reflect.DeepEqual(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) + } } continue } @@ -929,16 +993,49 @@ func TestCall(t *testing.T) { }, want: "0x000000000000000000000000000000000000000000000000000000000000007b", }, - // Block overrides should work + // // SPDX-License-Identifier: GPL-3.0 + //pragma solidity >=0.8.2 <0.9.0; + // + //contract BlockOverridesTest { + // function call() public view returns (uint256) { + // return block.number; + // } + // + // function estimate() public view { + // revert(string.concat("block ", uint2str(block.number))); + // } + // + // function uint2str(uint256 _i) internal pure returns (string memory str) { + // if (_i == 0) { + // return "0"; + // } + // uint256 j = _i; + // uint256 length; + // while (j != 0) { + // length++; + // j /= 10; + // } + // bytes memory bstr = new bytes(length); + // uint256 k = length; + // j = _i; + // while (j != 0) { + // bstr[--k] = bytes1(uint8(48 + (j % 10))); + // j /= 10; + // } + // str = string(bstr); + // } + //} { - name: "block-override", + name: "block-override-with-state-override", blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[1].addr, - Input: &hexutil.Bytes{ - 0x43, // NUMBER - 0x60, 0x00, 0x52, // MSTORE offset 0 - 0x60, 0x20, 0x60, 0x00, 0xf3, + To: &accounts[2].addr, + Data: hex2Bytes("0x28b5e32b"), //call + }, + overrides: StateOverride{ + accounts[2].addr: OverrideAccount{ + Code: hex2Bytes("608060405234801561000f575f5ffd5b5060043610610034575f3560e01c806328b5e32b146100385780633592d0161461004b575b5f5ffd5b4360405190815260200160405180910390f35b610053610055565b005b61005e4361009d565b60405160200161006e91906101a5565b60408051601f198184030181529082905262461bcd60e51b8252610094916004016101cd565b60405180910390fd5b6060815f036100c35750506040805180820190915260018152600360fc1b602082015290565b815f5b81156100ec57806100d681610216565b91506100e59050600a83610242565b91506100c6565b5f8167ffffffffffffffff81111561010657610106610255565b6040519080825280601f01601f191660200182016040528015610130576020820181803683370190505b508593509050815b831561019c57610149600a85610269565b61015490603061027c565b60f81b8261016183610295565b92508281518110610174576101746102aa565b60200101906001600160f81b03191690815f1a905350610195600a85610242565b9350610138565b50949350505050565b650313637b1b5960d51b81525f82518060208501600685015e5f920160060191825250919050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b634e487b7160e01b5f52601160045260245ffd5b5f6001820161022757610227610202565b5060010190565b634e487b7160e01b5f52601260045260245ffd5b5f826102505761025061022e565b500490565b634e487b7160e01b5f52604160045260245ffd5b5f826102775761027761022e565b500690565b8082018082111561028f5761028f610202565b92915050565b5f816102a3576102a3610202565b505f190190565b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220a253cad1e2e3523b8c053c1d0cd1e39d7f3bafcedd73440a244872701f05dab264736f6c634300081c0033"), }, }, blockOverrides: BlockOverrides{Number: (*hexutil.Big)(big.NewInt(11))}, diff --git a/eth/gasestimator/gasestimator.go b/internal/ethapi/gasestimator.go similarity index 93% rename from eth/gasestimator/gasestimator.go rename to internal/ethapi/gasestimator.go index d43057dda27d..3e343f777360 100644 --- a/eth/gasestimator/gasestimator.go +++ b/internal/ethapi/gasestimator.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package gasestimator +package ethapi import ( "context" @@ -49,7 +49,7 @@ type Options struct { // Estimate returns the lowest possible gas limit that allows the transaction to // run successfully with the provided context options. It returns an error if the // transaction would always revert, or if there are unexpected failures. -func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) { +func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64, overrides *BlockOverrides) (uint64, []byte, error) { // Binary search the gas limit, as it may need to be higher than the amount used var ( lo uint64 // lowest-known gas limit where tx execution fails @@ -114,7 +114,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin // unused access list items). Ever so slightly wasteful, but safer overall. if len(call.Data) == 0 { if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 { - failed, _, err := execute(ctx, call, opts, params.TxGas) + failed, _, err := execute(ctx, call, opts, params.TxGas, nil) if !failed && err == nil { return params.TxGas, nil, nil } @@ -122,7 +122,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin } // We first execute the transaction at the highest allowable gas limit, since if this fails we // can return error immediately. - failed, result, err := execute(ctx, call, opts, hi) + failed, result, err := execute(ctx, call, opts, hi, overrides) if err != nil { return 0, nil, err } @@ -144,7 +144,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin // check that gas amount and use as a limit for the binary search. optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63 if optimisticGasLimit < hi { - failed, _, err = execute(ctx, call, opts, optimisticGasLimit) + failed, _, err = execute(ctx, call, opts, optimisticGasLimit, nil) if err != nil { // This should not happen under normal conditions since if we make it this far the // transaction had run without error at least once before. @@ -175,7 +175,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin // range here is skewed to favor the low side. mid = lo * 2 } - failed, _, err = execute(ctx, call, opts, mid) + failed, _, err = execute(ctx, call, opts, mid, nil) if err != nil { // This should not happen under normal conditions since if we make it this far the // transaction had run without error at least once before. @@ -195,14 +195,14 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin // returns true if the transaction fails for a reason that might be related to // not enough gas. A non-nil error means execution failed due to reasons unrelated // to the gas limit. -func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64) (bool, *core.ExecutionResult, error) { +func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64, overrides *BlockOverrides) (bool, *core.ExecutionResult, error) { // Configure the call for this specific execution (and revert the change after) defer func(gas uint64) { call.GasLimit = gas }(call.GasLimit) call.GasLimit = gasLimit // Execute the call and separate execution faults caused by a lack of gas or // other non-fixable conditions - result, err := run(ctx, call, opts) + result, err := run(ctx, call, opts, overrides) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { return true, nil, nil // Special case, raise gas limit @@ -214,7 +214,7 @@ func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit ui // run assembles the EVM as defined by the consensus rules and runs the requested // call invocation. -func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) { +func run(ctx context.Context, call *core.Message, opts *Options, overrides *BlockOverrides) (*core.ExecutionResult, error) { // Assemble the call and the call context var ( msgContext = core.NewEVMTxContext(call) @@ -222,6 +222,9 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio dirtyState = opts.State.Copy() ) + + overrides.Apply(&evmContext) + // Lower the basefee to 0 to avoid breaking EVM // invariants (basefee < feecap). if msgContext.GasPrice.Sign() == 0 { diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index b6346e910da1..fb40638a17e7 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -160,7 +160,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGas BlobHashes: args.BlobHashes, } latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) - estimated, err := DoEstimateGas(ctx, b, callArgs, latestBlockNr, nil, b.RPCGasCap()) + estimated, err := DoEstimateGas(ctx, b, callArgs, latestBlockNr, nil, nil, b.RPCGasCap()) if err != nil { return err } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 0c346bbf7971..8ac8f44958b0 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -503,8 +503,8 @@ web3._extend({ new web3._extend.Method({ name: 'estimateGas', call: 'eth_estimateGas', - params: 3, - inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputBlockNumberFormatter, null], + params: 4, + inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputBlockNumberFormatter, null, null], outputFormatter: web3._extend.utils.toDecimal }), new web3._extend.Method({