laconicd/x/evm/keeper/statedb.go

222 lines
6.2 KiB
Go

package keeper
import (
"bytes"
"fmt"
"math/big"
ethermint "github.com/cerc-io/laconicd/types"
"github.com/cerc-io/laconicd/x/evm/statedb"
"github.com/cerc-io/laconicd/x/evm/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common"
)
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))
value := store.Get(key.Bytes())
if len(value) == 0 {
return common.Hash{}
}
return common.BytesToHash(value)
}
// GetCode loads contract code from database, implements `statedb.Keeper` interface.
func (k *Keeper) GetCode(ctx sdk.Context, codeHash common.Hash) []byte {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode)
return store.Get(codeHash.Bytes())
}
// ForEachStorage iterate contract storage, callback return false to break early
func (k *Keeper) ForEachStorage(ctx sdk.Context, addr common.Address, cb func(key, value common.Hash) bool) {
store := ctx.KVStore(k.storeKey)
prefix := types.AddressStoragePrefix(addr)
iterator := sdk.KVStorePrefixIterator(store, prefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
key := common.BytesToHash(iterator.Key())
value := common.BytesToHash(iterator.Value())
// check if iteration stops
if !cb(key, value) {
return
}
}
}
// SetBalance update account's balance, compare with current balance first, then decide to mint or burn.
func (k *Keeper) SetBalance(ctx sdk.Context, addr common.Address, amount *big.Int) error {
cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(ctx)
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
balance := coin.Amount.BigInt()
delta := new(big.Int).Sub(amount, balance)
switch delta.Sign() {
case 1:
// mint
coins := sdk.NewCoins(sdk.NewCoin(params.EvmDenom, sdk.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
}
case -1:
// burn
coins := sdk.NewCoins(sdk.NewCoin(params.EvmDenom, sdk.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
}
default:
// not changed
}
return nil
}
// SetAccount updates nonce/balance/codeHash together.
func (k *Keeper) SetAccount(ctx sdk.Context, addr common.Address, account statedb.Account) error {
// update account
cosmosAddr := sdk.AccAddress(addr.Bytes())
acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
if acct == nil {
acct = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr)
}
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
}
}
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
}
// SetState update contract storage, delete if value is empty.
func (k *Keeper) SetState(ctx sdk.Context, addr common.Address, key common.Hash, value []byte) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr))
action := "updated"
if len(value) == 0 {
store.Delete(key.Bytes())
action = "deleted"
} else {
store.Set(key.Bytes(), value)
}
k.Logger(ctx).Debug(
fmt.Sprintf("state %s", action),
"ethereum-address", addr.Hex(),
"key", key.Hex(),
)
}
// SetCode set contract code, delete if code is empty.
func (k *Keeper) SetCode(ctx sdk.Context, codeHash, code []byte) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode)
// store or delete code
action := "updated"
if len(code) == 0 {
store.Delete(codeHash)
action = "deleted"
} else {
store.Set(codeHash, code)
}
k.Logger(ctx).Debug(
fmt.Sprintf("code %s", action),
"code-hash", common.BytesToHash(codeHash).Hex(),
)
}
// DeleteAccount handles contract's suicide call:
// - clear balance
// - remove code
// - remove states
// - remove auth account
func (k *Keeper) DeleteAccount(ctx sdk.Context, addr common.Address) error {
cosmosAddr := sdk.AccAddress(addr.Bytes())
acct := k.accountKeeper.GetAccount(ctx, cosmosAddr)
if acct == nil {
return nil
}
// 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)
}
// clear balance
if err := k.SetBalance(ctx, addr, new(big.Int)); err != nil {
return err
}
// remove code
codeHashBz := ethAcct.GetCodeHash().Bytes()
if !bytes.Equal(codeHashBz, types.EmptyCodeHash) {
k.SetCode(ctx, codeHashBz, nil)
}
// clear storage
k.ForEachStorage(ctx, addr, func(key, _ common.Hash) bool {
k.SetState(ctx, addr, key, nil)
return true
})
// remove auth account
k.accountKeeper.RemoveAccount(ctx, acct)
k.Logger(ctx).Debug(
"account suicided",
"ethereum-address", addr.Hex(),
"cosmos-address", cosmosAddr.String(),
)
return nil
}