Skip to content

Commit ef85a33

Browse files
authored
fix: enforce EIP-6780 selfdestruct for prefunded addresses (#2580)
- implement CreateContract() to mark account as created for EIP-6780 - previously, prefunded addresses bypassed EIP-6780 because CreateAccount() was not called when Exist() returned true - createContract() is called unconditionally during contract creation, ensuring proper AccountCreated flag is set" ## Describe your changes and provide context ## Testing performed to validate your change
1 parent b01060b commit ef85a33

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

x/evm/state/state_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,58 @@ func TestSelfDestructAssociated(t *testing.T) {
137137
require.Equal(t, uint256.NewInt(1), statedb.GetBalance(fc))
138138
}
139139

140+
// TestEIP6780WithPrefundedAddress verifies that EIP-6780 selfdestruct works correctly
141+
// when a contract is deployed to a prefunded address. This tests the fix for a bug where
142+
// contracts deployed to addresses with existing balance would not be destroyed by
143+
// SelfDestruct6780 because CreateAccount() was not called (since the account already existed).
144+
// The fix ensures CreateContract() marks the account as created for EIP-6780 purposes.
145+
func TestEIP6780WithPrefundedAddress(t *testing.T) {
146+
k := &testkeeper.EVMTestApp.EvmKeeper
147+
ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now())
148+
seiAddr, evmAddr := testkeeper.MockAddressPair()
149+
k.SetAddressMapping(ctx, seiAddr, evmAddr)
150+
151+
statedb := state.NewDBImpl(ctx, k, false)
152+
153+
// Prefund the address with balance using statedb context
154+
amt := sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(1000000)))
155+
k.BankKeeper().MintCoins(statedb.Ctx(), types.ModuleName, amt)
156+
k.BankKeeper().SendCoinsFromModuleToAccount(statedb.Ctx(), types.ModuleName, seiAddr, amt)
157+
158+
// Verify the account has balance but is not marked as "created" yet
159+
require.True(t, statedb.GetBalance(evmAddr).CmpBig(big.NewInt(0)) > 0, "address should have balance")
160+
require.False(t, statedb.Created(evmAddr), "account should not be marked as created before CreateContract")
161+
162+
// Simulate the EVM's contract creation flow for a prefunded address:
163+
// In go-ethereum's create(), if Exist() returns true (which it does for prefunded addresses),
164+
// CreateAccount() is NOT called. Instead, only CreateContract() is called.
165+
// This is the exact scenario that was broken before the fix.
166+
require.True(t, statedb.Exist(evmAddr), "prefunded address should exist")
167+
168+
// Only call CreateContract (not CreateAccount) - this simulates the real EVM behavior
169+
statedb.CreateContract(evmAddr)
170+
171+
// After CreateContract, the account should be marked as created for EIP-6780
172+
require.True(t, statedb.Created(evmAddr), "account should be marked as created after CreateContract")
173+
174+
// Set some contract state
175+
statedb.SetCode(evmAddr, []byte("contract code"))
176+
key := common.BytesToHash([]byte("storage_key"))
177+
val := common.BytesToHash([]byte("storage_value"))
178+
statedb.SetState(evmAddr, key, val)
179+
180+
// Now SelfDestruct6780 should work correctly - the key test is that destructed == true
181+
_, destructed := statedb.SelfDestruct6780(evmAddr)
182+
require.True(t, destructed, "SelfDestruct6780 should destruct the contract created in same tx")
183+
require.True(t, statedb.HasSelfDestructed(evmAddr), "account should be marked as self-destructed")
184+
185+
// Finalize to clear the state
186+
statedb.Finalize()
187+
188+
// After finalize, the contract's state should be cleared
189+
require.Equal(t, common.Hash{}, statedb.GetState(evmAddr, key), "storage should be cleared after finalize")
190+
}
191+
140192
func TestSnapshot(t *testing.T) {
141193
k := &testkeeper.EVMTestApp.EvmKeeper
142194
ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now())

x/evm/state/statedb.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,13 @@ func (s *DBImpl) SetTxContext(common.Hash, int) {
185185

186186
func (s *DBImpl) AccessEvents() *vm.AccessEvents { return nil }
187187

188-
func (s *DBImpl) CreateContract(common.Address) {}
188+
// CreateContract marks the account as created for EIP-6780 purposes.
189+
// This is called regardless of whether the account previously existed
190+
// (e.g., prefunded addresses), ensuring that contracts created and
191+
// self-destructed in the same transaction are properly destroyed.
192+
func (s *DBImpl) CreateContract(acc common.Address) {
193+
s.MarkAccount(acc, AccountCreated)
194+
}
189195

190196
func (s *DBImpl) PointCache() *ethutils.PointCache {
191197
return nil

0 commit comments

Comments
 (0)