package keeper_test import ( "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" ethcmn "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/cosmos/ethermint/crypto" ethermint "github.com/cosmos/ethermint/types" "github.com/cosmos/ethermint/x/evm/types" ) func (suite *KeeperTestSuite) TestBloomFilter() { // Prepare db for logs tHash := ethcmn.BytesToHash([]byte{0x1}) suite.app.EvmKeeper.Prepare(suite.ctx, tHash, ethcmn.Hash{}, 0) contractAddress := ethcmn.BigToAddress(big.NewInt(1)) log := ethtypes.Log{Address: contractAddress} testCase := []struct { name string malleate func() numLogs int isBloom bool }{ { "no logs", func() {}, 0, false, }, { "add log", func() { suite.app.EvmKeeper.AddLog(suite.ctx, &log) }, 1, false, }, { "bloom", func() {}, 0, true, }, } for _, tc := range testCase { tc.malleate() logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, tHash) if !tc.isBloom { suite.Require().NoError(err, tc.name) suite.Require().Len(logs, tc.numLogs, tc.name) if len(logs) != 0 { suite.Require().Equal(log, *logs[0], tc.name) } } else { // get logs bloom from the log bloomInt := ethtypes.LogsBloom(logs) bloomFilter := ethtypes.BytesToBloom(bloomInt.Bytes()) suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress), tc.name) suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2))), tc.name) } } } func (suite *KeeperTestSuite) TestStateDB_Balance() { testCase := []struct { name string malleate func() balance *big.Int }{ { "set balance", func() { suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, big.NewInt(100)) }, big.NewInt(100), }, { "sub balance", func() { suite.app.EvmKeeper.SubBalance(suite.ctx, suite.address, big.NewInt(100)) }, big.NewInt(0), }, { "add balance", func() { suite.app.EvmKeeper.AddBalance(suite.ctx, suite.address, big.NewInt(200)) }, big.NewInt(200), }, } for _, tc := range testCase { tc.malleate() suite.Require().Equal(tc.balance, suite.app.EvmKeeper.GetBalance(suite.ctx, suite.address), tc.name) } } func (suite *KeeperTestSuite) TestStateDBNonce() { nonce := uint64(123) suite.app.EvmKeeper.SetNonce(suite.ctx, suite.address, nonce) suite.Require().Equal(nonce, suite.app.EvmKeeper.GetNonce(suite.ctx, suite.address)) } func (suite *KeeperTestSuite) TestStateDB_Error() { nonce := suite.app.EvmKeeper.GetNonce(suite.ctx, ethcmn.Address{}) suite.Require().Equal(0, int(nonce)) suite.Require().Error(suite.app.EvmKeeper.Error(suite.ctx)) } func (suite *KeeperTestSuite) TestStateDB_Database() { suite.Require().Nil(suite.app.EvmKeeper.Database(suite.ctx)) } func (suite *KeeperTestSuite) TestStateDB_State() { key := ethcmn.BytesToHash([]byte("foo")) val := ethcmn.BytesToHash([]byte("bar")) suite.app.EvmKeeper.SetState(suite.ctx, suite.address, key, val) testCase := []struct { name string address ethcmn.Address key ethcmn.Hash value ethcmn.Hash }{ { "found state", suite.address, ethcmn.BytesToHash([]byte("foo")), ethcmn.BytesToHash([]byte("bar")), }, { "state not found", suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.Hash{}, }, { "object not found", ethcmn.Address{}, ethcmn.BytesToHash([]byte("foo")), ethcmn.Hash{}, }, } for _, tc := range testCase { value := suite.app.EvmKeeper.GetState(suite.ctx, tc.address, tc.key) suite.Require().Equal(tc.value, value, tc.name) } } func (suite *KeeperTestSuite) TestStateDB_Code() { testCase := []struct { name string address ethcmn.Address code []byte malleate func() }{ { "no stored code for state object", suite.address, nil, func() {}, }, { "existing address", suite.address, []byte("code"), func() { suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, []byte("code")) }, }, { "state object not found", ethcmn.Address{}, nil, func() {}, }, } for _, tc := range testCase { tc.malleate() suite.Require().Equal(tc.code, suite.app.EvmKeeper.GetCode(suite.ctx, tc.address), tc.name) suite.Require().Equal(len(tc.code), suite.app.EvmKeeper.GetCodeSize(suite.ctx, tc.address), tc.name) } } func (suite *KeeperTestSuite) TestStateDB_Logs() { testCase := []struct { name string log ethtypes.Log }{ { "state db log", ethtypes.Log{ Address: suite.address, Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))}, Data: []byte("data"), BlockNumber: 1, TxHash: ethcmn.Hash{}, TxIndex: 1, BlockHash: ethcmn.Hash{}, Index: 1, Removed: false, }, }, } for _, tc := range testCase { hash := ethcmn.BytesToHash([]byte("hash")) logs := []*ethtypes.Log{&tc.log} err := suite.app.EvmKeeper.SetLogs(suite.ctx, hash, logs) suite.Require().NoError(err, tc.name) dbLogs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, hash) suite.Require().NoError(err, tc.name) suite.Require().Equal(logs, dbLogs, tc.name) suite.app.EvmKeeper.DeleteLogs(suite.ctx, hash) dbLogs, err = suite.app.EvmKeeper.GetLogs(suite.ctx, hash) suite.Require().NoError(err, tc.name) suite.Require().Empty(dbLogs, tc.name) suite.app.EvmKeeper.AddLog(suite.ctx, &tc.log) suite.Require().Equal(logs, suite.app.EvmKeeper.AllLogs(suite.ctx), tc.name) //resets state but checking to see if storekey still persists. err = suite.app.EvmKeeper.Reset(suite.ctx, hash) suite.Require().NoError(err, tc.name) suite.Require().Equal(logs, suite.app.EvmKeeper.AllLogs(suite.ctx), tc.name) } } func (suite *KeeperTestSuite) TestStateDB_Preimage() { hash := ethcmn.BytesToHash([]byte("hash")) preimage := []byte("preimage") suite.app.EvmKeeper.AddPreimage(suite.ctx, hash, preimage) suite.Require().Equal(preimage, suite.app.EvmKeeper.Preimages(suite.ctx)[hash]) } func (suite *KeeperTestSuite) TestStateDB_Refund() { testCase := []struct { name string addAmount uint64 subAmount uint64 expRefund uint64 expPanic bool }{ { "refund 0", 0, 0, 0, false, }, { "refund positive amount", 100, 0, 100, false, }, { "refund panic", 100, 200, 100, true, }, } for _, tc := range testCase { suite.Run(tc.name, func() { suite.SetupTest() // reset suite.app.EvmKeeper.AddRefund(suite.ctx, tc.addAmount) suite.Require().Equal(tc.addAmount, suite.app.EvmKeeper.GetRefund(suite.ctx)) if tc.expPanic { suite.Panics(func() { suite.app.EvmKeeper.SubRefund(suite.ctx, tc.subAmount) }) } else { suite.app.EvmKeeper.SubRefund(suite.ctx, tc.subAmount) suite.Require().Equal(tc.expRefund, suite.app.EvmKeeper.GetRefund(suite.ctx)) } }) } } func (suite *KeeperTestSuite) TestStateDB_CreateAccount() { prevBalance := big.NewInt(12) testCase := []struct { name string address ethcmn.Address malleate func() }{ { "existing account", suite.address, func() { suite.app.EvmKeeper.AddBalance(suite.ctx, suite.address, prevBalance) }, }, { "new account", ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1"), func() { prevBalance = big.NewInt(0) }, }, } for _, tc := range testCase { suite.Run(tc.name, func() { suite.SetupTest() // reset tc.malleate() suite.app.EvmKeeper.CreateAccount(suite.ctx, tc.address) suite.Require().True(suite.app.EvmKeeper.Exist(suite.ctx, tc.address)) suite.Require().Equal(prevBalance, suite.app.EvmKeeper.GetBalance(suite.ctx, tc.address)) }) } } func (suite *KeeperTestSuite) TestStateDB_ClearStateObj() { priv, err := crypto.GenerateKey() suite.Require().NoError(err) addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey) suite.app.EvmKeeper.CreateAccount(suite.ctx, addr) suite.Require().True(suite.app.EvmKeeper.Exist(suite.ctx, addr)) suite.app.EvmKeeper.ClearStateObjects(suite.ctx) suite.Require().False(suite.app.EvmKeeper.Exist(suite.ctx, addr)) } func (suite *KeeperTestSuite) TestStateDB_Reset() { priv, err := crypto.GenerateKey() suite.Require().NoError(err) addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey) suite.app.EvmKeeper.CreateAccount(suite.ctx, addr) suite.Require().True(suite.app.EvmKeeper.Exist(suite.ctx, addr)) err = suite.app.EvmKeeper.Reset(suite.ctx, ethcmn.BytesToHash(nil)) suite.Require().NoError(err) suite.Require().False(suite.app.EvmKeeper.Exist(suite.ctx, addr)) } func (suite *KeeperTestSuite) TestSuiteDB_Prepare() { thash := ethcmn.BytesToHash([]byte("thash")) bhash := ethcmn.BytesToHash([]byte("bhash")) txi := 1 suite.app.EvmKeeper.Prepare(suite.ctx, thash, bhash, txi) suite.Require().Equal(txi, suite.app.EvmKeeper.TxIndex(suite.ctx)) suite.Require().Equal(bhash, suite.app.EvmKeeper.BlockHash(suite.ctx)) } func (suite *KeeperTestSuite) TestSuiteDB_CopyState() { testCase := []struct { name string log ethtypes.Log }{ { "copy state", ethtypes.Log{ Address: suite.address, Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))}, Data: []byte("data"), BlockNumber: 1, TxHash: ethcmn.Hash{}, TxIndex: 1, BlockHash: ethcmn.Hash{}, Index: 1, Removed: false, }, }, } for _, tc := range testCase { hash := ethcmn.BytesToHash([]byte("hash")) logs := []*ethtypes.Log{&tc.log} err := suite.app.EvmKeeper.SetLogs(suite.ctx, hash, logs) suite.Require().NoError(err, tc.name) copyDB := suite.app.EvmKeeper.Copy(suite.ctx) suite.Require().Equal(suite.app.EvmKeeper.Exist(suite.ctx, suite.address), copyDB.Exist(suite.address), tc.name) } } func (suite *KeeperTestSuite) TestSuiteDB_Empty() { suite.Require().True(suite.app.EvmKeeper.Empty(suite.ctx, suite.address)) suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, big.NewInt(100)) suite.Require().False(suite.app.EvmKeeper.Empty(suite.ctx, suite.address)) } func (suite *KeeperTestSuite) TestSuiteDB_Suicide() { testCase := []struct { name string amount *big.Int expPass bool delete bool }{ { "suicide zero balance", big.NewInt(0), false, false, }, { "suicide with balance", big.NewInt(100), true, false, }, { "delete", big.NewInt(0), true, true, }, } for _, tc := range testCase { if tc.delete { _, err := suite.app.EvmKeeper.Commit(suite.ctx, tc.delete) suite.Require().NoError(err, tc.name) suite.Require().False(suite.app.EvmKeeper.Exist(suite.ctx, suite.address), tc.name) continue } if tc.expPass { suite.app.EvmKeeper.SetBalance(suite.ctx, suite.address, tc.amount) suicide := suite.app.EvmKeeper.Suicide(suite.ctx, suite.address) suite.Require().True(suicide, tc.name) suite.Require().True(suite.app.EvmKeeper.HasSuicided(suite.ctx, suite.address), tc.name) } else { //Suicide only works for an account with non-zero balance/nonce priv, err := crypto.GenerateKey() suite.Require().NoError(err) addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey) suicide := suite.app.EvmKeeper.Suicide(suite.ctx, addr) suite.Require().False(suicide, tc.name) suite.Require().False(suite.app.EvmKeeper.HasSuicided(suite.ctx, addr), tc.name) } } } func (suite *KeeperTestSuite) TestCommitStateDB_Commit() { testCase := []struct { name string malleate func() deleteObjs bool expPass bool }{ { "commit suicided", func() { ok := suite.app.EvmKeeper.Suicide(suite.ctx, suite.address) suite.Require().True(ok) }, true, true, }, { "commit with dirty value", func() { suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, []byte("code")) }, false, true, }, } for _, tc := range testCase { tc.malleate() hash, err := suite.app.EvmKeeper.Commit(suite.ctx, tc.deleteObjs) suite.Require().Equal(ethcmn.Hash{}, hash) if !tc.expPass { suite.Require().Error(err, tc.name) continue } suite.Require().NoError(err, tc.name) acc := suite.app.AccountKeeper.GetAccount(suite.ctx, sdk.AccAddress(suite.address.Bytes())) if tc.deleteObjs { suite.Require().Nil(acc, tc.name) continue } suite.Require().NotNil(acc, tc.name) ethAcc, ok := acc.(*ethermint.EthAccount) suite.Require().True(ok) suite.Require().Equal(ethcrypto.Keccak256([]byte("code")), ethAcc.CodeHash) } } func (suite *KeeperTestSuite) TestCommitStateDB_Finalize() { testCase := []struct { name string malleate func() deleteObjs bool expPass bool }{ { "finalize suicided", func() { ok := suite.app.EvmKeeper.Suicide(suite.ctx, suite.address) suite.Require().True(ok) }, true, true, }, { "finalize, not suicided", func() { suite.app.EvmKeeper.AddBalance(suite.ctx, suite.address, big.NewInt(5)) }, false, true, }, { "finalize, dirty storage", func() { suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.BytesToHash([]byte("value"))) }, false, true, }, } for _, tc := range testCase { tc.malleate() err := suite.app.EvmKeeper.Finalise(suite.ctx, tc.deleteObjs) if !tc.expPass { suite.Require().Error(err, tc.name) hash := suite.app.EvmKeeper.GetCommittedState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte("key"))) suite.Require().NotEqual(ethcmn.Hash{}, hash, tc.name) continue } suite.Require().NoError(err, tc.name) acc := suite.app.AccountKeeper.GetAccount(suite.ctx, sdk.AccAddress(suite.address.Bytes())) if tc.deleteObjs { suite.Require().Nil(acc, tc.name) continue } suite.Require().NotNil(acc, tc.name) } } func (suite *KeeperTestSuite) TestCommitStateDB_GetCommittedState() { hash := suite.app.EvmKeeper.GetCommittedState(suite.ctx, ethcmn.Address{}, ethcmn.BytesToHash([]byte("key"))) suite.Require().Equal(ethcmn.Hash{}, hash) } func (suite *KeeperTestSuite) TestCommitStateDB_Snapshot() { id := suite.app.EvmKeeper.Snapshot(suite.ctx) suite.Require().NotPanics(func() { suite.app.EvmKeeper.RevertToSnapshot(suite.ctx, id) }) suite.Require().Panics(func() { suite.app.EvmKeeper.RevertToSnapshot(suite.ctx, -1) }, "invalid revision should panic") } func (suite *KeeperTestSuite) TestCommitStateDB_ForEachStorage() { var storage types.Storage testCase := []struct { name string malleate func() callback func(key, value ethcmn.Hash) (stop bool) expValues []ethcmn.Hash }{ { "aggregate state", func() { for i := 0; i < 5; i++ { suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte(fmt.Sprintf("key%d", i))), ethcmn.BytesToHash([]byte(fmt.Sprintf("value%d", i)))) } }, func(key, value ethcmn.Hash) bool { storage = append(storage, types.NewState(key, value)) return false }, []ethcmn.Hash{ ethcmn.BytesToHash([]byte("value0")), ethcmn.BytesToHash([]byte("value1")), ethcmn.BytesToHash([]byte("value2")), ethcmn.BytesToHash([]byte("value3")), ethcmn.BytesToHash([]byte("value4")), }, }, { "filter state", func() { suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.BytesToHash([]byte("value"))) suite.app.EvmKeeper.SetState(suite.ctx, suite.address, ethcmn.BytesToHash([]byte("filterkey")), ethcmn.BytesToHash([]byte("filtervalue"))) }, func(key, value ethcmn.Hash) bool { if value == ethcmn.BytesToHash([]byte("filtervalue")) { storage = append(storage, types.NewState(key, value)) return true } return false }, []ethcmn.Hash{ ethcmn.BytesToHash([]byte("filtervalue")), }, }, } for _, tc := range testCase { suite.Run(tc.name, func() { suite.SetupTest() // reset tc.malleate() suite.app.EvmKeeper.Finalise(suite.ctx, false) err := suite.app.EvmKeeper.ForEachStorage(suite.ctx, 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([]ethcmn.Hash, len(storage)) for i := range storage { vals[i] = storage[i].Value } suite.Require().ElementsMatch(tc.expValues, vals) }) storage = types.Storage{} } }