core: implement EIP-7928 spec change (#35260)

https://github.com/ethereum/EIPs/pull/11838/changes
This commit is contained in:
rjl493456442 2026-07-01 16:48:13 +08:00 committed by GitHub
parent 00a773dad7
commit ea242145cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 0 deletions

View file

@ -518,6 +518,73 @@ func TestBALStaticCallTargetIncluded(t *testing.T) {
assertEmpty(t, assertPresent(t, b, target))
}
// makeValueCaller emits a single value-transferring CALL-family op (CALL 0xf1
// or CALLCODE 0xf2) against `target` with value=1, then STOPs. Used together
// with a zero-balance caller to make the value transfer fail CanTransfer.
func makeValueCaller(op byte, target common.Address) []byte {
code := []byte{
0x60, 0x00, // retSize
0x60, 0x00, // retOff
0x60, 0x00, // argsSize
0x60, 0x00, // argsOff
0x60, 0x01, // value = 1
0x73, // PUSH20 target
}
code = append(code, target.Bytes()...)
return append(code, 0x5a, op, 0x50, 0x00) // GAS, op, POP, STOP
}
// TestBALCallToDelegatedTargetBalanceFail asserts the EIP-7928 rule revised in
// ethereum/EIPs#11838: when a CALL targets an EIP-7702 delegated account and the
// delegated address passes its access_cost gas check, the delegated
// (implementation) address MUST appear in the BAL even when the call then fails
// its sender-balance check, because the delegation is resolved before that
// check. CALL routes through the EIP-8037 gas path.
func TestBALCallToDelegatedTargetBalanceFail(t *testing.T) {
delegated := common.HexToAddress("0xde1e9a7ed") // EOA carrying a 7702 designator
impl := common.HexToAddress("0x111111") // delegation target (implementation)
caller := common.HexToAddress("0xca11") // zero-balance contract issuing the CALL
env := newBALTestEnv(types.GenesisAlloc{
caller: {Code: makeValueCaller(0xf1 /* CALL */, delegated), Balance: common.Big0},
delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
})
b, _ := env.run(t, func(g *BlockGen) {
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
})
assertPresent(t, b, caller)
assertPresent(t, b, delegated)
// The call failed its sender-balance check, so the implementation never
// executed: it is recorded with an empty change set, but it MUST be present.
assertEmpty(t, assertPresent(t, b, impl))
}
// TestBALCallCodeToDelegatedTargetBalanceFail is the CALLCODE analogue of
// TestBALCallToDelegatedTargetBalanceFail, exercising the EIP-7702 gas path
// (CALLCODE/STATICCALL/DELEGATECALL) rather than the EIP-8037 one.
func TestBALCallCodeToDelegatedTargetBalanceFail(t *testing.T) {
delegated := common.HexToAddress("0xde1e9a7ed")
impl := common.HexToAddress("0x111111")
caller := common.HexToAddress("0xca11")
env := newBALTestEnv(types.GenesisAlloc{
caller: {Code: makeValueCaller(0xf2 /* CALLCODE */, delegated), Balance: common.Big0},
delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
})
b, _ := env.run(t, func(g *BlockGen) {
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
})
assertPresent(t, b, caller)
assertPresent(t, b, delegated)
assertEmpty(t, assertPresent(t, b, impl))
}
// ============================== Revert behaviour ==============================
// TestBALRevertedTxStillIncluded: a tx whose top-level call REVERTs still

View file

@ -257,6 +257,14 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
return gasFunc
}
// recordDelegationAccess records the EIP-7702 delegated target in the block
// access list (EIP-7928).
func recordDelegationAccess(evm *EVM, target common.Address) {
if evm.chainRules.IsAmsterdam {
evm.StateDB.GetCode(target)
}
}
var (
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
@ -336,6 +344,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
// The delegated address has passed its gas check; record it in the
// block access list now, before the call's sender-balance and
// call-stack-depth checks.
recordDelegationAccess(evm, target)
}
// Calculate the gas budget for the nested call. The costs defined by
// EIP-2929 and EIP-7702 have already been applied.
@ -416,6 +428,10 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
// The delegated address has passed its gas check; record it in the
// block access list now, before the call's sender-balance and
// call-stack-depth checks.
recordDelegationAccess(evm, target)
}
// Compute and charge state gas (new account creation) AFTER regular gas.