internal/ethapi: apply block overrides to header in eth_call

When BlockOverrides specifies BaseFeePerGas, doCall applies the
override to the derived EVM block context, but the original header is
still passed down to applyMessage. applyMessage then computes the
message's gasPrice via args.ToMessage(header.BaseFee, ...) which
ignores the override, so subsequent GASPRICE queries and
effectiveTip calculations use the pre-override basefee.

As a result, eth_call with a 1559 transaction (MaxFeePerGas +
MaxPriorityFeePerGas) and a BaseFeePerGas block override returns a
GASPRICE derived from the real block's basefee instead of the
override. tracers/api.go and simulate.go already read BaseFee from
the overridden block context, so only eth_call was affected.

Mirror the fix applied to DoEstimateGas in #34081: after applying the
overrides to blockCtx, rebuild the header via blockOverrides.MakeHeader
so downstream code sees the overridden basefee.

Add a TestCall case that asserts GASPRICE returns tip + overridden
basefee. It fails on master and passes with this change.
This commit is contained in:
rayoo 2026-04-28 10:43:45 +08:00
parent 822e7c6486
commit 1481f98554
2 changed files with 25 additions and 0 deletions

View file

@ -734,6 +734,10 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
if err := blockOverrides.Apply(&blockCtx); err != nil {
return nil, err
}
// Override the header so callers that compute gas price from 1559 fee
// fields see the overridden basefee. Otherwise GASPRICE/effectiveTip
// would be derived from the pre-override basefee.
header = blockOverrides.MakeHeader(header)
}
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
precompiles := vm.ActivePrecompiledContracts(rules)

View file

@ -1315,6 +1315,27 @@ func TestCall(t *testing.T) {
},
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`),
},
// Verify that an overridden basefee is honored when computing gasPrice
// from the 1559 fee fields. Returning GASPRICE opcode; expected value
// is min(MaxFeePerGas, MaxPriorityFeePerGas + overridden BaseFee).
//
// BaseFee override = 0xa (10); MaxFeePerGas = 0x64 (100);
// MaxPriorityFeePerGas = 0x2 (2); expected GASPRICE = 12.
{
name: "basefee-override-used-in-gasprice",
blockNumber: rpc.LatestBlockNumber,
call: TransactionArgs{
From: &accounts[0].addr,
// Contract: GASPRICE; PUSH1 0; MSTORE; PUSH1 32; PUSH1 0; RETURN
Input: hex2Bytes("3a60005260206000f3"),
MaxFeePerGas: (*hexutil.Big)(big.NewInt(100)),
MaxPriorityFeePerGas: (*hexutil.Big)(big.NewInt(2)),
},
blockOverrides: override.BlockOverrides{
BaseFeePerGas: (*hexutil.Big)(big.NewInt(10)),
},
want: "0x000000000000000000000000000000000000000000000000000000000000000c",
},
}
for _, tc := range testSuite {
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)