diff --git a/app/ante/eth.go b/app/ante/eth.go index 3838e3be14..cff1138a06 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -88,13 +88,13 @@ func (avd EthAccountVerificationDecorator) AnteHandle( if acct == nil { acc := avd.ak.NewAccountWithAddress(ctx, from) avd.ak.SetAccount(ctx, acc) - acct = statedb.NewEmptyAccount() } else if acct.IsContract() { return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash) } - if err := keeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(acct.Balance), txData); err != nil { + balance := avd.evmKeeper.GetEVMDenomBalance(ctx, fromAddr) + if err := keeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(balance), txData); err != nil { return ctx, errorsmod.Wrap(err, "failed to check sender balance") } } diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index 6ffdb7d4c5..4d397f76fa 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -45,7 +45,7 @@ type EVMKeeper interface { NewEVM(ctx sdk.Context, msg core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB, customContracts []vm.PrecompiledContract) *vm.EVM DeductTxCostsFromUserBalance(ctx sdk.Context, fees sdk.Coins, from common.Address) error - GetBalance(ctx sdk.Context, addr common.Address) *big.Int + GetEVMDenomBalance(ctx sdk.Context, addr common.Address) *big.Int ResetTransientGasUsed(ctx sdk.Context) GetTxIndexTransient(ctx sdk.Context) uint64 GetParams(ctx sdk.Context) evmtypes.Params diff --git a/app/ante/sigs_test.go b/app/ante/sigs_test.go index 0c7b0539c8..30f050458c 100644 --- a/app/ante/sigs_test.go +++ b/app/ante/sigs_test.go @@ -17,9 +17,10 @@ func (suite AnteTestSuite) TestSignatures() { acc := statedb.NewEmptyAccount() acc.Nonce = 1 - acc.Balance = big.NewInt(10000000000) + balance := big.NewInt(10000000000) suite.app.EvmKeeper.SetAccount(suite.ctx, addr, *acc) + suite.app.EvmKeeper.SetBalance(suite.ctx, addr, balance) msgEthereumTx := evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &to, big.NewInt(10), 100000, big.NewInt(1), nil, nil, nil, nil) msgEthereumTx.From = addr.Hex() diff --git a/go.mod b/go.mod index 6041fb7a07..d38501f935 100644 --- a/go.mod +++ b/go.mod @@ -20,11 +20,11 @@ require ( github.com/cosmos/ibc-go/v7 v7.1.0 github.com/davecgh/go-spew v1.1.1 github.com/ethereum/go-ethereum v1.10.26 + github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.3 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 - github.com/holiman/uint256 v1.2.1 github.com/improbable-eng/grpc-web v0.15.0 github.com/onsi/ginkgo/v2 v2.7.0 github.com/onsi/gomega v1.26.0 @@ -113,7 +113,6 @@ require ( github.com/go-stack/stack v1.8.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/googleapis v1.4.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect @@ -139,6 +138,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/holiman/uint256 v1.2.1 // indirect github.com/huandu/skiplist v1.2.0 // indirect github.com/huin/goupnp v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index bd2a507bc7..219e921645 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -595,7 +595,7 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() { ) suite.SignTx(tx) - before := k.GetBalance(suite.ctx, suite.from) + before := k.GetEVMDenomBalance(suite.ctx, suite.from) evmParams := suite.app.EvmKeeper.GetParams(suite.ctx) ethCfg := evmParams.GetChainConfig().EthereumConfig(nil) @@ -615,7 +615,7 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() { suite.Require().Equal(tc.expErr, res.VmError) suite.Require().Empty(res.Logs) - after := k.GetBalance(suite.ctx, suite.from) + after := k.GetEVMDenomBalance(suite.ctx, suite.from) if tc.expErr == "out of gas" { suite.Require().Equal(tc.gasLimit, res.GasUsed) diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index 4e618d2eae..c25c874c19 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -66,9 +66,10 @@ func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ ctx := sdk.UnwrapSDKContext(c) acct := k.GetAccountOrEmpty(ctx, addr) + balance := k.GetEVMDenomBalance(ctx, addr) return &types.QueryAccountResponse{ - Balance: acct.Balance.String(), + Balance: balance.String(), CodeHash: common.BytesToHash(acct.CodeHash).Hex(), Nonce: acct.Nonce, }, nil @@ -153,7 +154,7 @@ func (k Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*typ ctx := sdk.UnwrapSDKContext(c) - balanceInt := k.GetBalance(ctx, common.HexToAddress(req.Address)) + balanceInt := k.GetEVMDenomBalance(ctx, common.HexToAddress(req.Address)) return &types.QueryBalanceResponse{ Balance: balanceInt.String(), @@ -202,7 +203,7 @@ func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que ctx := sdk.UnwrapSDKContext(c) address := common.HexToAddress(req.Address) - acct := k.GetAccountWithoutBalance(ctx, address) + acct := k.GetAccount(ctx, address) var code []byte if acct != nil && acct.IsContract() { diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 9964b0c268..6232a01aa0 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -274,9 +274,9 @@ func (k Keeper) Tracer(ctx sdk.Context, msg core.Message, ethCfg *params.ChainCo return types.NewTracer(k.tracer, msg, ethCfg, ctx.BlockHeight()) } -// GetAccountWithoutBalance load nonce and codehash without balance, +// GetAccount load nonce and codehash without balance, // more efficient in cases where balance is not needed. -func (k *Keeper) GetAccountWithoutBalance(ctx sdk.Context, addr common.Address) *statedb.Account { +func (k *Keeper) GetAccount(ctx sdk.Context, addr common.Address) *statedb.Account { cosmosAddr := sdk.AccAddress(addr.Bytes()) acct := k.accountKeeper.GetAccount(ctx, cosmosAddr) if acct == nil { @@ -304,7 +304,6 @@ func (k *Keeper) GetAccountOrEmpty(ctx sdk.Context, addr common.Address) statedb // empty account return statedb.Account{ - Balance: new(big.Int), CodeHash: types.EmptyCodeHash, } } @@ -320,8 +319,8 @@ func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 { return acct.GetSequence() } -// GetBalance load account's balance of gas token -func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int { +// GetEVMDenomBalance returns the balance of evm denom +func (k *Keeper) GetEVMDenomBalance(ctx sdk.Context, addr common.Address) *big.Int { cosmosAddr := sdk.AccAddress(addr.Bytes()) evmParams := k.GetParams(ctx) evmDenom := evmParams.GetEvmDenom() @@ -329,8 +328,12 @@ func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int { if evmDenom == "" { return big.NewInt(-1) } - coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, evmDenom) - return coin.Amount.BigInt() + return k.GetBalance(ctx, cosmosAddr, evmDenom) +} + +// GetBalance load account's balance of specified denom +func (k *Keeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) *big.Int { + return k.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt() } // GetBaseFee returns current base fee, return values: diff --git a/x/evm/keeper/keeper_test.go b/x/evm/keeper/keeper_test.go index 6478c7d8b3..b7297d8005 100644 --- a/x/evm/keeper/keeper_test.go +++ b/x/evm/keeper/keeper_test.go @@ -499,7 +499,6 @@ func (suite *KeeperTestSuite) TestGetAccountStorage() { func (suite *KeeperTestSuite) TestGetAccountOrEmpty() { empty := statedb.Account{ - Balance: new(big.Int), CodeHash: types.EmptyCodeHash, } diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 8340734dbd..9d851291b6 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -348,7 +348,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, return nil, errorsmod.Wrap(types.ErrCallDisabled, "failed to call contract") } - stateDB := statedb.New(ctx, k, txConfig) + stateDB := statedb.NewWithParams(ctx, k, txConfig, cfg.Params) evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, k.customContracts) leftoverGas := msg.Gas() // Allow the tracer captures the tx level events, mainly the gas consumption. diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index f4fb742a8e..b0dbac54e4 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -19,8 +19,6 @@ import ( "fmt" "math/big" - sdkmath "cosmossdk.io/math" - errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -36,17 +34,6 @@ var _ statedb.Keeper = &Keeper{} // StateDB Keeper implementation // ---------------------------------------------------------------------------- -// GetAccount returns nil if account is not exist, returns error if it's not `EthAccountI` -func (k *Keeper) GetAccount(ctx sdk.Context, addr common.Address) *statedb.Account { - acct := k.GetAccountWithoutBalance(ctx, addr) - if acct == nil { - return nil - } - - acct.Balance = k.GetBalance(ctx, addr) - return acct -} - // GetState loads contract state from database, implements `statedb.Keeper` interface. func (k *Keeper) GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr)) @@ -84,37 +71,36 @@ func (k *Keeper) ForEachStorage(ctx sdk.Context, addr common.Address, cb func(ke } } -// SetBalance update account's balance, compare with current balance first, then decide to mint or burn. +func (k *Keeper) AddBalance(ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error { + if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { + return err + } + return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins) +} + +func (k *Keeper) SubBalance(ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error { + if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, coins); err != nil { + return err + } + return k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins) +} + +// SetBalance reset the account's balance, mainly used by unit tests func (k *Keeper) SetBalance(ctx sdk.Context, addr common.Address, amount *big.Int) error { + evmDenom := k.GetParams(ctx).EvmDenom cosmosAddr := sdk.AccAddress(addr.Bytes()) - - params := k.GetParams(ctx) - coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom) - balance := coin.Amount.BigInt() + balance := k.GetBalance(ctx, cosmosAddr, evmDenom) delta := new(big.Int).Sub(amount, balance) switch delta.Sign() { case 1: - // mint - coins := sdk.NewCoins(sdk.NewCoin(params.EvmDenom, sdkmath.NewIntFromBigInt(delta))) - if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil { - return err - } - if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, cosmosAddr, coins); err != nil { - return err - } + coins := sdk.NewCoins(sdk.NewCoin(evmDenom, sdk.NewIntFromBigInt(delta))) + return k.AddBalance(ctx, cosmosAddr, coins) case -1: - // burn - coins := sdk.NewCoins(sdk.NewCoin(params.EvmDenom, sdkmath.NewIntFromBigInt(new(big.Int).Neg(delta)))) - if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, types.ModuleName, coins); err != nil { - return err - } - if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins); err != nil { - return err - } + coins := sdk.NewCoins(sdk.NewCoin(evmDenom, sdk.NewIntFromBigInt(new(big.Int).Abs(delta)))) + return k.SubBalance(ctx, cosmosAddr, coins) default: - // not changed + return nil } - return nil } // SetAccount updates nonce/balance/codeHash together. @@ -140,16 +126,11 @@ func (k *Keeper) SetAccount(ctx sdk.Context, addr common.Address, account stated k.accountKeeper.SetAccount(ctx, acct) - if err := k.SetBalance(ctx, addr, account.Balance); err != nil { - return err - } - k.Logger(ctx).Debug( "account updated", "ethereum-address", addr.Hex(), "nonce", account.Nonce, "codeHash", codeHash.Hex(), - "balance", account.Balance, ) return nil } @@ -190,10 +171,11 @@ func (k *Keeper) SetCode(ctx sdk.Context, codeHash, code []byte) { } // DeleteAccount handles contract's suicide call: -// - clear balance // - remove code // - remove states // - remove auth account +// +// NOTE: balance should be cleared separately func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error { cosmosAddr := sdk.AccAddress(addr.Bytes()) acct := k.accountKeeper.GetAccount(ctx, cosmosAddr) @@ -207,11 +189,6 @@ func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error { return errorsmod.Wrapf(types.ErrInvalidAccount, "type %T, address %s", acct, addr) } - // clear balance - if err := k.SetBalance(ctx, addr, new(big.Int)); err != nil { - return err - } - // clear storage k.ForEachStorage(ctx, addr, func(key, _ common.Hash) bool { k.SetState(ctx, addr, key, nil) diff --git a/x/evm/keeper/statedb_test.go b/x/evm/keeper/statedb_test.go index 9b4fbac52f..24390a0d26 100644 --- a/x/evm/keeper/statedb_test.go +++ b/x/evm/keeper/statedb_test.go @@ -81,7 +81,7 @@ func (suite *KeeperTestSuite) TestAddBalance() { { "negative amount", big.NewInt(-1), - false, // seems to be consistent with go-ethereum's implementation + true, }, } @@ -112,7 +112,7 @@ func (suite *KeeperTestSuite) TestSubBalance() { "positive amount, below zero", big.NewInt(100), func(vm.StateDB) {}, - false, + true, }, { "positive amount, above zero", @@ -132,7 +132,7 @@ func (suite *KeeperTestSuite) TestSubBalance() { "negative amount", big.NewInt(-1), func(vm.StateDB) {}, - false, + true, }, } @@ -891,7 +891,7 @@ func (suite *KeeperTestSuite) TestSetBalance() { if tc.expErr { suite.Require().Error(err) } else { - balance := suite.app.EvmKeeper.GetBalance(suite.ctx, tc.addr) + balance := suite.app.EvmKeeper.GetEVMDenomBalance(suite.ctx, tc.addr) suite.Require().NoError(err) suite.Require().Equal(amount, balance) } @@ -933,7 +933,7 @@ func (suite *KeeperTestSuite) TestDeleteAccount() { suite.Require().Error(err) } else { suite.Require().NoError(err) - balance := suite.app.EvmKeeper.GetBalance(suite.ctx, tc.addr) + balance := suite.app.EvmKeeper.GetEVMDenomBalance(suite.ctx, tc.addr) suite.Require().Equal(new(big.Int), balance) } }) diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go index f14e119f29..5de7a342d9 100644 --- a/x/evm/keeper/utils_test.go +++ b/x/evm/keeper/utils_test.go @@ -237,9 +237,9 @@ func (suite *KeeperTestSuite) TestCheckSenderBalance() { txData, _ := evmtypes.UnpackTxData(tx.Data) - acct := suite.app.EvmKeeper.GetAccountOrEmpty(suite.ctx, suite.address) + balance := suite.app.EvmKeeper.GetEVMDenomBalance(suite.ctx, suite.address) err := keeper.CheckSenderBalance( - sdkmath.NewIntFromBigInt(acct.Balance), + sdkmath.NewIntFromBigInt(balance), txData, ) diff --git a/x/evm/simulation/operations.go b/x/evm/simulation/operations.go index 7087071b8e..e8106c57b7 100644 --- a/x/evm/simulation/operations.go +++ b/x/evm/simulation/operations.go @@ -256,7 +256,7 @@ func EstimateGas(ctx *simulateContext, from, to *common.Address, data *hexutil.B // RandomTransferableAmount generates a random valid transferable amount. // Transferable amount is between the range [0, spendable), spendable = balance - gasFeeCap * GasLimit. func RandomTransferableAmount(ctx *simulateContext, address common.Address, estimateGas uint64, gasFeeCap *big.Int) (amount *big.Int, err error) { - balance := ctx.keeper.GetBalance(ctx.context, address) + balance := ctx.keeper.GetEVMDenomBalance(ctx.context, address) feeLimit := new(big.Int).Mul(gasFeeCap, big.NewInt(int64(estimateGas))) if (feeLimit.Cmp(balance)) > 0 { return nil, ErrNoEnoughBalance diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go index c481a963fc..b27872c004 100644 --- a/x/evm/statedb/interfaces.go +++ b/x/evm/statedb/interfaces.go @@ -16,15 +16,23 @@ package statedb import ( + "math/big" + storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/evmos/ethermint/x/evm/types" ) // Keeper provide underlying storage of StateDB type Keeper interface { // for cache store wrapping StoreKeys() map[string]storetypes.StoreKey + GetParams(sdk.Context) evmtypes.Params + + AddBalance(ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error + SubBalance(ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) error + GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) *big.Int // Read methods GetAccount(ctx sdk.Context, addr common.Address) *Account diff --git a/x/evm/statedb/journal.go b/x/evm/statedb/journal.go index 49f61c0040..cf5fe1c639 100644 --- a/x/evm/statedb/journal.go +++ b/x/evm/statedb/journal.go @@ -18,7 +18,6 @@ package statedb import ( "bytes" - "math/big" "sort" "github.com/ethereum/go-ethereum/common" @@ -102,16 +101,10 @@ type ( prev *stateObject } suicideChange struct { - account *common.Address - prev bool // whether account had already suicided - prevbalance *big.Int - } - - // Changes to individual accounts. - balanceChange struct { account *common.Address - prev *big.Int + prev bool // whether account had already suicided } + nonceChange struct { account *common.Address prev uint64 @@ -161,7 +154,6 @@ func (ch suicideChange) Revert(s *StateDB) { obj := s.getStateObject(*ch.account) if obj != nil { obj.suicided = ch.prev - obj.setBalance(ch.prevbalance) } } @@ -169,14 +161,6 @@ func (ch suicideChange) Dirtied() *common.Address { return ch.account } -func (ch balanceChange) Revert(s *StateDB) { - s.getStateObject(*ch.account).setBalance(ch.prev) -} - -func (ch balanceChange) Dirtied() *common.Address { - return ch.account -} - func (ch nonceChange) Revert(s *StateDB) { s.getStateObject(*ch.account).setNonce(ch.prev) } diff --git a/x/evm/statedb/mock_test.go b/x/evm/statedb/mock_test.go deleted file mode 100644 index b051011bd3..0000000000 --- a/x/evm/statedb/mock_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package statedb_test - -import ( - "bytes" - "errors" - "math/big" - - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/evmos/ethermint/x/evm/statedb" -) - -var ( - _ statedb.Keeper = &MockKeeper{} - errAddress common.Address = common.BigToAddress(big.NewInt(100)) - emptyCodeHash = crypto.Keccak256(nil) -) - -type MockAcount struct { - account statedb.Account - states statedb.Storage -} - -type MockKeeper struct { - accounts map[common.Address]MockAcount - codes map[common.Hash][]byte - keys map[string]storetypes.StoreKey - eventConverter statedb.EventConverter -} - -func NewMockKeeperWith(keys map[string]storetypes.StoreKey, eventConverter statedb.EventConverter) *MockKeeper { - return &MockKeeper{ - accounts: make(map[common.Address]MockAcount), - codes: make(map[common.Hash][]byte), - keys: keys, - eventConverter: eventConverter, - } -} - -func NewMockKeeper() *MockKeeper { - return NewMockKeeperWith(nil, nil) -} - -func (k MockKeeper) StoreKeys() map[string]storetypes.StoreKey { - return k.keys -} - -func (k MockKeeper) EventConverter() statedb.EventConverter { - return k.eventConverter -} - -func (k MockKeeper) GetAccount(ctx sdk.Context, addr common.Address) *statedb.Account { - acct, ok := k.accounts[addr] - if !ok { - return nil - } - return &acct.account -} - -func (k MockKeeper) GetState(ctx sdk.Context, addr common.Address, key common.Hash) common.Hash { - return k.accounts[addr].states[key] -} - -func (k MockKeeper) GetCode(ctx sdk.Context, codeHash common.Hash) []byte { - return k.codes[codeHash] -} - -func (k MockKeeper) ForEachStorage(ctx sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) { - if acct, ok := k.accounts[addr]; ok { - for k, v := range acct.states { - if !cb(k, v) { - return - } - } - } -} - -func (k MockKeeper) SetAccount(ctx sdk.Context, addr common.Address, account statedb.Account) error { - if addr == errAddress { - return errors.New("mock db error") - } - acct, exists := k.accounts[addr] - if exists { - // update - acct.account = account - k.accounts[addr] = acct - } else { - k.accounts[addr] = MockAcount{account: account, states: make(statedb.Storage)} - } - return nil -} - -func (k MockKeeper) SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte) { - if acct, ok := k.accounts[addr]; ok { - if len(value) == 0 { - delete(acct.states, key) - } else { - acct.states[key] = common.BytesToHash(value) - } - } -} - -func (k MockKeeper) SetCode(ctx sdk.Context, codeHash []byte, code []byte) { - k.codes[common.BytesToHash(codeHash)] = code -} - -func (k MockKeeper) DeleteAccount(ctx sdk.Context, addr common.Address) error { - if addr == errAddress { - return errors.New("mock db error") - } - old := k.accounts[addr] - delete(k.accounts, addr) - if !bytes.Equal(old.account.CodeHash, emptyCodeHash) { - delete(k.codes, common.BytesToHash(old.account.CodeHash)) - } - return nil -} - -func (k MockKeeper) Clone() *MockKeeper { - accounts := make(map[common.Address]MockAcount, len(k.accounts)) - for k, v := range k.accounts { - accounts[k] = v - } - codes := make(map[common.Hash][]byte, len(k.codes)) - for k, v := range k.codes { - codes[k] = v - } - keys := make(map[string]storetypes.StoreKey, len(k.keys)) - for k, v := range k.keys { - keys[k] = v - } - return &MockKeeper{accounts, codes, keys, k.eventConverter} -} diff --git a/x/evm/statedb/state_object.go b/x/evm/statedb/state_object.go index dd344dfadb..584f0d3402 100644 --- a/x/evm/statedb/state_object.go +++ b/x/evm/statedb/state_object.go @@ -17,7 +17,6 @@ package statedb import ( "bytes" - "math/big" "sort" "github.com/ethereum/go-ethereum/common" @@ -30,14 +29,12 @@ var emptyCodeHash = crypto.Keccak256(nil) // These objects are stored in the storage of auth module. type Account struct { Nonce uint64 - Balance *big.Int CodeHash []byte } // NewEmptyAccount returns an empty account. func NewEmptyAccount() *Account { return &Account{ - Balance: new(big.Int), CodeHash: emptyCodeHash, } } @@ -84,9 +81,6 @@ type stateObject struct { // newObject creates a state object. func newObject(db *StateDB, address common.Address, account Account) *stateObject { - if account.Balance == nil { - account.Balance = new(big.Int) - } if account.CodeHash == nil { account.CodeHash = emptyCodeHash } @@ -101,44 +95,13 @@ func newObject(db *StateDB, address common.Address, account Account) *stateObjec // empty returns whether the account is considered empty. func (s *stateObject) empty() bool { - return s.account.Nonce == 0 && s.account.Balance.Sign() == 0 && bytes.Equal(s.account.CodeHash, emptyCodeHash) + return s.account.Nonce == 0 && bytes.Equal(s.account.CodeHash, emptyCodeHash) } func (s *stateObject) markSuicided() { s.suicided = true } -// AddBalance adds amount to s's balance. -// It is used to add funds to the destination account of a transfer. -func (s *stateObject) AddBalance(amount *big.Int) { - if amount.Sign() == 0 { - return - } - s.SetBalance(new(big.Int).Add(s.Balance(), amount)) -} - -// SubBalance removes amount from s's balance. -// It is used to remove funds from the origin account of a transfer. -func (s *stateObject) SubBalance(amount *big.Int) { - if amount.Sign() == 0 { - return - } - s.SetBalance(new(big.Int).Sub(s.Balance(), amount)) -} - -// SetBalance update account balance. -func (s *stateObject) SetBalance(amount *big.Int) { - s.db.journal.append(balanceChange{ - account: &s.address, - prev: new(big.Int).Set(s.account.Balance), - }) - s.setBalance(amount) -} - -func (s *stateObject) setBalance(amount *big.Int) { - s.account.Balance = amount -} - // // Attribute accessors // @@ -202,11 +165,6 @@ func (s *stateObject) CodeHash() []byte { return s.account.CodeHash } -// Balance returns the balance of account -func (s *stateObject) Balance() *big.Int { - return s.account.Balance -} - // Nonce returns the nonce of account func (s *stateObject) Nonce() uint64 { return s.account.Nonce diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index b71377d637..f33f57a973 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -26,6 +26,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + evmtypes "github.com/evmos/ethermint/x/evm/types" "github.com/evmos/ethermint/store/cachemulti" ) @@ -73,10 +74,18 @@ type StateDB struct { // events emitted by native action nativeEvents sdk.Events + + // handle balances natively + evmDenom string + err error } // New creates a new state from a given trie. func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { + return NewWithParams(ctx, keeper, txConfig, keeper.GetParams(ctx)) +} + +func NewWithParams(ctx sdk.Context, keeper Keeper, txConfig TxConfig, params evmtypes.Params) *StateDB { cacheCtx := ctx.WithMultiStore(cachemulti.NewStore(ctx.MultiStore(), keeper.StoreKeys())) return &StateDB{ keeper: keeper, @@ -89,6 +98,7 @@ func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { txConfig: txConfig, nativeEvents: sdk.Events{}, + evmDenom: params.EvmDenom, } } @@ -148,16 +158,15 @@ func (s *StateDB) Exist(addr common.Address) bool { // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *StateDB) Empty(addr common.Address) bool { so := s.getStateObject(addr) - return so == nil || so.empty() + if so == nil { + return true + } + return so.empty() && s.GetBalance(addr).Sign() == 0 } // GetBalance retrieves the balance from the given address or 0 if object not found func (s *StateDB) GetBalance(addr common.Address) *big.Int { - stateObject := s.getStateObject(addr) - if stateObject != nil { - return stateObject.Balance() - } - return common.Big0 + return s.keeper.GetBalance(s.cacheCtx, sdk.AccAddress(addr.Bytes()), s.evmDenom) } // GetNonce returns the nonce of account, 0 if not exists. @@ -257,27 +266,24 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { stateObject := s.getStateObject(addr) if stateObject == nil { - stateObject, _ = s.createObject(addr) + stateObject = s.createObject(addr) } return stateObject } // createObject creates a new state object. If there is an existing account with // the given address, it is overwritten and returned as the second return value. -func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { - prev = s.getStateObject(addr) +func (s *StateDB) createObject(addr common.Address) *stateObject { + prev := s.getStateObject(addr) - newobj = newObject(s, addr, Account{}) + newobj := newObject(s, addr, Account{}) if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { s.journal.append(resetObjectChange{prev: prev}) } s.setStateObject(newobj) - if prev != nil { - return newobj, prev - } - return newobj, nil + return newobj } // CreateAccount explicitly creates a state object. If a state object with the address @@ -291,10 +297,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // // Carrying over the balance ensures that Ether doesn't disappear. func (s *StateDB) CreateAccount(addr common.Address) { - newObj, prev := s.createObject(addr) - if prev != nil { - newObj.setBalance(prev.account.Balance) - } + s.createObject(addr) } // ForEachStorage iterate the contract storage, the iteration order is not defined. @@ -319,6 +322,10 @@ func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } +func (s *StateDB) cloneNativeState() sdk.MultiStore { + return s.CacheMultiStore().Clone() +} + func (s *StateDB) restoreNativeState(ms sdk.MultiStore) { manager := sdk.NewEventManager() s.cacheCtx = s.cacheCtx.WithMultiStore(ms).WithEventManager(manager) @@ -328,7 +335,7 @@ func (s *StateDB) restoreNativeState(ms sdk.MultiStore) { // the writes will be revert when either the native action itself fail // or the wrapping message call reverted. func (s *StateDB) ExecuteNativeAction(contract common.Address, converter EventConverter, action func(ctx sdk.Context) error) error { - snapshot := s.CacheMultiStore().Clone() + snapshot := s.cloneNativeState() eventManager := sdk.NewEventManager() if err := action(s.cacheCtx.WithEventManager(eventManager)); err != nil { @@ -349,17 +356,27 @@ func (s *StateDB) ExecuteNativeAction(contract common.Address, converter EventCo // AddBalance adds amount to the account associated with addr. func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.AddBalance(amount) + if amount.Sign() <= 0 { + return + } + coins := sdk.Coins{sdk.NewCoin(s.evmDenom, sdk.NewIntFromBigInt(amount))} + if err := s.ExecuteNativeAction(common.Address{}, nil, func(ctx sdk.Context) error { + return s.keeper.AddBalance(ctx, sdk.AccAddress(addr.Bytes()), coins) + }); err != nil { + s.err = err } } // SubBalance subtracts amount from the account associated with addr. func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { - stateObject := s.getOrNewStateObject(addr) - if stateObject != nil { - stateObject.SubBalance(amount) + if amount.Sign() <= 0 { + return + } + coins := sdk.Coins{sdk.NewCoin(s.evmDenom, sdk.NewIntFromBigInt(amount))} + if err := s.ExecuteNativeAction(common.Address{}, nil, func(ctx sdk.Context) error { + return s.keeper.SubBalance(ctx, sdk.AccAddress(addr.Bytes()), coins) + }); err != nil { + s.err = err } } @@ -398,12 +415,16 @@ func (s *StateDB) Suicide(addr common.Address) bool { return false } s.journal.append(suicideChange{ - account: &addr, - prev: stateObject.suicided, - prevbalance: new(big.Int).Set(stateObject.Balance()), + account: &addr, + prev: stateObject.suicided, }) stateObject.markSuicided() - stateObject.account.Balance = new(big.Int) + + // clear balance + balance := s.GetBalance(addr) + if balance.Sign() > 0 { + s.SubBalance(addr, balance) + } return true } @@ -496,6 +517,11 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. func (s *StateDB) Commit() error { + // if there's any errors during the execution, abort + if s.err != nil { + return s.err + } + // commit the native cache store first, // the states managed by precompiles and the other part of StateDB must not overlap. s.CacheMultiStore().Write() diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go index 8cbf995bed..d6d21f2b73 100644 --- a/x/evm/statedb/statedb_test.go +++ b/x/evm/statedb/statedb_test.go @@ -11,15 +11,28 @@ import ( "github.com/cosmos/cosmos-sdk/store/rootmulti" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/evmos/ethermint/app" + "github.com/evmos/ethermint/encoding" + ethermint "github.com/evmos/ethermint/types" + evmkeeper "github.com/evmos/ethermint/x/evm/keeper" "github.com/evmos/ethermint/x/evm/statedb" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) var ( + emptyCodeHash = crypto.Keccak256(nil) address common.Address = common.BigToAddress(big.NewInt(101)) address2 common.Address = common.BigToAddress(big.NewInt(102)) address3 common.Address = common.BigToAddress(big.NewInt(103)) @@ -40,9 +53,9 @@ func (suite *StateDBTestSuite) TestAccount() { txConfig.TxHash = common.BigToHash(big.NewInt(100)) testCases := []struct { name string - malleate func(*statedb.StateDB) + malleate func(*statedb.StateDB, sdk.MultiStore) }{ - {"non-exist account", func(db *statedb.StateDB) { + {"non-exist account", func(db *statedb.StateDB, cms sdk.MultiStore) { suite.Require().Equal(false, db.Exist(address)) suite.Require().Equal(true, db.Empty(address)) suite.Require().Equal(big.NewInt(0), db.GetBalance(address)) @@ -50,17 +63,16 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().Equal(common.Hash{}, db.GetCodeHash(address)) suite.Require().Equal(uint64(0), db.GetNonce(address)) }}, - {"empty account", func(db *statedb.StateDB) { + {"empty account", func(db *statedb.StateDB, cms sdk.MultiStore) { db.CreateAccount(address) suite.Require().NoError(db.Commit()) - keeper := db.Keeper().(*MockKeeper) - acct := keeper.accounts[address] - suite.Require().Equal(statedb.NewEmptyAccount(), &acct.account) - suite.Require().Empty(acct.states) - suite.Require().False(acct.account.IsContract()) + ctx, keeper := newTestKeeper(suite.T(), cms) + acct := keeper.GetAccount(ctx, address) + suite.Require().Equal(statedb.NewEmptyAccount(), acct) + suite.Require().False(acct.IsContract()) - db = statedb.New(sdk.Context{}, keeper, txConfig) + db = statedb.New(ctx, keeper, txConfig) suite.Require().Equal(true, db.Exist(address)) suite.Require().Equal(true, db.Empty(address)) suite.Require().Equal(big.NewInt(0), db.GetBalance(address)) @@ -68,7 +80,7 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().Equal(common.BytesToHash(emptyCodeHash), db.GetCodeHash(address)) suite.Require().Equal(uint64(0), db.GetNonce(address)) }}, - {"suicide", func(db *statedb.StateDB) { + {"suicide", func(db *statedb.StateDB, cms sdk.MultiStore) { // non-exist account. suite.Require().False(db.Suicide(address)) suite.Require().False(db.HasSuicided(address)) @@ -79,10 +91,15 @@ func (suite *StateDBTestSuite) TestAccount() { db.AddBalance(address, big.NewInt(100)) db.SetState(address, key1, value1) db.SetState(address, key2, value2) + codeHash := db.GetCodeHash(address) suite.Require().NoError(db.Commit()) + ctx, keeper := newTestKeeper(suite.T(), cms) + + suite.Require().NotEmpty(keeper.GetCode(ctx, codeHash)) + // suicide - db = statedb.New(sdk.Context{}, db.Keeper(), txConfig) + db = statedb.New(ctx, keeper, txConfig) suite.Require().False(db.HasSuicided(address)) suite.Require().True(db.Suicide(address)) @@ -96,28 +113,30 @@ func (suite *StateDBTestSuite) TestAccount() { suite.Require().NoError(db.Commit()) + ctx, keeper = newTestKeeper(suite.T(), cms) + // not accessible from StateDB anymore - db = statedb.New(sdk.Context{}, db.Keeper(), txConfig) + db = statedb.New(ctx, keeper, txConfig) suite.Require().False(db.Exist(address)) // and cleared in keeper too - keeper := db.Keeper().(*MockKeeper) - suite.Require().Empty(keeper.accounts) - suite.Require().Empty(keeper.codes) + suite.Require().Nil(keeper.GetAccount(ctx, address)) + // code is not deleted when contract suicided. + suite.Require().NotEmpty(keeper.GetCode(ctx, codeHash)) }}, } for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, txConfig) - tc.malleate(db) + raw, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, txConfig) + tc.malleate(db, raw) }) } } func (suite *StateDBTestSuite) TestAccountOverride() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + _, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) // test balance carry over when overwritten amount := big.NewInt(1) @@ -139,16 +158,13 @@ func (suite *StateDBTestSuite) TestDBError() { name string malleate func(vm.StateDB) }{ - {"set account", func(db vm.StateDB) { - db.SetNonce(errAddress, 1) - }}, - {"delete account", func(db vm.StateDB) { - db.SetNonce(errAddress, 1) - suite.Require().True(db.Suicide(errAddress)) + {"negative balance", func(db vm.StateDB) { + db.SubBalance(address, big.NewInt(10)) }}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + _, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) tc.malleate(db) suite.Require().Error(db.Commit()) } @@ -180,15 +196,17 @@ func (suite *StateDBTestSuite) TestBalance() { for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + raw, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) tc.malleate(db) // check dirty state suite.Require().Equal(tc.expBalance, db.GetBalance(address)) suite.Require().NoError(db.Commit()) + + ctx, keeper = newTestKeeper(suite.T(), raw) // check committed balance too - suite.Require().Equal(tc.expBalance, keeper.accounts[address].account.Balance) + suite.Require().Equal(tc.expBalance, keeper.GetEVMDenomBalance(ctx, address)) }) } } @@ -232,17 +250,20 @@ func (suite *StateDBTestSuite) TestState() { for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + raw, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) tc.malleate(db) suite.Require().NoError(db.Commit()) // check committed states in keeper - suite.Require().Equal(tc.expStates, keeper.accounts[address].states) + ctx, keeper = newTestKeeper(suite.T(), raw) + for k, v := range tc.expStates { + suite.Require().Equal(v, keeper.GetState(ctx, address, k)) + } // check ForEachStorage - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) - collected := CollectContractStorage(db) + db = statedb.New(ctx, keeper, emptyTxConfig) + collected := CollectContractStorage(db, address) if len(tc.expStates) > 0 { suite.Require().Equal(tc.expStates, collected) } else { @@ -273,8 +294,8 @@ func (suite *StateDBTestSuite) TestCode() { for _, tc := range testCases { suite.Run(tc.name, func() { - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + raw, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) tc.malleate(db) // check dirty state @@ -284,8 +305,9 @@ func (suite *StateDBTestSuite) TestCode() { suite.Require().NoError(db.Commit()) - // check again - db = statedb.New(sdk.Context{}, keeper, emptyTxConfig) + // check the committed state + ctx, keeper = newTestKeeper(suite.T(), raw) + db = statedb.New(ctx, keeper, emptyTxConfig) suite.Require().Equal(tc.expCode, db.GetCode(address)) suite.Require().Equal(len(tc.expCode), db.GetCodeSize(address)) suite.Require().Equal(tc.expCodeHash, db.GetCodeHash(address)) @@ -338,8 +360,8 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { } for _, tc := range testCases { suite.Run(tc.name, func() { - ctx := sdk.Context{}.WithEventManager(sdk.NewEventManager()) - keeper := NewMockKeeper() + raw, ctx, keeper := setupTestEnv(suite.T()) + ctx = ctx.WithEventManager(sdk.NewEventManager()) { // do some arbitrary changes to the storage @@ -352,7 +374,8 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { suite.Require().NoError(db.Commit()) } - originalKeeper := keeper.Clone() + originalState := cloneRawState(suite.T(), raw) + ctx, keeper = newTestKeeper(suite.T(), raw) // run test db := statedb.New(ctx, keeper, emptyTxConfig) @@ -366,9 +389,9 @@ func (suite *StateDBTestSuite) TestRevertSnapshot() { suite.Require().NoError(db.Commit()) - // check keeper should stay the same - suite.Require().Equal(originalKeeper.accounts, keeper.accounts) - suite.Require().Equal(originalKeeper.codes, keeper.codes) + newState := cloneRawState(suite.T(), raw) + // check the committed state stays the same + suite.Require().Equal(originalState, newState) }) } } @@ -378,7 +401,8 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { value1 := common.BigToHash(big.NewInt(1)) value2 := common.BigToHash(big.NewInt(2)) - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + _, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) rev1 := db.Snapshot() db.SetState(address, key, value1) @@ -395,7 +419,8 @@ func (suite *StateDBTestSuite) TestNestedSnapshot() { } func (suite *StateDBTestSuite) TestInvalidSnapshotId() { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + _, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) suite.Require().Panics(func() { db.RevertToSnapshot(1) }) @@ -467,7 +492,8 @@ func (suite *StateDBTestSuite) TestAccessList() { } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + _, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) tc.malleate(db) } } @@ -480,7 +506,8 @@ func (suite *StateDBTestSuite) TestLog() { txHash, 1, 1, ) - db := statedb.New(sdk.Context{}, NewMockKeeper(), txConfig) + _, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, txConfig) data := []byte("hello world") db.AddLog(ðtypes.Log{ Address: address, @@ -530,7 +557,8 @@ func (suite *StateDBTestSuite) TestRefund() { }, 0, true}, } for _, tc := range testCases { - db := statedb.New(sdk.Context{}, NewMockKeeper(), emptyTxConfig) + _, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) if !tc.expPanic { tc.malleate(db) suite.Require().Equal(tc.expRefund, db.GetRefund()) @@ -548,19 +576,23 @@ func (suite *StateDBTestSuite) TestIterateStorage() { key2 := common.BigToHash(big.NewInt(3)) value2 := common.BigToHash(big.NewInt(4)) - keeper := NewMockKeeper() - db := statedb.New(sdk.Context{}, keeper, emptyTxConfig) + raw, ctx, keeper := setupTestEnv(suite.T()) + db := statedb.New(ctx, keeper, emptyTxConfig) db.SetState(address, key1, value1) db.SetState(address, key2, value2) // ForEachStorage only iterate committed state - suite.Require().Empty(CollectContractStorage(db)) + suite.Require().Empty(CollectContractStorage(db, address)) suite.Require().NoError(db.Commit()) - storage := CollectContractStorage(db) + storage := CollectContractStorage(db, address) suite.Require().Equal(2, len(storage)) - suite.Require().Equal(keeper.accounts[address].states, storage) + + ctx, keeper = newTestKeeper(suite.T(), raw) + for k, v := range storage { + suite.Require().Equal(v, keeper.GetState(ctx, address, k)) + } // break early iteration storage = make(statedb.Storage) @@ -573,16 +605,9 @@ func (suite *StateDBTestSuite) TestIterateStorage() { } func (suite *StateDBTestSuite) TestNativeAction() { - db := dbm.NewMemDB() - ms := rootmulti.NewStore(db, log.NewNopLogger()) - keys := map[string]storetypes.StoreKey{ - "storekey": storetypes.NewKVStoreKey("storekey"), - "mem": storetypes.NewMemoryStoreKey("mem"), - } - ms.MountStoreWithDB(keys["storekey"], storetypes.StoreTypeIAVL, nil) - ms.MountStoreWithDB(keys["mem"], storetypes.StoreTypeMemory, nil) - suite.Require().NoError(ms.LoadLatestVersion()) - ctx := sdk.NewContext(ms, tmproto.Header{}, false, log.NewNopLogger()) + _, ctx, keeper := setupTestEnv(suite.T()) + storeKey := testStoreKeys["testnative"] + transientKey := testTransientKeys[evmtypes.TransientKey] eventConverter := func(event sdk.Event) (*ethtypes.Log, error) { converters := map[string]statedb.EventConverter{ @@ -600,26 +625,25 @@ func (suite *StateDBTestSuite) TestNativeAction() { return converter(event) } - keeper := NewMockKeeperWith(keys, eventConverter) stateDB := statedb.New(ctx, keeper, emptyTxConfig) contract := common.BigToAddress(big.NewInt(101)) stateDB.ExecuteNativeAction(contract, eventConverter, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) store.Set([]byte("success1"), []byte("value")) ctx.EventManager().EmitEvent(sdk.NewEvent("success1")) - mem := ctx.KVStore(keys["mem"]) + mem := ctx.KVStore(transientKey) mem.Set([]byte("mem"), []byte("value")) return nil }) stateDB.ExecuteNativeAction(contract, eventConverter, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) store.Set([]byte("failure1"), []byte("value")) ctx.EventManager().EmitEvent(sdk.NewEvent("failure1")) - mem := ctx.KVStore(keys["mem"]) + mem := ctx.KVStore(transientKey) suite.Require().Equal([]byte("value"), mem.Get([]byte("mem"))) return errors.New("failure") }) @@ -633,7 +657,7 @@ func (suite *StateDBTestSuite) TestNativeAction() { // test query stateDB.ExecuteNativeAction(contract, nil, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) suite.Require().Equal([]byte("value"), store.Get([]byte("success1"))) suite.Require().Nil(store.Get([]byte("failure1"))) return nil @@ -641,13 +665,13 @@ func (suite *StateDBTestSuite) TestNativeAction() { rev1 := stateDB.Snapshot() stateDB.ExecuteNativeAction(contract, eventConverter, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) store.Set([]byte("success2"), []byte("value")) ctx.EventManager().EmitEvent(sdk.NewEvent("success2")) return nil }) stateDB.ExecuteNativeAction(contract, eventConverter, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) store.Set([]byte("failure2"), []byte("value")) ctx.EventManager().EmitEvent(sdk.NewEvent("failure2")) return errors.New("failure") @@ -665,7 +689,7 @@ func (suite *StateDBTestSuite) TestNativeAction() { }}, stateDB.Logs()) // test query stateDB.ExecuteNativeAction(contract, nil, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) suite.Require().Equal([]byte("value"), store.Get([]byte("success1"))) suite.Require().Equal([]byte("value"), store.Get([]byte("success2"))) suite.Require().Nil(store.Get([]byte("failure2"))) @@ -683,7 +707,7 @@ func (suite *StateDBTestSuite) TestNativeAction() { _ = stateDB.Snapshot() stateDB.ExecuteNativeAction(contract, eventConverter, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) store.Set([]byte("success3"), []byte("value")) ctx.EventManager().EmitEvent(sdk.NewEvent("success3")) return nil @@ -694,7 +718,7 @@ func (suite *StateDBTestSuite) TestNativeAction() { // test query stateDB.ExecuteNativeAction(contract, eventConverter, func(ctx sdk.Context) error { - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) suite.Require().Equal([]byte("value"), store.Get([]byte("success1"))) suite.Require().Nil(store.Get([]byte("success2"))) suite.Require().Equal([]byte("value"), store.Get([]byte("success3"))) @@ -704,7 +728,7 @@ func (suite *StateDBTestSuite) TestNativeAction() { suite.Require().NoError(stateDB.Commit()) // query committed state - store := ctx.KVStore(keys["storekey"]) + store := ctx.KVStore(storeKey) suite.Require().Equal([]byte("value"), store.Get([]byte("success1"))) suite.Require().Nil(store.Get([]byte("success2"))) suite.Require().Equal([]byte("value"), store.Get([]byte("success3"))) @@ -715,7 +739,7 @@ func (suite *StateDBTestSuite) TestNativeAction() { suite.Require().Equal(sdk.Events{{Type: "success1"}, {Type: "success3"}}, ctx.EventManager().Events()) } -func CollectContractStorage(db vm.StateDB) statedb.Storage { +func CollectContractStorage(db vm.StateDB, address common.Address) statedb.Storage { storage := make(statedb.Storage) db.ForEachStorage(address, func(k, v common.Hash) bool { storage[k] = v @@ -724,6 +748,86 @@ func CollectContractStorage(db vm.StateDB) statedb.Storage { return storage } +var ( + testStoreKeys = sdk.NewKVStoreKeys(authtypes.StoreKey, banktypes.StoreKey, evmtypes.StoreKey, "testnative") + testTransientKeys = sdk.NewTransientStoreKeys(evmtypes.TransientKey) +) + +func cloneRawState(t *testing.T, cms sdk.MultiStore) map[string]map[string][]byte { + result := make(map[string]map[string][]byte) + + for name, key := range testStoreKeys { + store := cms.GetKVStore(key) + itr := store.Iterator(nil, nil) + defer itr.Close() + + state := make(map[string][]byte) + for ; itr.Valid(); itr.Next() { + state[string(itr.Key())] = itr.Value() + } + + result[name] = state + } + + return result +} + +func newTestKeeper(t *testing.T, cms sdk.MultiStore) (sdk.Context, *evmkeeper.Keeper) { + appCodec := encoding.MakeConfig(app.ModuleBasics).Codec + authAddr := authtypes.NewModuleAddress(govtypes.ModuleName).String() + accountKeeper := authkeeper.NewAccountKeeper( + appCodec, testStoreKeys[authtypes.StoreKey], + ethermint.ProtoAccount, + map[string][]string{ + evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + }, + sdk.GetConfig().GetBech32AccountAddrPrefix(), + authAddr, + ) + bankKeeper := bankkeeper.NewBaseKeeper( + appCodec, + testStoreKeys[banktypes.StoreKey], + accountKeeper, + map[string]bool{}, + authAddr, + ) + allKeys := make(map[string]storetypes.StoreKey, len(testStoreKeys)+len(testTransientKeys)) + for k, v := range testStoreKeys { + allKeys[k] = v + } + for k, v := range testTransientKeys { + allKeys[k] = v + } + evmKeeper := evmkeeper.NewKeeper( + appCodec, testStoreKeys[evmtypes.StoreKey], testTransientKeys[evmtypes.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), + accountKeeper, bankKeeper, nil, nil, + "", + paramstypes.Subspace{}, nil, + allKeys, + ) + + ctx := sdk.NewContext(cms, tmproto.Header{}, false, log.NewNopLogger()) + return ctx, evmKeeper +} + +func setupTestEnv(t *testing.T) (sdk.MultiStore, sdk.Context, *evmkeeper.Keeper) { + db := dbm.NewMemDB() + cms := rootmulti.NewStore(db, log.NewNopLogger()) + for _, key := range testStoreKeys { + cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, nil) + } + for _, key := range testTransientKeys { + cms.MountStoreWithDB(key, storetypes.StoreTypeTransient, nil) + } + require.NoError(t, cms.LoadLatestVersion()) + + ctx, keeper := newTestKeeper(t, cms) + require.NoError(t, keeper.SetParams(ctx, evmtypes.Params{ + EvmDenom: "uphoton", + })) + return cms, ctx, keeper +} + func TestStateDBTestSuite(t *testing.T) { suite.Run(t, &StateDBTestSuite{}) }