From c77809a00814baecf0c65b484a7d45adad402cc6 Mon Sep 17 00:00:00 2001 From: Oleg Nikonychev Date: Tue, 30 Jul 2024 00:15:34 +0400 Subject: [PATCH] feat(evm): export genesis (#1967) * testing evm genesis export * feat(evm): export genesis * chore: changelog update --------- Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> --- CHANGELOG.md | 1 + x/evm/evmmodule/genesis.go | 56 +++++++++++++- x/evm/evmmodule/genesis_test.go | 126 ++++++++++++++++++++++++++++++++ x/evm/evmtest/tx.go | 1 - x/evm/keeper/statedb.go | 22 +++--- 5 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 x/evm/evmmodule/genesis_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c7a790b..580d2fde7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1962](https://github.com/NibiruChain/nibiru/pull/1962) - chore(evm): code cleanup, unused code, typos, styles, warnings - [#1963](https://github.com/NibiruChain/nibiru/pull/1963) - feat(evm): Deduct a fee during the creation of a FunToken mapping. Implemented by `deductCreateFunTokenFee` inside of the `eth.evm.v1.MsgCreateFunToken` transaction. - [#1965](https://github.com/NibiruChain/nibiru/pull/1965) - refactor(evm): remove evm post-processing hooks +- [#1967](https://github.com/NibiruChain/nibiru/pull/1967) - feat(evm): export genesis - [#1968](https://github.com/NibiruChain/nibiru/pull/1968) - refactor(evm): funtoken events, cli commands and queries - [#1970](https://github.com/NibiruChain/nibiru/pull/1970) - refactor(evm): move evm antehandlers to separate package. Remove "gosdk/sequence_test.go", which causes a race condition in CI. - [#1971](https://github.com/NibiruChain/nibiru/pull/1971) - feat(evm): typed events for contract creation, contract execution and transfer diff --git a/x/evm/evmmodule/genesis.go b/x/evm/evmmodule/genesis.go index 32b9f0ca7..9828f0169 100644 --- a/x/evm/evmmodule/genesis.go +++ b/x/evm/evmmodule/genesis.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" + "github.com/NibiruChain/collections" abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -31,6 +32,7 @@ func InitGenesis( panic("the EVM module account has not been set") } + // Create evm contracts from genstate.Accounts for _, account := range genState.Accounts { address := gethcommon.HexToAddress(account.Address) accAddress := sdk.AccAddress(address.Bytes()) @@ -57,7 +59,6 @@ func InitGenesis( panic(fmt.Sprintf("%s account: %s , evm state codehash: %v, ethAccount codehash: %v, evm state code: %s\n", s, account.Address, codeHash, ethAcct.GetCodeHash(), account.Code)) } - k.SetCode(ctx, codeHash.Bytes(), code) for _, storage := range account.Storage { @@ -65,14 +66,61 @@ func InitGenesis( } } + // Create fungible token mappings + for _, funToken := range genState.FuntokenMappings { + err := k.FunTokens.SafeInsert( + ctx, gethcommon.HexToAddress(funToken.Erc20Addr.String()), funToken.BankDenom, funToken.IsMadeFromCoin, + ) + if err != nil { + panic(fmt.Errorf("failed creating funtoken: %w", err)) + } + } + return []abci.ValidatorUpdate{} } // ExportGenesis exports genesis state of the EVM module func ExportGenesis(ctx sdk.Context, k *keeper.Keeper, ak evm.AccountKeeper) *evm.GenesisState { - // TODO: impl ExportGenesis + var genesisAccounts []evm.GenesisAccount + + // 1. Export EVM contacts + // TODO: find the way to get eth contract addresses from the evm keeper + allAccounts := ak.GetAllAccounts(ctx) + for _, acc := range allAccounts { + ethAcct, ok := acc.(eth.EthAccountI) + if ok { + address := ethAcct.EthAddress() + codeHash := ethAcct.GetCodeHash() + code, err := k.EvmState.ContractBytecode.Get(ctx, codeHash.Bytes()) + if err != nil { + // Not a contract + continue + } + var storage evm.Storage + + k.ForEachStorage(ctx, address, func(key, value gethcommon.Hash) bool { + storage = append(storage, evm.NewStateFromEthHashes(key, value)) + return true + }) + genesisAccounts = append(genesisAccounts, evm.GenesisAccount{ + Address: address.String(), + Code: eth.BytesToHex(code), + Storage: storage, + }) + } + } + + // 2. Export Fungible tokens + var funTokens []evm.FunToken + iter := k.FunTokens.Iterate(ctx, collections.Range[[]byte]{}) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + funTokens = append(funTokens, iter.Value()) + } + return &evm.GenesisState{ - Accounts: []evm.GenesisAccount{}, - Params: evm.Params{}, + Params: k.GetParams(ctx), + Accounts: genesisAccounts, + FuntokenMappings: funTokens, } } diff --git a/x/evm/evmmodule/genesis_test.go b/x/evm/evmmodule/genesis_test.go new file mode 100644 index 000000000..182f9d2f2 --- /dev/null +++ b/x/evm/evmmodule/genesis_test.go @@ -0,0 +1,126 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package evmmodule_test + +import ( + "math/big" + "testing" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/suite" + + "github.com/NibiruChain/nibiru/eth" + "github.com/NibiruChain/nibiru/x/evm" + "github.com/NibiruChain/nibiru/x/evm/embeds" + "github.com/NibiruChain/nibiru/x/evm/evmmodule" + "github.com/NibiruChain/nibiru/x/evm/evmtest" +) + +type Suite struct { + suite.Suite +} + +// TestKeeperSuite: Runs all the tests in the suite. +func TestKeeperSuite(t *testing.T) { + s := new(Suite) + suite.Run(t, s) +} + +// TestExportInitGenesis +// - creates evm state with erc20 contract, sends tokens to user A and B +// - creates fungible token from unibi coin and sends to user C +// - exports / imports genesis +// - verifies that contracts are in place and user balances match +// - verifies that fungible token is in place and the balance is correct +func (s *Suite) TestExportInitGenesis() { + deps := evmtest.NewTestDeps() + erc20Contract := embeds.SmartContract_TestERC20.MustLoad() + fromUser := deps.Sender.EthAddr + toUserA := gethcommon.HexToAddress("0xAE8A5F44A9b55Ae6D2c9C228253E8fAfb837d2F2") + toUserB := gethcommon.HexToAddress("0xf893292542F2578F1004e62fd723901ddE5EC5Cf") + toUserC := gethcommon.HexToAddress("0xe90f75496E744b92B52535bB05a29123D0D94D49") + amountToSendA := big.NewInt(1550) + amountToSendB := big.NewInt(333) + amountToSendC := big.NewInt(228) + + // Create ERC-20 contract + deployResp, err := evmtest.DeployContract(&deps, embeds.SmartContract_TestERC20, s.T()) + s.Require().NoError(err) + erc20Addr := deployResp.ContractAddr + totalSupply, err := deps.K.ERC20().LoadERC20BigInt( + deps.Ctx, erc20Contract.ABI, erc20Addr, "totalSupply", + ) + s.Require().NoError(err) + + // Transfer ERC-20 tokens to user A + _, err = deps.K.ERC20().Transfer(erc20Addr, fromUser, toUserA, amountToSendA, deps.Ctx) + s.Require().NoError(err) + + // Transfer ERC-20 tokens to user B + _, err = deps.K.ERC20().Transfer(erc20Addr, fromUser, toUserB, amountToSendB, deps.Ctx) + s.Require().NoError(err) + + // Create fungible token from bank coin + funToken := evmtest.CreateFunTokenForBankCoin(&deps, "unibi", &s.Suite) + s.Require().NoError(err) + funTokenAddr := funToken.Erc20Addr.ToAddr() + + // Fund sender's wallet + spendableCoins := sdk.NewCoins(sdk.NewInt64Coin("unibi", totalSupply.Int64())) + err = deps.Chain.BankKeeper.MintCoins(deps.Ctx, evm.ModuleName, spendableCoins) + s.Require().NoError(err) + err = deps.Chain.BankKeeper.SendCoinsFromModuleToAccount( + deps.Ctx, evm.ModuleName, deps.Sender.NibiruAddr, spendableCoins, + ) + s.Require().NoError(err) + + // Send fungible token coins from bank to evm + _, err = deps.K.SendFunTokenToEvm( + deps.Ctx, + &evm.MsgSendFunTokenToEvm{ + Sender: deps.Sender.NibiruAddr.String(), + BankCoin: sdk.Coin{Denom: "unibi", Amount: math.NewInt(amountToSendC.Int64())}, + ToEthAddr: eth.MustNewHexAddrFromStr(toUserC.String()), + }, + ) + s.Require().NoError(err) + + // Export genesis + evmGenesisState := evmmodule.ExportGenesis(deps.Ctx, &deps.K, deps.Chain.AccountKeeper) + authGenesisState := deps.Chain.AccountKeeper.ExportGenesis(deps.Ctx) + + // Init genesis from the exported state + deps = evmtest.NewTestDeps() + deps.Chain.AccountKeeper.InitGenesis(deps.Ctx, *authGenesisState) + evmmodule.InitGenesis(deps.Ctx, &deps.K, deps.Chain.AccountKeeper, *evmGenesisState) + + // Verify erc20 balances for users A, B and sender + balance, err := deps.K.ERC20().BalanceOf(erc20Addr, toUserA, deps.Ctx) + s.Require().NoError(err) + s.Require().Equal(amountToSendA, balance) + + balance, err = deps.K.ERC20().BalanceOf(erc20Addr, toUserB, deps.Ctx) + s.Require().NoError(err) + s.Require().Equal(amountToSendB, balance) + + balance, err = deps.K.ERC20().BalanceOf(erc20Addr, fromUser, deps.Ctx) + s.Require().NoError(err) + s.Require().Equal( + new(big.Int).Sub(totalSupply, big.NewInt(amountToSendA.Int64()+amountToSendB.Int64())), + balance, + ) + + // Check that fungible token mapping is in place + iter := deps.K.FunTokens.Indexes.BankDenom.ExactMatch(deps.Ctx, "unibi") + funTokens := deps.K.FunTokens.Collect(deps.Ctx, iter) + s.Require().Len(funTokens, 1) + s.Require().Equal(funTokenAddr.String(), funTokens[0].Erc20Addr.String()) + s.Require().Equal("unibi", funTokens[0].BankDenom) + s.Require().True(funTokens[0].IsMadeFromCoin) + + // Check that fungible token balance of user C is correct + balance, err = deps.K.ERC20().BalanceOf(funTokenAddr, toUserC, deps.Ctx) + s.Require().NoError(err) + s.Require().Equal(amountToSendC, balance) +} diff --git a/x/evm/evmtest/tx.go b/x/evm/evmtest/tx.go index fea498fbc..5d09c4d5e 100644 --- a/x/evm/evmtest/tx.go +++ b/x/evm/evmtest/tx.go @@ -159,7 +159,6 @@ func DeployContract( Nonce: (*hexutil.Uint64)(&nonce), Input: (*hexutil.Bytes)(&bytecodeForCall), From: &deps.Sender.EthAddr, - // ChainID: deps.Chain.EvmKeeper.EthChainID(deps.Ctx), } ethTxMsg, err := GenerateAndSignEthTxMsg(jsonTxArgs, deps) require.NoError(t, err) diff --git a/x/evm/keeper/statedb.go b/x/evm/keeper/statedb.go index e1c2ceeaa..3ce4ba434 100644 --- a/x/evm/keeper/statedb.go +++ b/x/evm/keeper/statedb.go @@ -5,6 +5,7 @@ import ( "math/big" sdkmath "cosmossdk.io/math" + "github.com/NibiruChain/collections" sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -50,18 +51,15 @@ func (k *Keeper) ForEachStorage( addr gethcommon.Address, stopIter func(key, value gethcommon.Hash) bool, ) { - store := ctx.KVStore(k.storeKey) - prefix := evm.PrefixAccStateEthAddr(addr) - - iterator := sdk.KVStorePrefixIterator(store, prefix) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - key := gethcommon.BytesToHash(iterator.Key()) - value := gethcommon.BytesToHash(iterator.Value()) - - // check if iteration stops - if !stopIter(key, value) { + iter := k.EvmState.AccState.Iterate( + ctx, + collections.PairRange[gethcommon.Address, gethcommon.Hash]{}.Prefix(addr), + ) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + hash := iter.Key().K2() + val := iter.Value() + if !stopIter(hash, gethcommon.BytesToHash(val)) { return } }