Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

### API-BREAKING

- [\#1270](https://github.com/cosmos/evm/pull/1270) Add `DeleteNewContractAccount` to `statedb.Keeper` and the expected EVM keeper interfaces; downstream implementations must provide the new method.
- [\#1146](https://github.com/cosmos/evm/pull/1146) Remove `EndBlocker` based mempool updates, use `PrepareCheckStater` instead.

### IMPROVEMENTS
Expand Down Expand Up @@ -42,6 +43,7 @@

### BUG FIXES

- [\#1270](https://github.com/cosmos/evm/pull/1270) Fix EIP-6780 `SELFDESTRUCT` for contract creation at pre-funded addresses when init code produces no runtime code.
- [\#1265](https://github.com/cosmos/evm/pull/1265) Apply `json-rpc.evm-timeout` to `eth_estimateGas`, matching `eth_call`.
- [\#1223](https://github.com/cosmos/evm/pull/1223) Reject EVM txs below the base fee at mempool insert instead of silently queuing them.
- [\#1214](https://github.com/cosmos/evm/pull/1214) Emit the canonical CometBFT block hash in the `newHeads` subscription so it matches `eth_getBlockByNumber` (completes [\#725](https://github.com/cosmos/evm/pull/725)).
Expand Down
1 change: 1 addition & 0 deletions mempool/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type VMKeeperI interface {
DeleteCode(ctx sdk.Context, codeHash []byte)
SetCode(ctx sdk.Context, codeHash []byte, code []byte)
DeleteAccount(ctx sdk.Context, addr common.Address) error
DeleteNewContractAccount(ctx sdk.Context, addr common.Address) error
KVStoreKeys() map[string]storetypes.StoreKey
}

Expand Down
18 changes: 18 additions & 0 deletions mempool/mocks/VMKeeperI.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion tests/integration/x/vm/test_commit_idempotency.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,12 @@ func (s *KeeperTestSuite) TestCommitIdempotencyWithSelfDestruct() {

addr := common.BytesToAddress([]byte("testaddr"))

// Setup: Create account and self-destruct
// Setup: Create a contract and self-destruct it
db := s.StateDB()
cacheCtx, err := db.GetCacheContext()
s.Require().NoError(err)
db.CreateAccount(addr)
db.CreateContract(addr)
db.SelfDestruct(addr)
err = db.FlushToCacheCtx()
s.Require().NoError(err)
Expand Down
46 changes: 46 additions & 0 deletions tests/integration/x/vm/test_state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
gethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"

Expand Down Expand Up @@ -668,6 +669,51 @@ func (s *KeeperTestSuite) TestApplyTransaction() {
}
}

func (s *KeeperTestSuite) TestApplyTransactionWithPreFundedEmptyRuntimeSelfDestruct() {
s.SetupTest()

ctx := s.Network.GetContext()
evmKeeper := s.Network.App.GetEVMKeeper()
sender := s.Keyring.GetKey(0)
beneficiary := s.Keyring.GetAddr(1)
prefund := big.NewInt(7)

initialNonce := evmKeeper.GetNonce(ctx, sender.Addr)
contractAddr := crypto.CreateAddress(sender.Addr, initialNonce+1)
beneficiaryBalance := evmKeeper.GetBalance(ctx, beneficiary).ToBig()

fundingRes, err := s.Factory.ExecuteEthTx(sender.Priv, types.EvmTxArgs{
To: &contractAddr,
Amount: prefund,
GasLimit: params.TxGas,
})
s.Require().NoError(err)
s.Require().True(fundingRes.IsOK())
s.Require().NoError(s.Network.NextBlock())

ctx = s.Network.GetContext()
s.Require().NotNil(evmKeeper.GetAccount(ctx, contractAddr))
s.Require().Equal(0, evmKeeper.GetBalance(ctx, contractAddr).ToBig().Cmp(prefund))
s.Require().Equal(initialNonce+1, evmKeeper.GetNonce(ctx, sender.Addr))

// PUSH20 beneficiary; SELFDESTRUCT. The constructor returns no runtime code.
initCode := append([]byte{0x73}, beneficiary.Bytes()...)
initCode = append(initCode, 0xff)
creationRes, err := s.Factory.ExecuteEthTx(sender.Priv, types.EvmTxArgs{
Input: initCode,
GasLimit: 200_000,
})
s.Require().NoError(err)
s.Require().True(creationRes.IsOK())
s.Require().NoError(s.Network.NextBlock())

ctx = s.Network.GetContext()
s.Require().Nil(evmKeeper.GetAccount(ctx, contractAddr))
s.Require().True(evmKeeper.GetBalance(ctx, contractAddr).IsZero())
expectedBeneficiaryBalance := new(big.Int).Add(beneficiaryBalance, prefund)
s.Require().Equal(0, evmKeeper.GetBalance(ctx, beneficiary).ToBig().Cmp(expectedBeneficiaryBalance))
}

type testHooks struct {
postProcessing func(ctx sdk.Context, sender common.Address, msg core.Message, receipt *gethtypes.Receipt) error
}
Expand Down
1 change: 1 addition & 0 deletions x/erc20/types/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type EVMKeeper interface {
EstimateGasInternal(c context.Context, req *evmtypes.EthCallRequest, fromType evmtypes.CallType) (*evmtypes.EstimateGasResponse, error)
ApplyMessage(ctx sdk.Context, stateDB *statedb.StateDB, msg core.Message, tracer *tracing.Hooks, commit, callFromPrecompile, internal bool) (*evmtypes.MsgEthereumTxResponse, error)
DeleteAccount(ctx sdk.Context, addr common.Address) error
DeleteNewContractAccount(ctx sdk.Context, addr common.Address) error
IsAvailableStaticPrecompile(params *evmtypes.Params, address common.Address) bool
CallEVM(ctx sdk.Context, stateDB *statedb.StateDB, abi abi.ABI, from, contract common.Address, commit, callFromPrecompile bool, gasCap *big.Int, method string, args ...interface{}) (*evmtypes.MsgEthereumTxResponse, error)
CallEVMWithData(ctx sdk.Context, stateDB *statedb.StateDB, from common.Address, contract *common.Address, data []byte, commit bool, callFromPrecompile bool, gasCap *big.Int) (*evmtypes.MsgEthereumTxResponse, error)
Expand Down
18 changes: 18 additions & 0 deletions x/erc20/types/mocks/EVMKeeper.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions x/ibc/callbacks/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type EVMKeeper interface {
DeleteCode(ctx sdk.Context, codeHash []byte)
SetCode(ctx sdk.Context, codeHash []byte, code []byte)
DeleteAccount(ctx sdk.Context, addr common.Address) error
DeleteNewContractAccount(ctx sdk.Context, addr common.Address) error
KVStoreKeys() map[string]storetypes.StoreKey
}

Expand Down
31 changes: 23 additions & 8 deletions x/vm/keeper/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,29 @@ func (k *Keeper) DeleteCode(ctx sdk.Context, codeHash []byte) {
)
}

// DeleteAccount handles contract's suicide call:
// - clear balance
// - remove code
// - remove states
// - remove the code hash
// - remove auth account
// DeleteAccount removes a persisted contract after SELFDESTRUCT:
// - clear its balance
// - remove its storage
// - remove its address-to-code-hash mapping
// - remove its auth account
//
// Contract bytecode is content-addressed and may be shared, so it is not
// removed from the code store here.
func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error {
ctx, span := ctx.StartSpan(tracer, "DeleteAccount", trace.WithAttributes(attribute.String("address", addr.Hex())))
return k.deleteAccount(ctx, addr, false)
}

// DeleteNewContractAccount deletes a contract that the caller verified was
// created in the current EVM transaction.
func (k *Keeper) DeleteNewContractAccount(ctx sdk.Context, addr common.Address) error {
return k.deleteAccount(ctx, addr, true)
}

func (k *Keeper) deleteAccount(ctx sdk.Context, addr common.Address, isNewContract bool) error {
ctx, span := ctx.StartSpan(tracer, "DeleteAccount", trace.WithAttributes(
attribute.String("address", addr.Hex()),
attribute.Bool("new_contract", isNewContract),
))
defer span.End()
cosmosAddr := sdk.AccAddress(addr.Bytes())
acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
Expand All @@ -343,7 +358,7 @@ func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error {
}

// NOTE: only Ethereum contracts can be self-destructed
if !k.IsContract(ctx, addr) {
if !isNewContract && !k.IsContract(ctx, addr) {
return errors.New("only smart contracts can be self-destructed")
}

Expand Down
3 changes: 3 additions & 0 deletions x/vm/statedb/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type Keeper interface {
DeleteCode(ctx sdk.Context, codeHash []byte)
SetCode(ctx sdk.Context, codeHash []byte, code []byte)
DeleteAccount(ctx sdk.Context, addr common.Address) error
// DeleteNewContractAccount is called only after StateDB verifies that the
// account became a contract in the current transaction.
DeleteNewContractAccount(ctx sdk.Context, addr common.Address) error

// Getter for injected Store keys
// It is used for StateDB.snapshotter creation
Expand Down
4 changes: 4 additions & 0 deletions x/vm/statedb/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ func (k MockKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error {
return nil
}

func (k MockKeeper) DeleteNewContractAccount(ctx sdk.Context, addr common.Address) error {
return k.DeleteAccount(ctx, addr)
}

func (k MockKeeper) Clone() *MockKeeper {
accounts := maps.Clone(k.accounts)
codes := maps.Clone(k.codes)
Expand Down
12 changes: 4 additions & 8 deletions x/vm/statedb/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -744,15 +744,11 @@ func (s *StateDB) commitWithCtx(ctx sdk.Context) error {
for _, addr := range s.journal.sortedDirties() {
obj := s.stateObjects[addr]
if obj.selfDestructed {
// For EIP-6780 same-tx self-destruct: persist code+account first so DeleteAccount's
// IsContract check can verify it, then immediately delete everything
if obj.code != nil && obj.dirtyCode && len(obj.code) > 0 {
s.keeper.SetCode(ctx, obj.CodeHash(), obj.code)
if err := s.keeper.SetAccount(ctx, obj.Address(), obj.account); err != nil {
return errorsmod.Wrap(err, "failed to set account before delete")
}
deleteAccount := s.keeper.DeleteAccount
if obj.newContract {
deleteAccount = s.keeper.DeleteNewContractAccount
}
if err := s.keeper.DeleteAccount(ctx, obj.Address()); err != nil {
if err := deleteAccount(ctx, obj.Address()); err != nil {
return errorsmod.Wrapf(err, "failed to delete account %s", obj.Address())
}
} else {
Expand Down
6 changes: 6 additions & 0 deletions x/vm/statedb/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ func (suite *StateDBTestSuite) TestDBError() {
db.SelfDestruct(mocks.ErrAddress)
suite.Require().True(db.HasSelfDestructed(mocks.ErrAddress))
}},
{"delete new contract account", func(db vm.StateDB) {
db.CreateAccount(mocks.ErrAddress)
db.CreateContract(mocks.ErrAddress)
db.SelfDestruct(mocks.ErrAddress)
suite.Require().True(db.HasSelfDestructed(mocks.ErrAddress))
}},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
Expand Down
4 changes: 4 additions & 0 deletions x/vm/types/mocks/EVMKeeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ func (k EVMKeeper) DeleteAccount(_ sdk.Context, addr common.Address) error {
return nil
}

func (k EVMKeeper) DeleteNewContractAccount(ctx sdk.Context, addr common.Address) error {
return k.DeleteAccount(ctx, addr)
}

func (k EVMKeeper) Clone() *EVMKeeper {
accounts := maps.Clone(k.accounts)
codes := maps.Clone(k.codes)
Expand Down
Loading