Skip to content

Commit 29536a5

Browse files
committed
core: implement EIP-7928 spec change
1 parent 04bf045 commit 29536a5

2 files changed

Lines changed: 83 additions & 0 deletions

File tree

core/bal_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,73 @@ func TestBALStaticCallTargetIncluded(t *testing.T) {
518518
assertEmpty(t, assertPresent(t, b, target))
519519
}
520520

521+
// makeValueCaller emits a single value-transferring CALL-family op (CALL 0xf1
522+
// or CALLCODE 0xf2) against `target` with value=1, then STOPs. Used together
523+
// with a zero-balance caller to make the value transfer fail CanTransfer.
524+
func makeValueCaller(op byte, target common.Address) []byte {
525+
code := []byte{
526+
0x60, 0x00, // retSize
527+
0x60, 0x00, // retOff
528+
0x60, 0x00, // argsSize
529+
0x60, 0x00, // argsOff
530+
0x60, 0x01, // value = 1
531+
0x73, // PUSH20 target
532+
}
533+
code = append(code, target.Bytes()...)
534+
return append(code, 0x5a, op, 0x50, 0x00) // GAS, op, POP, STOP
535+
}
536+
537+
// TestBALCallToDelegatedTargetBalanceFail asserts the EIP-7928 rule revised in
538+
// ethereum/EIPs#11838: when a CALL targets an EIP-7702 delegated account and the
539+
// delegated address passes its access_cost gas check, the delegated
540+
// (implementation) address MUST appear in the BAL even when the call then fails
541+
// its sender-balance check, because the delegation is resolved before that
542+
// check. CALL routes through the EIP-8037 gas path.
543+
func TestBALCallToDelegatedTargetBalanceFail(t *testing.T) {
544+
delegated := common.HexToAddress("0xde1e9a7ed") // EOA carrying a 7702 designator
545+
impl := common.HexToAddress("0x111111") // delegation target (implementation)
546+
caller := common.HexToAddress("0xca11") // zero-balance contract issuing the CALL
547+
548+
env := newBALTestEnv(types.GenesisAlloc{
549+
caller: {Code: makeValueCaller(0xf1 /* CALL */, delegated), Balance: common.Big0},
550+
delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
551+
impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
552+
})
553+
554+
b, _ := env.run(t, func(g *BlockGen) {
555+
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
556+
})
557+
558+
assertPresent(t, b, caller)
559+
assertPresent(t, b, delegated)
560+
// The call failed its sender-balance check, so the implementation never
561+
// executed: it is recorded with an empty change set, but it MUST be present.
562+
assertEmpty(t, assertPresent(t, b, impl))
563+
}
564+
565+
// TestBALCallCodeToDelegatedTargetBalanceFail is the CALLCODE analogue of
566+
// TestBALCallToDelegatedTargetBalanceFail, exercising the EIP-7702 gas path
567+
// (CALLCODE/STATICCALL/DELEGATECALL) rather than the EIP-8037 one.
568+
func TestBALCallCodeToDelegatedTargetBalanceFail(t *testing.T) {
569+
delegated := common.HexToAddress("0xde1e9a7ed")
570+
impl := common.HexToAddress("0x111111")
571+
caller := common.HexToAddress("0xca11")
572+
573+
env := newBALTestEnv(types.GenesisAlloc{
574+
caller: {Code: makeValueCaller(0xf2 /* CALLCODE */, delegated), Balance: common.Big0},
575+
delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
576+
impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
577+
})
578+
579+
b, _ := env.run(t, func(g *BlockGen) {
580+
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
581+
})
582+
583+
assertPresent(t, b, caller)
584+
assertPresent(t, b, delegated)
585+
assertEmpty(t, assertPresent(t, b, impl))
586+
}
587+
521588
// ============================== Revert behaviour ==============================
522589

523590
// TestBALRevertedTxStillIncluded: a tx whose top-level call REVERTs still

core/vm/operations_acl.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,14 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
257257
return gasFunc
258258
}
259259

260+
// recordDelegationAccess records the EIP-7702 delegated target in the block
261+
// access list (EIP-7928).
262+
func recordDelegationAccess(evm *EVM, target common.Address) {
263+
if evm.chainRules.IsAmsterdam {
264+
evm.StateDB.GetCode(target)
265+
}
266+
}
267+
260268
var (
261269
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
262270
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
@@ -336,6 +344,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
336344
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
337345
return GasCosts{}, ErrOutOfGas
338346
}
347+
// The delegated address has passed its gas check; record it in the
348+
// block access list now, before the call's sender-balance and
349+
// call-stack-depth checks.
350+
recordDelegationAccess(evm, target)
339351
}
340352
// Calculate the gas budget for the nested call. The costs defined by
341353
// EIP-2929 and EIP-7702 have already been applied.
@@ -416,6 +428,10 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
416428
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
417429
return GasCosts{}, ErrOutOfGas
418430
}
431+
// The delegated address has passed its gas check; record it in the
432+
// block access list now, before the call's sender-balance and
433+
// call-stack-depth checks.
434+
recordDelegationAccess(evm, target)
419435
}
420436

421437
// Compute and charge state gas (new account creation) AFTER regular gas.

0 commit comments

Comments
 (0)