Skip to content

Commit

Permalink
feat(evm): export genesis (#1967)
Browse files Browse the repository at this point in the history
* testing evm genesis export

* feat(evm): export genesis

* chore: changelog update

---------

Co-authored-by: Unique Divine <[email protected]>
  • Loading branch information
onikonychev and Unique-Divine authored Jul 29, 2024
1 parent 9ac9d70 commit c77809a
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 52 additions & 4 deletions x/evm/evmmodule/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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())
Expand All @@ -57,22 +59,68 @@ 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 {
k.SetState(ctx, address, gethcommon.HexToHash(storage.Key), gethcommon.HexToHash(storage.Value).Bytes())
}
}

// 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,
}
}
126 changes: 126 additions & 0 deletions x/evm/evmmodule/genesis_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 0 additions & 1 deletion x/evm/evmtest/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 10 additions & 12 deletions x/evm/keeper/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
}
Expand Down

0 comments on commit c77809a

Please sign in to comment.