222 lines
6.2 KiB
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
|
|
}
|