diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index f45fc0d8c9..2ee49476aa 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -44,6 +44,8 @@ type Options struct { BlobBaseFee *big.Int // BlobBaseFee optionally overrides the blob base fee in the execution context. + Precompiles vm.PrecompiledContracts // Precompiles optionally overrides the default set of precompiles (e.g. for state overrides that move precompiles). + ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination } @@ -245,6 +247,9 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio } evm := vm.NewEVM(evmContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true}) defer evm.Release() + if opts.Precompiles != nil { + evm.SetPrecompiles(opts.Precompiles) + } // Monitor the outer context and interrupt the EVM upon cancellation. To avoid // a dangling goroutine until the outer estimation finishes, create an internal diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2eb4dee3c0..572b2d1abe 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -943,6 +943,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr Header: header, State: state, BlobBaseFee: blobBaseFee, + Precompiles: precompiles, ErrorRatio: estimateGasErrorRatio, } // Set any required transaction default, but make sure the gas cap itself is not messed with diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 80a9036ecc..033843f2e9 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -3984,6 +3984,52 @@ func TestEstimateGasWithMovePrecompile(t *testing.T) { } } +// TestEstimateGasMovePrecompileAway checks that eth_estimateGas honours a +// movePrecompileToAddress override on the *source* address: after a precompile +// is moved away, calling its old address must behave like a call to an empty +// account (intrinsic gas only). Without threading the overridden precompile set +// into the estimator, the old address would still execute the precompile and the +// estimate would be inflated. +func TestEstimateGasMovePrecompileAway(t *testing.T) { + t.Parallel() + var ( + accounts = newAccounts(1) + genesis = &core.Genesis{ + Config: params.MergedTestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + ) + backend := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) { + b.SetPoS() + }) + api := NewBlockChainAPI(backend) + // Move the RIPEMD-160 precompile (0x3) onto the SHA-256 address (0x2), + // then call the now-vacated 0x3 with empty calldata. The call must cost + // the bare intrinsic gas, since 0x3 no longer hosts a precompile. + var ( + ripemdAddr = common.BytesToAddress([]byte{0x3}) + sha256Addr = common.BytesToAddress([]byte{0x2}) + args = TransactionArgs{ + From: &accounts[0].addr, + To: &ripemdAddr, + } + overrides = &override.StateOverride{ + ripemdAddr: override.OverrideAccount{ + MovePrecompileTo: &sha256Addr, + }, + } + ) + gas, err := api.EstimateGas(context.Background(), args, nil, overrides, nil) + if err != nil { + t.Fatalf("EstimateGas failed: %v", err) + } + if uint64(gas) != params.TxGas { + t.Fatalf("mismatched gas: %d, want %d", gas, params.TxGas) + } +} + func TestEIP7910Config(t *testing.T) { var ( newUint64 = func(val uint64) *uint64 { return &val }