From 407839baec7c68863560803d9aef7221fbeae823 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 16 Jul 2026 14:17:18 +0800 Subject: [PATCH] core: remove duplication --- core/eip2780_test.go | 121 +++--------------------------------- core/eip7928_test.go | 144 +++++++++++-------------------------------- core/eip8246_test.go | 66 +++----------------- 3 files changed, 52 insertions(+), 279 deletions(-) diff --git a/core/eip2780_test.go b/core/eip2780_test.go index 503d52e1be..7b421b1fb4 100644 --- a/core/eip2780_test.go +++ b/core/eip2780_test.go @@ -463,56 +463,6 @@ func TestEIP2780RecipientOOG(t *testing.T) { } } -// TestEIP2780AuthOOG pins the atomic authorization charge point: a later -// authorization OOG reverts an earlier successful authorization too. -func TestEIP2780AuthOOG(t *testing.T) { - a0, authority0 := signAuth(t, authKeyA, delegate8037, 0) - key, _ := crypto.GenerateKey() - a1, err := types.SignSetCode(key, types.SetCodeAuthorization{ - ChainID: *uint256.MustFromBig(cfg8037.ChainID), Address: delegate8037, Nonce: 0, - }) - if err != nil { - t.Fatal(err) - } - authority1 := crypto.PubkeyToAddress(key.PublicKey) - to := common.HexToAddress("0xe0a0000000000000000000000000000000000009") - intrinsic := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + 2*params.RegularPerAuthBaseCost - gas := intrinsic + params.AccountWriteAmsterdam + authWorstState - sdb := mkState(senderAlloc(types.GenesisAlloc{to: {Balance: big.NewInt(1)}})) - res, gp, err := applyMsg(t, sdb, setCodeTxGas(0, to, 0, gas, []types.SetCodeAuthorization{a0, a1})) - if err != nil || res.Err != vm.ErrOutOfGas { - t.Fatalf("result=%v err=%v, want included OOG", res, err) - } - for _, authority := range []common.Address{authority0, authority1} { - if len(sdb.GetCode(authority)) != 0 || sdb.GetNonce(authority) != 0 { - t.Fatalf("authority %x persisted after authorization OOG", authority) - } - } - if gp.cumulativeState != 0 || gp.cumulativeRegular != gas { - t.Fatalf("gas = <%d,%d>, want <%d,0>", gp.cumulativeRegular, gp.cumulativeState, gas) - } -} - -// TestEIP2780AuthRevert distinguishes an EVM REVERT from a pre-frame OOG: -// state paid by a successful authorization precedes the frame and persists. -func TestEIP2780AuthRevert(t *testing.T) { - auth, authority := signAuth(t, authKeyA, delegate8037, 0) - reverter := common.HexToAddress("0xbad0000000000000000000000000000000000003") - sdb := mkState(senderAlloc(types.GenesisAlloc{ - reverter: {Code: []byte{0x60, 0x00, 0x60, 0x00, 0xfd}}, - })) - res, gp, err := applyMsg(t, sdb, setCodeTxGas(0, reverter, 0, 1_000_000, []types.SetCodeAuthorization{auth})) - if err != nil || res.Err != vm.ErrExecutionReverted { - t.Fatalf("result=%v err=%v, want execution revert", res, err) - } - if len(sdb.GetCode(authority)) == 0 || sdb.GetNonce(authority) != 1 { - t.Fatalf("authorization not retained after revert: code=%x nonce=%d", sdb.GetCode(authority), sdb.GetNonce(authority)) - } - if gp.cumulativeState != authWorstState { - t.Fatalf("state gas = %d, want %d", gp.cumulativeState, authWorstState) - } -} - // TestEIP2780SelfTransferDelegated verifies that a self-transfer incurs no // recipient touch or value charges, while resolving the sender's own // delegation is still paid for. @@ -648,72 +598,19 @@ func TestEIP2780RecipientRefill(t *testing.T) { } } -// TestEIP2780ValueEffects checks the observable value-transfer behavior along -// with the decomposition's special treatment of self and creation transfers. -func TestEIP2780ValueEffects(t *testing.T) { - recipient := common.HexToAddress("0xbeef000000000000000000000000000000000006") - sdb := mkState(senderAlloc(nil)) - before := sdb.GetBalance(senderAddr).ToBig() - tx := callTx(0, recipient, 7, 300_000, nil) - res, _, err := applyMsg(t, sdb, tx) +// TestEIP2780Coinbase keeps the intrinsic recipient charge separate from the +// runtime warmth of the coinbase account: calling the coinbase directly is +// still charged at the cold rate. The warm rate for a coinbase delegation +// target is covered by TestEIP2780DelegationWarmth. +func TestEIP2780Coinbase(t *testing.T) { + coinbase := common.HexToAddress("0xc01ba5e000000000000000000000000000000001") + res, gp, err := applyMsgCoinbase(t, mkState(senderAlloc(nil)), callTx(0, coinbase, 0, 100_000, nil), coinbase) if err != nil || res.Err != nil { t.Fatalf("result=%v err=%v", res, err) } - if got := sdb.GetBalance(recipient).ToBig(); got.Cmp(big.NewInt(7)) != 0 { - t.Fatalf("recipient balance = %v, want 7", got) + if want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam; gp.cumulativeRegular != want { + t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want) } - if got := sdb.GetBalance(senderAddr).ToBig(); got.Cmp(new(big.Int).Sub(before, big.NewInt(7))) != 0 { - t.Fatalf("sender balance = %v, want value transfer", got) - } - logs := sdb.GetLogs(common.Hash{}, 0, common.Hash{}, 0) - if len(logs) != 1 || logs[0].Topics[0] != params.EthTransferLogEvent { - t.Fatalf("logs = %+v, want one EIP-7708 transfer", logs) - } - - // A self transfer is not a value move and emits no transfer log. - sdb = mkState(senderAlloc(nil)) - before = sdb.GetBalance(senderAddr).ToBig() - tx = callTx(0, senderAddr, 7, 100_000, nil) - res, _, err = applyMsg(t, sdb, tx) - if err != nil || res.Err != nil { - t.Fatalf("self result=%v err=%v", res, err) - } - logs = sdb.GetLogs(common.Hash{}, 0, common.Hash{}, 0) - if sdb.GetBalance(senderAddr).Cmp(uint256.MustFromBig(before)) != 0 || len(logs) != 0 { - t.Fatalf("self transfer changed balance or emitted logs: balance=%v logs=%+v", sdb.GetBalance(senderAddr), logs) - } -} - -// TestEIP2780Coinbase keeps the intrinsic recipient charge separate from the -// runtime warmth of the coinbase account. -func TestEIP2780Coinbase(t *testing.T) { - const ( - base = params.TxBaseCost2780 - cold = params.ColdAccountAccessAmsterdam - warm = params.WarmAccountAccessAmsterdam - ) - coinbase := common.HexToAddress("0xc01ba5e000000000000000000000000000000001") - t.Run("recipient", func(t *testing.T) { - res, gp, err := applyMsgCoinbase(t, mkState(senderAlloc(nil)), callTx(0, coinbase, 0, 100_000, nil), coinbase) - if err != nil || res.Err != nil { - t.Fatalf("result=%v err=%v", res, err) - } - if gp.cumulativeRegular != base+cold { - t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, base+cold) - } - }) - t.Run("target", func(t *testing.T) { - delegated := common.HexToAddress("0xde1e000000000000000000000000000000000007") - res, gp, err := applyMsgCoinbase(t, mkState(senderAlloc(types.GenesisAlloc{ - delegated: {Code: types.AddressToDelegation(coinbase)}, - })), callTx(0, delegated, 0, 100_000, nil), coinbase) - if err != nil || res.Err != nil { - t.Fatalf("result=%v err=%v", res, err) - } - if gp.cumulativeRegular != base+cold+warm { - t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, base+cold+warm) - } - }) } // TestEIP2780DelegationWarmth adds the special targets which are warm before diff --git a/core/eip7928_test.go b/core/eip7928_test.go index 11f0a5e09f..816161a34f 100644 --- a/core/eip7928_test.go +++ b/core/eip7928_test.go @@ -276,24 +276,6 @@ func TestBALEmptyBlockExcludesCoinbase(t *testing.T) { assertAbsent(t, b, coinbase) } -// TestBALCoinbaseTipCapturesBalance: positive priority fee credits coinbase -// and the balance change appears in the BAL. -func TestBALCoinbaseTipCapturesBalance(t *testing.T) { - coinbase := common.Address{0xc0} - to := common.HexToAddress("0xabba") - env := newBALTestEnv(nil) - - b, _ := env.run(t, func(g *BlockGen) { - g.SetCoinbase(coinbase) - g.AddTx(env.tx(0, &to, big.NewInt(0), params.TxGas, 2 /* gwei tip */, nil)) - }) - - cb := assertPresent(t, b, coinbase) - if len(cb.BalanceChanges) == 0 || cb.BalanceChanges[0].PostBalance.Sign() == 0 { - t.Fatalf("coinbase missing positive balance change: %+v", cb.BalanceChanges) - } -} - // TestBALCoinbasePerTxBalance checks that fee-recipient balances are recorded // after each transaction, rather than only once at the end of the block. func TestBALCoinbasePerTxBalance(t *testing.T) { @@ -718,28 +700,6 @@ func TestBALDelegationTargetOOG(t *testing.T) { // ============================== Storage inclusion ============================== -// TestBALStorageWriteRecorded: SSTORE places the slot in storage_changes and -// keeps it out of storage_reads. -func TestBALStorageWriteRecorded(t *testing.T) { - contract := common.HexToAddress("0xc1") - slot := common.BigToHash(big.NewInt(0x01)) - // PUSH1 0x42 PUSH1 0x01 SSTORE STOP - code := []byte{0x60, 0x42, 0x60, 0x01, 0x55, 0x00} - env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}}) - - b, _ := env.run(t, func(g *BlockGen) { - g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil)) - }) - - aa := assertPresent(t, b, contract) - if !hasStorageWrite(b, contract, slot) { - t.Fatalf("expected slot 0x01 in storage_changes\n%s", b.PrettyPrint()) - } - if hasSlotIn(aa.StorageReads, slot) { - t.Fatalf("slot 0x01 must NOT appear in storage_reads") - } -} - // TestBALStorageSloadOnly: SLOAD without a write puts the slot in storage_reads. func TestBALStorageSloadOnly(t *testing.T) { contract := common.HexToAddress("0xc1") @@ -2128,84 +2088,50 @@ func TestBALDelegatedCallStorage(t *testing.T) { assertEmpty(t, assertPresent(t, b, implementation)) } -// TestBALDelegatedDelegateCallStorage checks the other storage context: -// DELEGATECALL to a delegated authority still executes the resolved code -// against the original caller's storage. -func TestBALDelegatedDelegateCallStorage(t *testing.T) { +// TestBALDelegatedCallFamilyStorage checks the storage context of the CALL +// family against a delegated authority: the resolved implementation supplies +// code only, while the storage read belongs to the executing context — the +// authority for CALL/STATICCALL, the caller for DELEGATECALL/CALLCODE. +func TestBALDelegatedCallFamilyStorage(t *testing.T) { caller := common.HexToAddress("0xca11") authority := common.HexToAddress("0xa7702") implementation := common.HexToAddress("0x1a11") slot := common.BigToHash(big.NewInt(0x07)) implCode := []byte{0x60, 0x07, 0x54, 0x50, 0x00} // SLOAD(7), POP, STOP - env := newBALTestEnv(types.GenesisAlloc{ - caller: {Code: makeStubCaller(0xf4 /* DELEGATECALL */, authority), Balance: common.Big0}, - authority: {Code: types.AddressToDelegation(implementation), Balance: common.Big0}, - implementation: {Code: implCode, Balance: common.Big0}, - }) - - b, _ := env.run(t, func(g *BlockGen) { - g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil)) - }) - - callerAccess := assertPresent(t, b, caller) - if !hasSlotIn(callerAccess.StorageReads, slot) { - t.Fatalf("delegated DELEGATECALL read must belong to caller\n%s", b.PrettyPrint()) + cases := []struct { + name string + op byte + readOwner common.Address + }{ + {"call", 0xf1, authority}, + {"callcode", 0xf2, caller}, + {"delegatecall", 0xf4, caller}, + {"staticcall", 0xfa, authority}, } - assertEmpty(t, assertPresent(t, b, authority)) - assertEmpty(t, assertPresent(t, b, implementation)) -} + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + env := newBALTestEnv(types.GenesisAlloc{ + caller: {Code: makeStubCaller(tc.op, authority), Balance: common.Big0}, + authority: {Code: types.AddressToDelegation(implementation), Balance: common.Big0}, + implementation: {Code: implCode, Balance: common.Big0}, + }) -// TestBALDelegatedStaticCallStorage covers STATICCALL's context. It resolves -// the authority's implementation code, but SLOAD remains attached to the static -// call target's storage. -func TestBALDelegatedStaticCallStorage(t *testing.T) { - caller := common.HexToAddress("0xca11") - authority := common.HexToAddress("0xa7702") - implementation := common.HexToAddress("0x1a11") - slot := common.BigToHash(big.NewInt(0x07)) - implCode := []byte{0x60, 0x07, 0x54, 0x50, 0x00} // SLOAD(7), POP, STOP - env := newBALTestEnv(types.GenesisAlloc{ - caller: {Code: makeStubCaller(0xfa /* STATICCALL */, authority), Balance: common.Big0}, - authority: {Code: types.AddressToDelegation(implementation), Balance: common.Big0}, - implementation: {Code: implCode, Balance: common.Big0}, - }) + b, _ := env.run(t, func(g *BlockGen) { + g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil)) + }) - b, _ := env.run(t, func(g *BlockGen) { - g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil)) - }) - - authorityAccess := assertPresent(t, b, authority) - if !hasSlotIn(authorityAccess.StorageReads, slot) { - t.Fatalf("delegated STATICCALL read must belong to authority\n%s", b.PrettyPrint()) + owner := assertPresent(t, b, tc.readOwner) + if !hasSlotIn(owner.StorageReads, slot) { + t.Fatalf("%s read must belong to %x\n%s", tc.name, tc.readOwner, b.PrettyPrint()) + } + for _, other := range []common.Address{caller, authority} { + if other != tc.readOwner { + assertEmpty(t, assertPresent(t, b, other)) + } + } + assertEmpty(t, assertPresent(t, b, implementation)) + }) } - assertEmpty(t, assertPresent(t, b, implementation)) -} - -// TestBALDelegatedCallCodeStorage covers CALLCODE's storage context. As with -// DELEGATECALL, the implementation address supplies code but is not the owner -// of storage reads. -func TestBALDelegatedCallCodeStorage(t *testing.T) { - caller := common.HexToAddress("0xca11") - authority := common.HexToAddress("0xa7702") - implementation := common.HexToAddress("0x1a11") - slot := common.BigToHash(big.NewInt(0x07)) - implCode := []byte{0x60, 0x07, 0x54, 0x50, 0x00} // SLOAD(7), POP, STOP - env := newBALTestEnv(types.GenesisAlloc{ - caller: {Code: makeStubCaller(0xf2 /* CALLCODE */, authority), Balance: common.Big0}, - authority: {Code: types.AddressToDelegation(implementation), Balance: common.Big0}, - implementation: {Code: implCode, Balance: common.Big0}, - }) - - b, _ := env.run(t, func(g *BlockGen) { - g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil)) - }) - - callerAccess := assertPresent(t, b, caller) - if !hasSlotIn(callerAccess.StorageReads, slot) { - t.Fatalf("delegated CALLCODE read must belong to caller\n%s", b.PrettyPrint()) - } - assertEmpty(t, assertPresent(t, b, authority)) - assertEmpty(t, assertPresent(t, b, implementation)) } // TestBALDelegationOneHop verifies that resolving A -> B does not diff --git a/core/eip8246_test.go b/core/eip8246_test.go index bc2918467b..b37e96d923 100644 --- a/core/eip8246_test.go +++ b/core/eip8246_test.go @@ -31,7 +31,8 @@ import ( // TestEIP8246SelfdestructNoBurn verifies that, once EIP-8246 is active // (Amsterdam), a contract that is created and self-destructs to itself within // the same transaction keeps its balance instead of burning it: the account -// survives as a balance-only account (no code, zero nonce, balance preserved). +// survives as a balance-only account (no code, zero nonce, balance preserved) +// whose storage is cleared at transaction finalization. // // https://eips.ethereum.org/EIPS/eip-8246 func TestEIP8246SelfdestructNoBurn(t *testing.T) { @@ -42,9 +43,11 @@ func TestEIP8246SelfdestructNoBurn(t *testing.T) { signer = types.LatestSigner(&config) engine = beacon.New(ethash.NewFaker()) value = big.NewInt(1_000_000) - // Init code: ADDRESS (0x30) ; SELFDESTRUCT (0xff). The created contract - // self-destructs to itself during its own creation transaction. - initcode = common.FromHex("30ff") + slot = common.BigToHash(big.NewInt(0x05)) + // Init code: SSTORE(5, 0x2a); ADDRESS (0x30); SELFDESTRUCT (0xff). The + // created contract stores a value and self-destructs to itself during + // its own creation transaction. + initcode = []byte{0x60, 0x2a, 0x60, 0x05, 0x55, 0x30, 0xff} ) // TODO: drop this hacky Amsterdam config initialization once the final // Amsterdam config is available (mirrors TestEthTransferLogs). @@ -99,61 +102,8 @@ func TestEIP8246SelfdestructNoBurn(t *testing.T) { if got := state.GetCodeSize(created); got != 0 { t.Errorf("created account code size = %d, want 0 (code must be cleared)", got) } -} - -// TestEIP8246SelfdestructClearsStorage verifies the other half of the -// balance-only-account rule: a same-transaction selfdestruct preserves balance -// but clears storage during transaction finalization. -func TestEIP8246SelfdestructClearsStorage(t *testing.T) { - var ( - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - sender = crypto.PubkeyToAddress(key.PublicKey) - config = *params.MergedTestChainConfig - signer = types.LatestSigner(&config) - engine = beacon.New(ethash.NewFaker()) - value = big.NewInt(1_000_000) - slot = common.BigToHash(big.NewInt(0x05)) - stored = common.BigToHash(big.NewInt(0x2a)) - created = crypto.CreateAddress(sender, 0) - ) - config.AmsterdamTime = new(uint64) - gspec := &Genesis{ - Config: &config, - Alloc: types.GenesisAlloc{sender: {Balance: newGwei(1_000_000_000)}}, - } - // SSTORE(5, 0x2a); ADDRESS; SELFDESTRUCT. - initcode := []byte{0x60, 0x2a, 0x60, 0x05, 0x55, 0x30, 0xff} - db, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(_ int, b *BlockGen) { - b.AddTx(types.MustSignNewTx(key, signer, &types.DynamicFeeTx{ - ChainID: gspec.Config.ChainID, - Nonce: 0, - Gas: 1_000_000, - GasFeeCap: newGwei(5), - GasTipCap: newGwei(5), - Value: value, - Data: initcode, - })) - }) - chain, err := NewBlockChain(db, gspec, engine, nil) - if err != nil { - t.Fatalf("failed to create chain: %v", err) - } - defer chain.Stop() - state, err := chain.StateAt(blocks[0].Header()) - if err != nil { - t.Fatalf("failed to obtain block state: %v", err) - } - if got := state.GetBalance(created).ToBig(); got.Cmp(value) != 0 { - t.Errorf("created balance = %v, want %v", got, value) - } - if got := state.GetNonce(created); got != 0 { - t.Errorf("created nonce = %d, want 0", got) - } - if got := state.GetCodeSize(created); got != 0 { - t.Errorf("created code size = %d, want 0", got) - } if got := state.GetState(created, slot); got != (common.Hash{}) { - t.Errorf("created storage slot %x = %x, want 0 (stored %x before selfdestruct)", slot, got, stored) + t.Errorf("created storage slot %x = %x, want 0 (storage must be cleared)", slot, got) } }