core: remove duplication

This commit is contained in:
Gary Rong 2026-07-16 14:17:18 +08:00
parent 478fe9e825
commit 407839baec
3 changed files with 52 additions and 279 deletions

View file

@ -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

View file

@ -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

View file

@ -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)
}
}