From c08dcfad0ccc90ec4cbd43bf05f463f9a30c6573 Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Wed, 2 Jun 2021 04:06:12 -0400 Subject: [PATCH] keeper: `StateDB` unit tests (#47) * keeper: statedb unit tests * evm: balance tests * evm: nonce and code tests * evm: refund test * evm: fix tx encoding * storage and access list tests --- app/encoding.go | 16 +- tests/signer.go | 17 + x/evm/keeper/keeper_test.go | 80 +---- x/evm/keeper/statedb.go | 60 +++- x/evm/keeper/statedb_test.go | 600 +++++++++++++++++++++++++++++++++++ x/evm/types/logs.go | 22 +- x/evm/types/msg.go | 6 +- 7 files changed, 685 insertions(+), 116 deletions(-) diff --git a/app/encoding.go b/app/encoding.go index ee3cf1d9..1ea6ca44 100644 --- a/app/encoding.go +++ b/app/encoding.go @@ -7,9 +7,11 @@ import ( "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/tx" - evmtypes "github.com/cosmos/ethermint/x/evm/types" + + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/cosmos/ethermint/codec" + evmtypes "github.com/cosmos/ethermint/x/evm/types" ) // MakeEncodingConfig creates an EncodingConfig for testing @@ -49,9 +51,9 @@ func NewTxConfig(marshaler amino.ProtoCodecMarshaler) client.TxConfig { // TxEncoder overwrites sdk.TxEncoder to support MsgEthereumTx func (g txConfig) TxEncoder() sdk.TxEncoder { return func(tx sdk.Tx) ([]byte, error) { - ethtx, ok := tx.(*evmtypes.MsgEthereumTx) + msg, ok := tx.(*evmtypes.MsgEthereumTx) if ok { - return g.cdc.MarshalBinaryBare(ethtx) + return msg.AsTransaction().MarshalBinary() } return g.TxConfig.TxEncoder()(tx) } @@ -60,11 +62,13 @@ func (g txConfig) TxEncoder() sdk.TxEncoder { // TxDecoder overwrites sdk.TxDecoder to support MsgEthereumTx func (g txConfig) TxDecoder() sdk.TxDecoder { return func(txBytes []byte) (sdk.Tx, error) { - var ethtx evmtypes.MsgEthereumTx + tx := ðtypes.Transaction{} - err := g.cdc.UnmarshalBinaryBare(txBytes, ðtx) + err := tx.UnmarshalBinary(txBytes) if err == nil { - return ðtx, nil + msg := &evmtypes.MsgEthereumTx{} + msg.FromEthereumTx(tx) + return msg, nil } return g.TxConfig.TxDecoder()(txBytes) diff --git a/tests/signer.go b/tests/signer.go index 03259baa..885bfc72 100644 --- a/tests/signer.go +++ b/tests/signer.go @@ -3,6 +3,9 @@ package tests import ( "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keyring" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -10,6 +13,20 @@ import ( "github.com/cosmos/ethermint/crypto/ethsecp256k1" ) +// NewAddrKey generates an Ethereum address and its corresponding private key. +func NewAddrKey() (common.Address, cryptotypes.PrivKey) { + privkey, _ := ethsecp256k1.GenerateKey() + addr := crypto.PubkeyToAddress(privkey.ToECDSA().PublicKey) + + return addr, privkey +} + +// GenerateAddress generates an Ethereum address. +func GenerateAddress() common.Address { + addr, _ := NewAddrKey() + return addr +} + var _ keyring.Signer = &Signer{} // Signer defines a type that is used on testing for signing MsgEthereumTx diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 97dcba36..570df989 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "math/big" "testing" "time" @@ -16,7 +15,6 @@ import ( "github.com/cosmos/ethermint/x/evm/types" ethcmn "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" ethcrypto "github.com/ethereum/go-ethereum/crypto" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -43,7 +41,7 @@ func (suite *KeeperTestSuite) SetupTest() { suite.app = app.Setup(checkTx) suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-1", Time: time.Now().UTC()}) - suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx) + suite.app.EvmKeeper.WithContext(suite.ctx) suite.address = ethcmn.HexToAddress(addrHex) @@ -65,82 +63,6 @@ func TestKeeperTestSuite(t *testing.T) { suite.Run(t, new(KeeperTestSuite)) } -func (suite *KeeperTestSuite) TestTransactionLogs() { - ethHash := ethcmn.BytesToHash(hash) - log := ðtypes.Log{ - Address: suite.address, - Data: []byte("log"), - BlockNumber: 10, - } - log2 := ðtypes.Log{ - Address: suite.address, - Data: []byte("log2"), - BlockNumber: 11, - } - expLogs := []*ethtypes.Log{log} - - err := suite.app.EvmKeeper.CommitStateDB.SetLogs(ethHash, expLogs) - suite.Require().NoError(err) - - logs, err := suite.app.EvmKeeper.CommitStateDB.GetLogs(ethHash) - suite.Require().NoError(err) - suite.Require().Equal(expLogs, logs) - - expLogs = []*ethtypes.Log{log2, log} - - // add another log under the zero hash - suite.app.EvmKeeper.CommitStateDB.AddLog(log2) - logs = suite.app.EvmKeeper.CommitStateDB.AllLogs() - suite.Require().Equal(expLogs, logs) - - // add another log under the zero hash - log3 := ðtypes.Log{ - Address: suite.address, - Data: []byte("log3"), - BlockNumber: 10, - } - suite.app.EvmKeeper.CommitStateDB.AddLog(log3) - - txLogs := suite.app.EvmKeeper.GetAllTxLogs(suite.ctx) - suite.Require().Equal(2, len(txLogs)) - - suite.Require().Equal(ethcmn.Hash{}.String(), txLogs[0].Hash) - suite.Require().Equal([]*ethtypes.Log{log2, log3}, txLogs[0].Logs) - - suite.Require().Equal(ethHash.String(), txLogs[1].Hash) - suite.Require().Equal([]*ethtypes.Log{log}, txLogs[1].Logs) -} - -func (suite *KeeperTestSuite) TestDBStorage() { - // Perform state transitions - suite.app.EvmKeeper.CommitStateDB.CreateAccount(suite.address) - suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.address, big.NewInt(5)) - suite.app.EvmKeeper.CommitStateDB.SetNonce(suite.address, 4) - suite.app.EvmKeeper.CommitStateDB.SetState(suite.address, ethcmn.HexToHash("0x2"), ethcmn.HexToHash("0x3")) - suite.app.EvmKeeper.CommitStateDB.SetCode(suite.address, []byte{0x1}) - - // Test block height mapping functionality - testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) - suite.app.EvmKeeper.SetBlockBloom(suite.ctx, 4, testBloom) - - // Get those state transitions - suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetBalance(suite.address).Cmp(big.NewInt(5)), 0) - suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetNonce(suite.address), uint64(4)) - suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetState(suite.address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3")) - suite.Require().Equal(suite.app.EvmKeeper.CommitStateDB.GetCode(suite.address), []byte{0x1}) - - bloom, found := suite.app.EvmKeeper.GetBlockBloom(suite.ctx, 4) - suite.Require().True(found) - suite.Require().Equal(bloom, testBloom) - - // commit stateDB - _, err := suite.app.EvmKeeper.CommitStateDB.Commit(false) - suite.Require().NoError(err, "failed to commit StateDB") - - // simulate BaseApp EndBlocker commitment - suite.app.Commit() -} - func (suite *KeeperTestSuite) TestChainConfig() { config, found := suite.app.EvmKeeper.GetChainConfig(suite.ctx) suite.Require().True(found) diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index ccf06c9d..f63d060a 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -9,7 +9,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -37,7 +36,8 @@ func (k *Keeper) CreateAccount(addr common.Address) { k.ResetAccount(addr) } - _ = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr) + account = k.accountKeeper.NewAccountWithAddress(k.ctx, cosmosAddr) + k.accountKeeper.SetAccount(k.ctx, account) k.Logger(k.ctx).Debug( log, @@ -52,6 +52,15 @@ func (k *Keeper) CreateAccount(addr common.Address) { // AddBalance calls CommitStateDB.AddBalance using the passed in context func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { + if amount.Sign() != 1 { + k.Logger(k.ctx).Debug( + "ignored non-positive amount addition", + "ethereum-address", addr.Hex(), + "amount", amount.Int64(), + ) + return + } + cosmosAddr := sdk.AccAddress(addr.Bytes()) params := k.GetParams(k.ctx) @@ -76,6 +85,15 @@ func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) { // SubBalance calls CommitStateDB.SubBalance using the passed in context func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) { + if amount.Sign() != 1 { + k.Logger(k.ctx).Debug( + "ignored non-positive amount addition", + "ethereum-address", addr.Hex(), + "amount", amount.Int64(), + ) + return + } + cosmosAddr := sdk.AccAddress(addr.Bytes()) params := k.GetParams(k.ctx) @@ -128,7 +146,8 @@ func (k *Keeper) GetNonce(addr common.Address) uint64 { return nonce } -// SetNonce calls CommitStateDB.SetNonce using the passed in context +// SetNonce sets the given nonce as the sequence of the address' account. If the +// account doesn't exist, a new one will be created from the address. func (k *Keeper) SetNonce(addr common.Address, nonce uint64) { cosmosAddr := sdk.AccAddress(addr.Bytes()) account := k.accountKeeper.GetAccount(k.ctx, cosmosAddr) @@ -215,6 +234,7 @@ func (k *Keeper) SetCode(addr common.Address, code []byte) { account := k.accountKeeper.GetAccount(k.ctx, addr.Bytes()) if account == nil { account = k.accountKeeper.NewAccountWithAddress(k.ctx, addr.Bytes()) + k.accountKeeper.SetAccount(k.ctx, account) } ethAccount, isEthAccount := account.(*ethermint.EthAccount) @@ -306,7 +326,8 @@ func (k *Keeper) GetRefund() uint64 { // State // ---------------------------------------------------------------------------- -// GetCommittedState calls CommitStateDB.GetCommittedState using the passed in context +// GetCommittedState returns the value set in store for the given key hash. If the key is not registered +// this function returns the empty hash. func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) @@ -319,13 +340,14 @@ func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common return common.BytesToHash(value) } -// GetState calls CommitStateDB.GetState using the passed in context +// GetState returns the commited state for the given key hash, as all changes are commited directly +// to the KVStore. func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash { - // All state is committed directly return k.GetCommittedState(addr, hash) } -// SetState calls CommitStateDB.SetState using the passed in context +// SetState sets the given hashes (key, value) to the KVStore. If the value hash is empty, this +// function deletes the key from the store. func (k *Keeper) SetState(addr common.Address, key, value common.Hash) { store := prefix.NewStore(k.ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) key = types.KeyAddressStorage(addr, key) @@ -415,6 +437,8 @@ func (k *Keeper) Exist(addr common.Address) bool { // - nonce is 0 // - balance amount for evm denom is 0 // - account code hash is empty +// +// Non-ethereum accounts are considered not empty func (k *Keeper) Empty(addr common.Address) bool { nonce := uint64(0) codeHash := types.EmptyCodeHash @@ -426,7 +450,6 @@ func (k *Keeper) Empty(addr common.Address) bool { nonce = account.GetSequence() ethAccount, isEthAccount := account.(*ethermint.EthAccount) if !isEthAccount { - // NOTE: non-ethereum accounts are considered not empty return false } @@ -526,20 +549,31 @@ func (k *Keeper) RevertToSnapshot(_ int) {} // context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log // to store. func (k *Keeper) AddLog(log *ethtypes.Log) { - txHash := common.BytesToHash(tmtypes.Tx(k.ctx.TxBytes()).Hash()) + if len(k.ctx.TxBytes()) > 0 { + tx := ðtypes.Transaction{} + if err := tx.UnmarshalBinary(k.ctx.TxBytes()); err != nil { + k.Logger(k.ctx).Error( + "ethereum tx unmarshaling failed", + "error", err, + ) + return + } + + log.TxHash = tx.Hash() + } log.BlockHash = k.headerHash - log.TxHash = txHash log.TxIndex = uint(k.GetTxIndexTransient()) - logs := k.GetTxLogs(txHash) + logs := k.GetTxLogs(log.TxHash) + log.Index = uint(len(logs)) logs = append(logs, log) - k.SetLogs(txHash, logs) + k.SetLogs(log.TxHash, logs) k.Logger(k.ctx).Debug( "log added", - "tx-hash", txHash.Hex(), + "tx-hash", log.TxHash.Hex(), "log-index", int(log.Index), ) } diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 94292649..70e15481 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -1 +1,601 @@ package keeper_test + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/cosmos/ethermint/tests" + "github.com/cosmos/ethermint/x/evm/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +func (suite *KeeperTestSuite) TestCreateAccount() { + testCases := []struct { + name string + addr common.Address + malleate func(common.Address) + callback func(common.Address) + }{ + { + "reset account", + suite.address, + func(addr common.Address) { + suite.app.EvmKeeper.AddBalance(addr, big.NewInt(100)) + suite.Require().NotZero(suite.app.EvmKeeper.GetBalance(addr).Int64()) + }, + func(addr common.Address) { + suite.Require().Zero(suite.app.EvmKeeper.GetBalance(addr).Int64()) + }, + }, + { + "create account", + tests.GenerateAddress(), + func(addr common.Address) { + acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addr.Bytes()) + suite.Require().Nil(acc) + }, + func(addr common.Address) { + acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addr.Bytes()) + suite.Require().NotNil(acc) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate(tc.addr) + suite.app.EvmKeeper.CreateAccount(tc.addr) + tc.callback(tc.addr) + }) + } +} + +func (suite *KeeperTestSuite) TestAddBalance() { + testCases := []struct { + name string + amount *big.Int + isNoOp bool + }{ + { + "positive amount", + big.NewInt(100), + false, + }, + { + "zero amount", + big.NewInt(0), + true, + }, + { + "negative amount", + big.NewInt(-1), + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + prev := suite.app.EvmKeeper.GetBalance(suite.address) + suite.app.EvmKeeper.AddBalance(suite.address, tc.amount) + post := suite.app.EvmKeeper.GetBalance(suite.address) + + if tc.isNoOp { + suite.Require().Equal(prev.Int64(), post.Int64()) + } else { + suite.Require().Equal(new(big.Int).Add(prev, tc.amount).Int64(), post.Int64()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSubBalance() { + testCases := []struct { + name string + amount *big.Int + malleate func() + isNoOp bool + }{ + { + "positive amount, below zero", + big.NewInt(100), + func() {}, + true, + }, + { + "positive amount, below zero", + big.NewInt(50), + func() { + suite.app.EvmKeeper.AddBalance(suite.address, big.NewInt(100)) + }, + false, + }, + { + "zero amount", + big.NewInt(0), + func() {}, + true, + }, + { + "negative amount", + big.NewInt(-1), + func() {}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + prev := suite.app.EvmKeeper.GetBalance(suite.address) + suite.app.EvmKeeper.SubBalance(suite.address, tc.amount) + post := suite.app.EvmKeeper.GetBalance(suite.address) + + if tc.isNoOp { + suite.Require().Equal(prev.Int64(), post.Int64()) + } else { + suite.Require().Equal(new(big.Int).Sub(prev, tc.amount).Int64(), post.Int64()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestGetNonce() { + testCases := []struct { + name string + address common.Address + expectedNonce uint64 + malleate func() + }{ + { + "account not found", + tests.GenerateAddress(), + 0, + func() {}, + }, + { + "existing account", + suite.address, + 1, + func() { + suite.app.EvmKeeper.SetNonce(suite.address, 1) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + nonce := suite.app.EvmKeeper.GetNonce(tc.address) + suite.Require().Equal(tc.expectedNonce, nonce) + + }) + } +} + +func (suite *KeeperTestSuite) TestSetNonce() { + testCases := []struct { + name string + address common.Address + nonce uint64 + malleate func() + }{ + { + "new account", + tests.GenerateAddress(), + 10, + func() {}, + }, + { + "existing account", + suite.address, + 99, + func() {}, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.app.EvmKeeper.SetNonce(tc.address, tc.nonce) + nonce := suite.app.EvmKeeper.GetNonce(tc.address) + suite.Require().Equal(tc.nonce, nonce) + }) + } +} + +func (suite *KeeperTestSuite) TestGetCodeHash() { + addr := tests.GenerateAddress() + baseAcc := &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()} + suite.app.AccountKeeper.SetAccount(suite.ctx, baseAcc) + + testCases := []struct { + name string + address common.Address + expHash common.Hash + malleate func() + }{ + { + "account not found", + tests.GenerateAddress(), + common.BytesToHash(types.EmptyCodeHash), + func() {}, + }, + { + "account not EthAccount type", + addr, + common.BytesToHash(types.EmptyCodeHash), + func() {}, + }, + { + "existing account", + suite.address, + crypto.Keccak256Hash([]byte("codeHash")), + func() { + suite.app.EvmKeeper.SetCode(suite.address, []byte("codeHash")) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + + tc.malleate() + + hash := suite.app.EvmKeeper.GetCodeHash(tc.address) + suite.Require().Equal(tc.expHash, hash) + }) + } +} + +func (suite *KeeperTestSuite) TestSetCode() { + addr := tests.GenerateAddress() + baseAcc := &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()} + suite.app.AccountKeeper.SetAccount(suite.ctx, baseAcc) + + testCases := []struct { + name string + address common.Address + code []byte + isNoOp bool + }{ + { + "account not found", + tests.GenerateAddress(), + []byte("code"), + false, + }, + { + "account not EthAccount type", + addr, + nil, + true, + }, + { + "existing account", + suite.address, + []byte("code"), + false, + }, + { + "existing account, code deleted from store", + suite.address, + nil, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + + prev := suite.app.EvmKeeper.GetCode(tc.address) + suite.app.EvmKeeper.SetCode(tc.address, tc.code) + post := suite.app.EvmKeeper.GetCode(tc.address) + + if tc.isNoOp { + suite.Require().Equal(prev, post) + } else { + suite.Require().Equal(tc.code, post) + } + + suite.Require().Equal(len(post), suite.app.EvmKeeper.GetCodeSize(tc.address)) + }) + } +} + +func (suite *KeeperTestSuite) TestRefund() { + testCases := []struct { + name string + malleate func() + expRefund uint64 + expPanic bool + }{ + { + "success - add and subtract refund", + func() { + suite.app.EvmKeeper.AddRefund(11) + }, + 1, + false, + }, + { + "fail - subtract amount > current refund", + func() { + }, + 0, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + + tc.malleate() + + if tc.expPanic { + suite.Require().Panics(func() { suite.app.EvmKeeper.SubRefund(10) }) + } else { + suite.app.EvmKeeper.SubRefund(10) + suite.Require().Equal(tc.expRefund, suite.app.EvmKeeper.GetRefund()) + } + + // clear and reset refund from store + suite.app.EvmKeeper.ResetRefundTransient(suite.ctx) + suite.Require().Zero(suite.app.EvmKeeper.GetRefund()) + }) + } +} + +func (suite *KeeperTestSuite) TestState() { + testCases := []struct { + name string + key, value common.Hash + }{ + { + "set state - delete from store", + common.BytesToHash([]byte("key")), + common.Hash{}, + }, + { + "set state - update value", + common.BytesToHash([]byte("key")), + common.BytesToHash([]byte("value")), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + + suite.app.EvmKeeper.SetState(suite.address, tc.key, tc.value) + value := suite.app.EvmKeeper.GetState(suite.address, tc.key) + suite.Require().Equal(tc.value, value) + }) + } +} + +func (suite *KeeperTestSuite) TestSuicide() { + testCases := []struct { + name string + suicided bool + }{ + {"success, first time suicided", true}, + {"success, already suicided", true}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.Require().Equal(tc.suicided, suite.app.EvmKeeper.Suicide(suite.address)) + suite.Require().Equal(tc.suicided, suite.app.EvmKeeper.HasSuicided(suite.address)) + }) + } +} + +func (suite *KeeperTestSuite) TestExist() { + testCases := []struct { + name string + address common.Address + malleate func() + exists bool + }{ + {"success, account exists", suite.address, func() {}, true}, + {"success, has suicided", suite.address, func() { + suite.app.EvmKeeper.Suicide(suite.address) + }, true}, + {"success, account doesn't exist", tests.GenerateAddress(), func() {}, false}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + suite.Require().Equal(tc.exists, suite.app.EvmKeeper.Exist(tc.address)) + }) + } +} + +func (suite *KeeperTestSuite) TestEmpty() { + addr := tests.GenerateAddress() + baseAcc := &authtypes.BaseAccount{Address: sdk.AccAddress(addr.Bytes()).String()} + suite.app.AccountKeeper.SetAccount(suite.ctx, baseAcc) + + testCases := []struct { + name string + address common.Address + malleate func() + empty bool + }{ + {"empty, account exists", suite.address, func() {}, true}, + {"not empty, non ethereum account", addr, func() {}, false}, + {"not empty, positive balance", suite.address, func() { + suite.app.EvmKeeper.AddBalance(suite.address, big.NewInt(100)) + }, false}, + {"empty, account doesn't exist", tests.GenerateAddress(), func() {}, true}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + suite.Require().Equal(tc.empty, suite.app.EvmKeeper.Empty(tc.address)) + }) + } +} + +func (suite *KeeperTestSuite) TestSnapshot() { + revision := suite.app.EvmKeeper.Snapshot() + suite.Require().Zero(revision) + suite.app.EvmKeeper.RevertToSnapshot(revision) // no-op +} + +func (suite *KeeperTestSuite) TestAddLog() { + addr := tests.GenerateAddress() + msg := types.NewMsgEthereumTx(big.NewInt(1), 0, &suite.address, big.NewInt(1), 100000, big.NewInt(1), []byte("test"), nil) + tx := msg.AsTransaction() + txBz, err := tx.MarshalBinary() + suite.Require().NoError(err) + txHash := tx.Hash() + + testCases := []struct { + name string + log, expLog *ethtypes.Log // pre and post populating log fields + malleate func() + }{ + { + "block hash not found", + ðtypes.Log{ + Address: addr, + }, + ðtypes.Log{ + Address: addr, + }, + func() {}, + }, + { + "tx hash from message", + ðtypes.Log{ + Address: addr, + }, + ðtypes.Log{ + Address: addr, + TxHash: txHash, + }, + func() { + suite.app.EvmKeeper.WithContext(suite.ctx.WithTxBytes(txBz)) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + prev := suite.app.EvmKeeper.GetTxLogs(tc.expLog.TxHash) + suite.app.EvmKeeper.AddLog(tc.log) + post := suite.app.EvmKeeper.GetTxLogs(tc.expLog.TxHash) + + suite.Require().NotZero(len(post), tc.expLog.TxHash.Hex()) + suite.Require().Equal(len(prev)+1, len(post)) + suite.Require().NotNil(post[len(post)-1]) + suite.Require().Equal(tc.log, post[len(post)-1]) + }) + } +} + +func (suite *KeeperTestSuite) TestAccessList() { + dest := tests.GenerateAddress() + precompiles := []common.Address{tests.GenerateAddress(), tests.GenerateAddress()} + accesses := ethtypes.AccessList{ + {Address: tests.GenerateAddress(), StorageKeys: []common.Hash{common.BytesToHash([]byte("key"))}}, + {Address: tests.GenerateAddress(), StorageKeys: []common.Hash{common.BytesToHash([]byte("key1"))}}, + } + + suite.app.EvmKeeper.PrepareAccessList(suite.address, &dest, precompiles, accesses) + + suite.Require().True(suite.app.EvmKeeper.AddressInAccessList(suite.address)) + suite.Require().True(suite.app.EvmKeeper.AddressInAccessList(dest)) + + for _, precompile := range precompiles { + suite.Require().True(suite.app.EvmKeeper.AddressInAccessList(precompile)) + } + + for _, access := range accesses { + for _, key := range access.StorageKeys { + addrOK, slotOK := suite.app.EvmKeeper.SlotInAccessList(access.Address, key) + suite.Require().True(addrOK) + suite.Require().True(slotOK) + } + } +} + +func (suite *KeeperTestSuite) TestForEachStorage() { + var storage types.Storage + + testCase := []struct { + name string + malleate func() + callback func(key, value common.Hash) (stop bool) + expValues []common.Hash + }{ + { + "aggregate state", + func() { + for i := 0; i < 5; i++ { + suite.app.EvmKeeper.SetState(suite.address, common.BytesToHash([]byte(fmt.Sprintf("key%d", i))), common.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) + } + }, + func(key, value common.Hash) bool { + storage = append(storage, types.NewState(key, value)) + return false + }, + []common.Hash{ + common.BytesToHash([]byte("value0")), + common.BytesToHash([]byte("value1")), + common.BytesToHash([]byte("value2")), + common.BytesToHash([]byte("value3")), + common.BytesToHash([]byte("value4")), + }, + }, + { + "filter state", + func() { + suite.app.EvmKeeper.SetState(suite.address, common.BytesToHash([]byte("key")), common.BytesToHash([]byte("value"))) + suite.app.EvmKeeper.SetState(suite.address, common.BytesToHash([]byte("filterkey")), common.BytesToHash([]byte("filtervalue"))) + }, + func(key, value common.Hash) bool { + if value == common.BytesToHash([]byte("filtervalue")) { + storage = append(storage, types.NewState(key, value)) + return true + } + return false + }, + []common.Hash{ + common.BytesToHash([]byte("filtervalue")), + }, + }, + } + + for _, tc := range testCase { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + tc.malleate() + + err := suite.app.EvmKeeper.ForEachStorage(suite.address, tc.callback) + suite.Require().NoError(err) + suite.Require().Equal(len(tc.expValues), len(storage), fmt.Sprintf("Expected values:\n%v\nStorage Values\n%v", tc.expValues, storage)) + + vals := make([]common.Hash, len(storage)) + for i := range storage { + vals[i] = common.HexToHash(storage[i].Value) + } + + // TODO: not sure why Equals fails + suite.Require().ElementsMatch(tc.expValues, vals) + }) + storage = types.Storage{} + } +} diff --git a/x/evm/types/logs.go b/x/evm/types/logs.go index 6e0545fd..2a5f94de 100644 --- a/x/evm/types/logs.go +++ b/x/evm/types/logs.go @@ -4,8 +4,6 @@ import ( "errors" "fmt" - log "github.com/xlab/suplog" - ethcmn "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -77,9 +75,9 @@ func (log *Log) Validate() error { // ToEthereum returns the Ethereum type Log from a Ethermint-proto compatible Log. func (log *Log) ToEthereum() *ethtypes.Log { - topics := make([]ethcmn.Hash, len(log.Topics)) + var topics []ethcmn.Hash // nolint: prealloc for i := range log.Topics { - topics[i] = ethcmn.HexToHash(log.Topics[i]) + topics = append(topics, ethcmn.HexToHash(log.Topics[i])) } return ðtypes.Log{ @@ -97,24 +95,18 @@ func (log *Log) ToEthereum() *ethtypes.Log { // LogsToEthereum casts the Ethermint Logs to a slice of Ethereum Logs. func LogsToEthereum(logs []*Log) []*ethtypes.Log { - ethLogs := make([]*ethtypes.Log, len(logs)) + var ethLogs []*ethtypes.Log // nolint: prealloc for i := range logs { - err := logs[i].Validate() - if err != nil { - log.WithError(err).Errorln("failed log validation", logs[i].String()) - continue - } - - ethLogs[i] = logs[i].ToEthereum() + ethLogs = append(ethLogs, logs[i].ToEthereum()) } return ethLogs } // NewLogFromEth creates a new Log instance from a Ethereum type Log. func NewLogFromEth(log *ethtypes.Log) *Log { - topics := make([]string, len(log.Topics)) - for i := range log.Topics { - topics[i] = log.Topics[i].String() + var topics []string // nolint: prealloc + for _, topic := range log.Topics { + topics = append(topics, topic.String()) } return &Log{ diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 32c275f4..46211803 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -89,7 +89,7 @@ func newMsgEthereumTx( } // fromEthereumTx populates the message fields from the given ethereum transaction -func (msg *MsgEthereumTx) fromEthereumTx(tx *ethtypes.Transaction) { +func (msg *MsgEthereumTx) FromEthereumTx(tx *ethtypes.Transaction) { to := "" if tx.To() != nil { to = tx.To().Hex() @@ -227,7 +227,7 @@ func (msg *MsgEthereumTx) DecodeRLP(stream *rlp.Stream) error { return err } - msg.fromEthereumTx(tx) + msg.FromEthereumTx(tx) return nil } @@ -258,7 +258,7 @@ func (msg *MsgEthereumTx) Sign(ethSigner ethtypes.Signer, keyringSigner keyring. return err } - msg.fromEthereumTx(tx) + msg.FromEthereumTx(tx) return nil }