Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

evm: balance and nonce invariants #661

Merged
merged 4 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/ethermint.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func NewEthermintApp(
app.mm.SetOrderInitGenesis(
auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName,
slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName,
crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, evm.ModuleName,
evm.ModuleName, crisis.ModuleName, genutil.ModuleName, evidence.ModuleName,
faucet.ModuleName,
)

Expand Down
18 changes: 4 additions & 14 deletions x/evm/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, d
for _, account := range data.Accounts {
address := ethcmn.HexToAddress(account.Address)
accAddress := sdk.AccAddress(address.Bytes())

// check that the EVM balance the matches the account balance
acc := accountKeeper.GetAccount(ctx, accAddress)
if acc == nil {
Expand All @@ -37,17 +36,11 @@ func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, d
}

evmBalance := acc.GetCoins().AmountOf(evmDenom)
if !evmBalance.Equal(account.Balance) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this check to the invariant

panic(
fmt.Errorf(
"balance mismatch for account %s, expected %s%s, got %s%s",
account.Address, evmBalance, evmDenom, account.Balance, evmDenom,
),
)
}

k.SetBalance(ctx, address, account.Balance.BigInt())
k.SetNonce(ctx, address, acc.GetSequence())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this setter was missing

k.SetBalance(ctx, address, evmBalance.BigInt())
k.SetCode(ctx, address, account.Code)

for _, storage := range account.Storage {
k.SetState(ctx, address, storage.Key, storage.Value)
}
Expand Down Expand Up @@ -88,6 +81,7 @@ func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisSta
for _, account := range accounts {
ethAccount, ok := account.(*ethermint.EthAccount)
if !ok {
// ignore non EthAccounts
continue
}

Expand All @@ -98,12 +92,8 @@ func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisSta
panic(err)
}

balanceInt := k.GetBalance(ctx, addr)
balance := sdk.NewIntFromBigInt(balanceInt)

genAccount := types.GenesisAccount{
Address: addr.String(),
Balance: balance,
Code: k.GetCode(ctx, addr),
Storage: storage,
}
Expand Down
3 changes: 0 additions & 3 deletions x/evm/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ func (suite *EvmTestSuite) TestInitGenesis() {
Accounts: []types.GenesisAccount{
{
Address: address.String(),
Balance: sdk.OneInt(),
Storage: types.Storage{
{Key: common.BytesToHash([]byte("key")), Value: common.BytesToHash([]byte("value"))},
},
Expand Down Expand Up @@ -87,7 +86,6 @@ func (suite *EvmTestSuite) TestInitGenesis() {
Accounts: []types.GenesisAccount{
{
Address: address.String(),
Balance: sdk.OneInt(),
},
},
},
Expand All @@ -105,7 +103,6 @@ func (suite *EvmTestSuite) TestInitGenesis() {
Accounts: []types.GenesisAccount{
{
Address: address.String(),
Balance: sdk.OneInt(),
},
},
},
Expand Down
58 changes: 58 additions & 0 deletions x/evm/keeper/invariants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"

ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)

const balanceInvariant = "balance"

// RegisterInvariants registers the evm module invariants
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
ir.RegisterRoute(types.ModuleName, balanceInvariant, k.BalanceInvariant())
}

// BalanceInvariant checks that all auth module's EthAccounts in the application have the same balance
// as the EVM one.
func (k Keeper) BalanceInvariant() sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var (
msg string
count int
)

k.accountKeeper.IterateAccounts(ctx, func(account authexported.Account) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
if !ok {
// ignore non EthAccounts
return false
}

evmDenom := k.GetParams(ctx).EvmDenom
accountBalance := ethAccount.GetCoins().AmountOf(evmDenom)
evmBalance := k.GetBalance(ctx, ethAccount.EthAddress())

if evmBalance.Cmp(accountBalance.BigInt()) != 0 {
count++
msg += fmt.Sprintf(
"\tbalance mismatch for address %s: account balance %s, evm balance %s\n",
account.GetAddress(), accountBalance.String(), evmBalance.String(),
)
}

return false
})

broken := count != 0

return sdk.FormatInvariant(
types.ModuleName, balanceInvariant,
fmt.Sprintf("amount balances mismatches found %d\n%s", count, msg),
), broken
}
}
76 changes: 76 additions & 0 deletions x/evm/keeper/invariants_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package keeper_test

import (
"math/big"

sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/cosmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/cosmos/ethermint/types"

ethcmn "github.com/ethereum/go-ethereum/common"
)

func (suite *KeeperTestSuite) TestBalanceInvariant() {
privkey, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)

address := ethcmn.HexToAddress(privkey.PubKey().Address().String())

