Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

types, evm: refactor accounts #884

Merged
merged 6 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* (types) [tharsis#884](https://github.com/tharsis/ethermint/pull/884) Introduce a new `EthAccountI` interface for EVM-compatible account types.
* (types) [tharsis#849](https://github.com/tharsis/ethermint/pull/849) Add `Type` function to distinguish EOAs from Contract accounts.
* (evm) [tharsis#826](https://github.com/tharsis/ethermint/issues/826) Improve allocation of bytes of `tx.To` address.
* (evm) [tharsis#827](https://github.com/tharsis/ethermint/issues/827) Speed up creation of event logs by using the slice insertion idiom with indices.
Expand All @@ -64,6 +65,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Bug Fixes

* (evm) [tharsis#884](https://github.com/tharsis/ethermint/pull/884) Support multiple account types on the EVM `StateDB`.
* (rpc) [tharsis#831](https://github.com/tharsis/ethermint/pull/831) Fix BaseFee value when height is specified.
* (evm) [tharsis#838](https://github.com/tharsis/ethermint/pull/838) Fix splitting of trace.Memory into 32 chunks.
* (rpc) [tharsis#860](https://github.com/tharsis/ethermint/pull/860) Fix `eth_getLogs` when specify blockHash without address/topics, and limit the response size.
Expand Down
7 changes: 2 additions & 5 deletions app/ante/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,8 @@ func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx

// check whether the sender address is EOA
fromAddr := common.BytesToAddress(from)
acct, err := avd.evmKeeper.GetAccount(ctx, fromAddr)
if err != nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType,
"the sender is not EthAccount: address %s", fromAddr)
}
acct := avd.evmKeeper.GetAccount(ctx, fromAddr)

if acct == nil {
acc := avd.ak.NewAccountWithAddress(ctx, from)
avd.ak.SetAccount(ctx, acc)
Expand Down
2 changes: 2 additions & 0 deletions app/ante/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"
tx "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
Expand All @@ -23,6 +24,7 @@ type EVMKeeper interface {
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool,
) (sdk.Coins, error)
BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
GetBalance(ctx sdk.Context, addr common.Address) *big.Int
}

type protoTxProvider interface {
Expand Down
20 changes: 20 additions & 0 deletions types/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

var (
_ authtypes.AccountI = (*EthAccount)(nil)
_ EthAccountI = (*EthAccount)(nil)
_ authtypes.GenesisAccount = (*EthAccount)(nil)
_ codectypes.UnpackInterfacesMessage = (*EthAccount)(nil)
)
Expand All @@ -25,6 +26,19 @@ const (
AccountTypeContract
)

// EthAccountI represents the interface of an EVM compatible account
type EthAccountI interface {
authtypes.AccountI
// EthAddress returns the ethereum Address representation of the AccAddress
EthAddress() common.Address
// CodeHash is the keccak256 hash of the contract code (if any)
GetCodeHash() common.Hash
// SetCodeHash sets the code hash to the account fields
SetCodeHash(code common.Hash) error
// Type returns the type of Ethereum Account (EOA or Contract)
Type() int8
}

// ----------------------------------------------------------------------------
// Main Ethermint account
// ----------------------------------------------------------------------------
Expand All @@ -48,6 +62,12 @@ func (acc EthAccount) GetCodeHash() common.Hash {
return common.HexToHash(acc.CodeHash)
}

// SetCodeHash sets the account code hash to the EthAccount fields
func (acc *EthAccount) SetCodeHash(codeHash common.Hash) error {
acc.CodeHash = codeHash.Hex()
return nil
}

// Type returns the type of Ethereum Account (EOA or Contract)
func (acc EthAccount) Type() int8 {
if bytes.Equal(emptyCodeHash, common.Hex2Bytes(acc.CodeHash)) {
Expand Down
12 changes: 0 additions & 12 deletions types/code.go

This file was deleted.

11 changes: 6 additions & 5 deletions x/evm/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,21 @@ func InitGenesis(
panic(fmt.Errorf("account not found for address %s", account.Address))
}

ethAcct, ok := acc.(*ethermint.EthAccount)
ethAcct, ok := acc.(ethermint.EthAccountI)
if !ok {
panic(
fmt.Errorf("account %s must be an %T type, got %T",
account.Address, &ethermint.EthAccount{}, acc,
fmt.Errorf("account %s must be an EthAccount interface, got %T",
account.Address, acc,
),
)
}

code := common.Hex2Bytes(account.Code)
codeHash := crypto.Keccak256Hash(code)
if !bytes.Equal(common.HexToHash(ethAcct.CodeHash).Bytes(), codeHash.Bytes()) {
if !bytes.Equal(ethAcct.GetCodeHash().Bytes(), codeHash.Bytes()) {
panic("code don't match codeHash")
}

k.SetCode(ctx, codeHash.Bytes(), code)

for _, storage := range account.Storage {
Expand All @@ -68,7 +69,7 @@ func InitGenesis(
func ExportGenesis(ctx sdk.Context, k *keeper.Keeper, ak types.AccountKeeper) *types.GenesisState {
var ethGenAccounts []types.GenesisAccount
ak.IterateAccounts(ctx, func(account authtypes.AccountI) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
ethAccount, ok := account.(ethermint.EthAccountI)
if !ok {
// ignore non EthAccounts
return false
Expand Down
11 changes: 3 additions & 8 deletions x/evm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,8 @@ func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ
addr := common.HexToAddress(req.Address)

ctx := sdk.UnwrapSDKContext(c)
acct, err := k.GetAccountOrEmpty(ctx, addr)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
acct := k.GetAccountOrEmpty(ctx, addr)

return &types.QueryAccountResponse{
Balance: acct.Balance.String(),
CodeHash: common.BytesToHash(acct.CodeHash).Hex(),
Expand Down Expand Up @@ -186,10 +184,7 @@ func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que
ctx := sdk.UnwrapSDKContext(c)

address := common.HexToAddress(req.Address)
acct, err := k.GetAccountWithoutBalance(ctx, address)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
acct := k.GetAccountWithoutBalance(ctx, address)

var code []byte
if acct != nil && acct.IsContract() {
Expand Down
6 changes: 2 additions & 4 deletions x/evm/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,7 @@ func (suite *KeeperTestSuite) TestQueryCode() {
}

func (suite *KeeperTestSuite) TestQueryTxLogs() {
var (
expLogs []*types.Log
)
var expLogs []*types.Log
txHash := common.BytesToHash([]byte("tx_hash"))
txIndex := uint(1)
logIndex := uint(1)
Expand Down Expand Up @@ -593,7 +591,7 @@ func (suite *KeeperTestSuite) TestEstimateGas() {
rsp, err := suite.queryClient.EstimateGas(sdk.WrapSDKContext(suite.ctx), &req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(tc.expGas, rsp.Gas)
suite.Require().Equal(int64(tc.expGas), int64(rsp.Gas))
} else {
suite.Require().Error(err)
}
Expand Down
45 changes: 19 additions & 26 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
Expand Down Expand Up @@ -235,38 +234,37 @@ func (k Keeper) Tracer(ctx sdk.Context, msg core.Message, ethCfg *params.ChainCo

// GetAccountWithoutBalance 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, error) {
func (k *Keeper) GetAccountWithoutBalance(ctx sdk.Context, addr common.Address) *statedb.Account {
cosmosAddr := sdk.AccAddress(addr.Bytes())
acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
if acct == nil {
return nil, nil
return nil
}

ethAcct, ok := acct.(*ethermint.EthAccount)
if !ok {
return nil, sdkerrors.Wrapf(types.ErrInvalidAccount, "type %T, address %s", acct, addr)
codeHash := types.EmptyCodeHash
ethAcct, ok := acct.(ethermint.EthAccountI)
if ok {
codeHash = ethAcct.GetCodeHash().Bytes()
}

return &statedb.Account{
Nonce: ethAcct.Sequence,
CodeHash: common.FromHex(ethAcct.CodeHash),
}, nil
Nonce: acct.GetSequence(),
CodeHash: codeHash,
}
}

// GetAccountOrEmpty returns empty account if not exist, returns error if it's not `EthAccount`
func (k *Keeper) GetAccountOrEmpty(ctx sdk.Context, addr common.Address) (statedb.Account, error) {
acct, err := k.GetAccount(ctx, addr)
if err != nil {
return statedb.Account{}, err
func (k *Keeper) GetAccountOrEmpty(ctx sdk.Context, addr common.Address) statedb.Account {
acct := k.GetAccount(ctx, addr)
if acct != nil {
return *acct
}
if acct == nil {
// empty account
return statedb.Account{
Balance: new(big.Int),
CodeHash: types.EmptyCodeHash,
}, nil

// empty account
return statedb.Account{
Balance: new(big.Int),
CodeHash: types.EmptyCodeHash,
}
return *acct, nil
}

// GetNonce returns the sequence number of an account, returns 0 if not exists.
Expand All @@ -277,12 +275,7 @@ func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 {
return 0
}

ethAcct, ok := acct.(*ethermint.EthAccount)
if !ok {
return 0
}

return ethAcct.Sequence
return acct.GetSequence()
}

// GetBalance load account's balance of gas token
Expand Down
49 changes: 29 additions & 20 deletions x/evm/keeper/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ import (
var _ statedb.Keeper = &Keeper{}

// ----------------------------------------------------------------------------
// statedb.Keeper implementation
// StateDB Keeper implementation
// ----------------------------------------------------------------------------

// GetAccount returns nil if account is not exist, returns error if it's not `EthAccount`
func (k *Keeper) GetAccount(ctx sdk.Context, addr common.Address) (*statedb.Account, error) {
acct, err := k.GetAccountWithoutBalance(ctx, addr)
if acct == nil || err != nil {
return acct, err
// 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, nil
return acct
}

// GetState loads contract state from database, implements `statedb.Keeper` interface.
Expand Down Expand Up @@ -109,25 +109,33 @@ func (k *Keeper) SetAccount(ctx sdk.Context, addr common.Address, account stated
if acct == nil {
acct = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr)
}
ethAcct, ok := acct.(*ethermint.EthAccount)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidAccount, "type %T, address %s", acct, addr)

if err := acct.SetSequence(account.Nonce); err != nil {
return err
}

codeHash := common.BytesToHash(account.CodeHash)

if ethAcct, ok := acct.(ethermint.EthAccountI); ok {
if err := ethAcct.SetCodeHash(codeHash); err != nil {
return err
}
}
if err := ethAcct.SetSequence(account.Nonce); err != nil {

k.accountKeeper.SetAccount(ctx, acct)

if err := k.SetBalance(ctx, addr, account.Balance); err != nil {
return err
}
ethAcct.CodeHash = common.BytesToHash(account.CodeHash).Hex()
k.accountKeeper.SetAccount(ctx, ethAcct)

err := k.SetBalance(ctx, addr, account.Balance)
k.Logger(ctx).Debug(
"account updated",
"ethereum-address", addr.Hex(),
"nonce", account.Nonce,
"codeHash", common.BytesToHash(account.CodeHash).Hex(),
"codeHash", codeHash.Hex(),
"balance", account.Balance,
)
return err
return nil
}

// SetState update contract storage, delete if value is empty.
Expand Down Expand Up @@ -177,7 +185,8 @@ func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error {
return nil
}

ethAcct, ok := acct.(*ethermint.EthAccount)
// NOTE: only Ethereum accounts (contracts) can be selfdestructed
ethAcct, ok := acct.(ethermint.EthAccountI)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidAccount, "type %T, address %s", acct, addr)
}
Expand All @@ -188,9 +197,9 @@ func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error {
}

// remove code
codeHash := common.HexToHash(ethAcct.CodeHash).Bytes()
if !bytes.Equal(codeHash, types.EmptyCodeHash) {
k.SetCode(ctx, codeHash, nil)
codeHashBz := ethAcct.GetCodeHash().Bytes()
if !bytes.Equal(codeHashBz, types.EmptyCodeHash) {
k.SetCode(ctx, codeHashBz, nil)
}

// clear storage
Expand Down