testCases := []struct {
name string
malleate func()
expBroken bool
}{
{
"balance mismatch",
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
err := acc.SetCoins(sdk.NewCoins(ethermint.NewPhotonCoinInt64(1)))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(1000))
},
true,
},
{
"balance ok",
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
err := acc.SetCoins(sdk.NewCoins(ethermint.NewPhotonCoinInt64(1)))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(1))
},
false,
},
{
"invalid account type",
func() {
acc := authtypes.NewBaseAccountWithAddress(address.Bytes())
suite.app.AccountKeeper.SetAccount(suite.ctx, &acc)
},
false,
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset values

tc.malleate()

_, broken := suite.app.EvmKeeper.BalanceInvariant()(suite.ctx)
if tc.expBroken {
suite.Require().True(broken)
} else {
suite.Require().False(broken)
}
})
}
}
3 changes: 3 additions & 0 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type Keeper struct {
// - storing block height -> bloom filter map. Needed for the Web3 API.
// - storing block hash -> block height map. Needed for the Web3 API.
storeKey sdk.StoreKey
// Account Keeper for fetching accounts
accountKeeper types.AccountKeeper
// Ethermint concrete implementation on the EVM StateDB interface
CommitStateDB *types.CommitStateDB
// Transaction counter in a block. Used on StateSB's Prepare function.
Expand All @@ -52,6 +54,7 @@ func NewKeeper(
return Keeper{
cdc: cdc,
storeKey: storeKey,
accountKeeper: ak,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak),
TxCount: 0,
Bloom: big.NewInt(0),
Expand Down
4 changes: 0 additions & 4 deletions x/evm/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,8 @@ func queryExportAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte,
return nil, err
}

balanceInt := keeper.GetBalance(ctx, addr)
balance := sdk.NewIntFromBigInt(balanceInt)

res := types.GenesisAccount{
Address: hexAddress,
Balance: balance,
Code: keeper.GetCode(ctx, addr),
Storage: storage,
araskachoi marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
4 changes: 3 additions & 1 deletion x/evm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ func (AppModule) Name() string {
}

// RegisterInvariants interface for registering invariants
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {}
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
keeper.RegisterInvariants(ir, am.keeper)
}

// Route specifies path for transactions
func (am AppModule) Route() string {
Expand Down
1 change: 1 addition & 0 deletions x/evm/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
type AccountKeeper interface {
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
GetAllAccounts(ctx sdk.Context) (accounts []authexported.Account)
IterateAccounts(ctx sdk.Context, cb func(account authexported.Account) bool)
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
SetAccount(ctx sdk.Context, account authexported.Account)
RemoveAccount(ctx sdk.Context, account authexported.Account)
Expand Down
10 changes: 1 addition & 9 deletions x/evm/types/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"errors"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
Expand All @@ -22,9 +20,9 @@ type (
// GenesisAccount defines an account to be initialized in the genesis state.
// Its main difference between with Geth's GenesisAccount is that it uses a custom
// storage type and that it doesn't contain the private key field.
// NOTE: balance is omitted as it is imported from the auth account balance.
GenesisAccount struct {
Address string `json:"address"`
Balance sdk.Int `json:"balance"`
Code hexutil.Bytes `json:"code,omitempty"`
Storage Storage `json:"storage,omitempty"`
}
Expand All @@ -35,12 +33,6 @@ func (ga GenesisAccount) Validate() error {
if ga.Address == (ethcmn.Address{}.String()) {
return fmt.Errorf("address cannot be the zero address %s", ga.Address)
}
if ga.Balance.IsNil() {
return errors.New("balance cannot be nil")
}
if ga.Balance.IsNegative() {
return errors.New("balance cannot be negative")
}
if ga.Code != nil && len(ga.Code) == 0 {
return errors.New("code bytes cannot be empty")
}
Expand Down
26 changes: 0 additions & 26 deletions x/evm/types/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/ethermint/crypto/ethsecp256k1"
)

Expand All @@ -26,7 +24,6 @@ func TestValidateGenesisAccount(t *testing.T) {
"valid genesis account",
GenesisAccount{
Address: address.String(),
Balance: sdk.NewInt(1),
Code: []byte{1, 2, 3},
Storage: Storage{
NewState(ethcmn.BytesToHash([]byte{1, 2, 3}), ethcmn.BytesToHash([]byte{1, 2, 3})),
Expand All @@ -38,31 +35,13 @@ func TestValidateGenesisAccount(t *testing.T) {
"empty account address bytes",
GenesisAccount{
Address: ethcmn.Address{}.String(),
Balance: sdk.NewInt(1),
},
false,
},
{
"nil account balance",
GenesisAccount{
Address: address.String(),
Balance: sdk.Int{},
},
false,
},
{
"nil account balance",
GenesisAccount{
Address: address.String(),
Balance: sdk.NewInt(-1),
},
false,
},
{
"empty code bytes",
GenesisAccount{
Address: address.String(),
Balance: sdk.NewInt(1),
Code: []byte{},
},
false,
Expand Down Expand Up @@ -101,7 +80,6 @@ func TestValidateGenesis(t *testing.T) {
Accounts: []GenesisAccount{
{
Address: address.String(),
Balance: sdk.NewInt(1),
Code: []byte{1, 2, 3},
Storage: Storage{
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
Expand Down Expand Up @@ -153,15 +131,13 @@ func TestValidateGenesis(t *testing.T) {
Accounts: []GenesisAccount{
{
Address: address.String(),
Balance: sdk.NewInt(1),
Code: []byte{1, 2, 3},
Storage: Storage{
NewState(ethcmn.BytesToHash([]byte{1, 2, 3}), ethcmn.BytesToHash([]byte{1, 2, 3})),
},
},
{
Address: address.String(),
Balance: sdk.NewInt(1),
Code: []byte{1, 2, 3},
Storage: Storage{
NewState(ethcmn.BytesToHash([]byte{1, 2, 3}), ethcmn.BytesToHash([]byte{1, 2, 3})),
Expand All @@ -177,7 +153,6 @@ func TestValidateGenesis(t *testing.T) {
Accounts: []GenesisAccount{
{
Address: address.String(),
Balance: sdk.NewInt(1),
Code: []byte{1, 2, 3},
Storage: Storage{
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
Expand Down Expand Up @@ -227,7 +202,6 @@ func TestValidateGenesis(t *testing.T) {
Accounts: []GenesisAccount{
{
Address: address.String(),
Balance: sdk.NewInt(1),
Code: []byte{1, 2, 3},
Storage: Storage{
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
Expand Down