laconicd/x/evm/keeper/keeper.go

424 lines
14 KiB
Go
Raw Normal View History

package keeper
import (
"bytes"
"math/big"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
2021-04-17 10:00:07 +00:00
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/palantir/stacktrace"
2021-04-18 15:54:18 +00:00
"github.com/tendermint/tendermint/libs/log"
ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm/types"
)
// Keeper grants access to the EVM module state and implements the go-ethereum StateDB interface.
type Keeper struct {
2021-04-17 10:00:07 +00:00
// Protobuf codec
cdc codec.BinaryCodec
// Store key required for the EVM Prefix KVStore. It is required by:
// - storing account's Storage State
// - storing account's Code
// - storing transaction Logs
// - storing Bloom filters by block height. Needed for the Web3 API.
storeKey sdk.StoreKey
2021-04-18 15:54:18 +00:00
// key to access the transient store, which is reset on every block during Commit
transientKey sdk.StoreKey
// module specific parameter space that can be configured through governance
paramSpace paramtypes.Subspace
// access to account state
accountKeeper types.AccountKeeper
// update balance and accounting operations with coins
bankKeeper types.BankKeeper
// access historical headers for EVM state transition execution
stakingKeeper types.StakingKeeper
2021-04-18 15:54:18 +00:00
// Manage the initial context and cache context stack for accessing the store,
// emit events and log info.
// It is kept as a field to make is accessible by the StateDb
// functions. Resets on every transaction/block.
ctxStack ContextStack
// chain ID number obtained from the context's chain id
eip155ChainID *big.Int
// Tracer used to collect execution traces from the EVM transaction execution
tracer string
// trace EVM state transition execution. This value is obtained from the `--trace` flag.
// For more info check https://geth.ethereum.org/docs/dapp/tracing
debug bool
}
// NewKeeper generates new evm module keeper
func NewKeeper(
cdc codec.BinaryCodec,
storeKey, transientKey sdk.StoreKey, paramSpace paramtypes.Subspace,
ak types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper,
tracer string, debug bool,
) *Keeper {
// ensure evm module account is set
if addr := ak.GetModuleAddress(types.ModuleName); addr == nil {
panic("the EVM module account has not been set")
}
// set KeyTable if it has not already been set
if !paramSpace.HasKeyTable() {
paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable())
}
// NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations
return &Keeper{
cdc: cdc,
paramSpace: paramSpace,
accountKeeper: ak,
2021-04-17 10:00:07 +00:00
bankKeeper: bankKeeper,
stakingKeeper: sk,
2021-04-17 10:00:07 +00:00
storeKey: storeKey,
transientKey: transientKey,
tracer: tracer,
debug: debug,
}
}
// Ctx returns the current context from the context stack
func (k Keeper) Ctx() sdk.Context {
return k.ctxStack.CurrentContext()
}
// CommitCachedContexts commit all the cache contexts created by `StateDB.Snapshot`.
func (k *Keeper) CommitCachedContexts() {
k.ctxStack.Commit()
}
// CachedContextsEmpty returns true if there's no cache contexts.
func (k *Keeper) CachedContextsEmpty() bool {
return k.ctxStack.IsEmpty()
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
2021-04-18 15:54:18 +00:00
return ctx.Logger().With("module", types.ModuleName)
}
// WithContext clears the context stack, and set the initial context.
func (k *Keeper) WithContext(ctx sdk.Context) {
k.ctxStack.Reset(ctx)
}
// WithChainID sets the chain id to the local variable in the keeper
func (k *Keeper) WithChainID(ctx sdk.Context) {
chainID, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
panic(err)
}
if k.eip155ChainID != nil && k.eip155ChainID.Cmp(chainID) != 0 {
panic("chain id already set")
}
k.eip155ChainID = chainID
}
// ChainID returns the EIP155 chain ID for the EVM context
func (k Keeper) ChainID() *big.Int {
return k.eip155ChainID
}
// ----------------------------------------------------------------------------
// Parent Block Gas Used
// Required by EIP1559 base fee calculation.
// ----------------------------------------------------------------------------
// GetBlockGasUsed returns the last block gas used value from the store.
func (k Keeper) GetBlockGasUsed(ctx sdk.Context) uint64 {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyPrefixBlockGasUsed)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// SetBlockGasUsed gets the current block gas consumed to the store.
// CONTRACT: this should be only called during EndBlock.
func (k Keeper) SetBlockGasUsed(ctx sdk.Context) {
if ctx.BlockGasMeter() == nil {
k.Logger(ctx).Error("block gas meter is nil when setting block gas used")
return
}
store := ctx.KVStore(k.storeKey)
gasBz := sdk.Uint64ToBigEndian(ctx.BlockGasMeter().GasConsumedToLimit())
store.Set(types.KeyPrefixBlockGasUsed, gasBz)
}
// ----------------------------------------------------------------------------
// Parent Base Fee
// Required by EIP1559 base fee calculation.
// ----------------------------------------------------------------------------
// GetBlockGasUsed returns the last block gas used value from the store.
func (k Keeper) GetBaseFee(ctx sdk.Context) *big.Int {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyPrefixBaseFee)
if len(bz) == 0 {
return new(big.Int).SetUint64(types.InitialBaseFee) // TODO: use geth params
}
return new(big.Int).SetBytes(bz)
}
// SetBlockGasUsed gets the current block gas consumed to the store.
// CONTRACT: this should be only called during EndBlock.
func (k Keeper) SetBaseFee(ctx sdk.Context, baseFee *big.Int) {
store := ctx.KVStore(k.storeKey)
store.Set(types.KeyPrefixBaseFee, baseFee.Bytes())
}
// ----------------------------------------------------------------------------
// Block Bloom
2021-04-18 15:54:18 +00:00
// Required by Web3 API.
// ----------------------------------------------------------------------------
2021-04-18 15:54:18 +00:00
// GetBlockBloom gets bloombits from block height
func (k Keeper) GetBlockBloom(ctx sdk.Context, height int64) (ethtypes.Bloom, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.BloomKey(height))
if len(bz) == 0 {
return ethtypes.Bloom{}, false
2021-04-18 15:54:18 +00:00
}
return ethtypes.BytesToBloom(bz), true
}
// SetBlockBloom sets the mapping from block height to bloom bits
func (k Keeper) SetBlockBloom(ctx sdk.Context, height int64, bloom ethtypes.Bloom) {
store := ctx.KVStore(k.storeKey)
key := types.BloomKey(height)
store.Set(key, bloom.Bytes())
}
// GetBlockBloomTransient returns bloom bytes for the current block height
func (k Keeper) GetBlockBloomTransient() *big.Int {
store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(k.Ctx().BlockHeight()))
bz := store.Get(heightBz)
if len(bz) == 0 {
return big.NewInt(0)
}
return new(big.Int).SetBytes(bz)
}
// SetBlockBloomTransient sets the given bloom bytes to the transient store. This value is reset on
// every block.
func (k Keeper) SetBlockBloomTransient(bloom *big.Int) {
store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(k.Ctx().BlockHeight()))
store.Set(heightBz, bloom.Bytes())
}
// ----------------------------------------------------------------------------
// Tx
// ----------------------------------------------------------------------------
// GetTxHashTransient returns the hash of current processing transaction
func (k Keeper) GetTxHashTransient() common.Hash {
store := k.Ctx().TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientTxHash)
if len(bz) == 0 {
return common.Hash{}
}
return common.BytesToHash(bz)
}
// SetTxHashTransient set the hash of processing transaction
func (k Keeper) SetTxHashTransient(hash common.Hash) {
store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientTxHash, hash.Bytes())
}
// GetTxIndexTransient returns EVM transaction index on the current block.
func (k Keeper) GetTxIndexTransient() uint64 {
store := k.Ctx().TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientTxIndex)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// IncreaseTxIndexTransient fetches the current EVM tx index from the transient store, increases its
// value by one and then sets the new index back to the transient store.
func (k Keeper) IncreaseTxIndexTransient() {
txIndex := k.GetTxIndexTransient()
store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(txIndex+1))
}
// ResetRefundTransient resets the available refund amount to 0
func (k Keeper) ResetRefundTransient(ctx sdk.Context) {
store := ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(0))
}
// ----------------------------------------------------------------------------
// Log
// ----------------------------------------------------------------------------
// GetAllTxLogs return all the transaction logs from the store.
func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefixLogs)
defer iterator.Close()
txsLogs := []types.TransactionLogs{}
for ; iterator.Valid(); iterator.Next() {
2021-04-17 10:00:07 +00:00
var txLog types.TransactionLogs
k.cdc.MustUnmarshal(iterator.Value(), &txLog)
// add a new entry
txsLogs = append(txsLogs, txLog)
}
return txsLogs
}
// GetLogs returns the current logs for a given transaction hash from the KVStore.
// This function returns an empty, non-nil slice if no logs are found.
func (k Keeper) GetTxLogs(txHash common.Hash) []*ethtypes.Log {
store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixLogs)
bz := store.Get(txHash.Bytes())
if len(bz) == 0 {
return []*ethtypes.Log{}
}
var logs types.TransactionLogs
k.cdc.MustUnmarshal(bz, &logs)
return logs.EthLogs()
}
// SetLogs sets the logs for a transaction in the KVStore.
func (k Keeper) SetLogs(txHash common.Hash, logs []*ethtypes.Log) {
store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixLogs)
txLogs := types.NewTransactionLogsFromEth(txHash, logs)
bz := k.cdc.MustMarshal(&txLogs)
store.Set(txHash.Bytes(), bz)
}
// DeleteLogs removes the logs from the KVStore. It is used during journal.Revert.
func (k Keeper) DeleteTxLogs(ctx sdk.Context, txHash common.Hash) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixLogs)
store.Delete(txHash.Bytes())
}
// GetLogSizeTransient returns EVM log index on the current block.
func (k Keeper) GetLogSizeTransient() uint64 {
store := k.Ctx().TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientLogSize)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// IncreaseLogSizeTransient fetches the current EVM log index from the transient store, increases its
// value by one and then sets the new index back to the transient store.
func (k Keeper) IncreaseLogSizeTransient() {
logSize := k.GetLogSizeTransient()
store := k.Ctx().TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientLogSize, sdk.Uint64ToBigEndian(logSize+1))
}
// ----------------------------------------------------------------------------
// Storage
// ----------------------------------------------------------------------------
// GetAccountStorage return state storage associated with an account
func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (types.Storage, error) {
storage := types.Storage{}
err := k.ForEachStorage(address, func(key, value common.Hash) bool {
storage = append(storage, types.NewState(key, value))
return false
})
if err != nil {
return types.Storage{}, err
}
return storage, nil
}
// ----------------------------------------------------------------------------
// Account
// ----------------------------------------------------------------------------
func (k Keeper) DeleteState(addr common.Address, key common.Hash) {
store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.AddressStoragePrefix(addr))
key = types.KeyAddressStorage(addr, key)
store.Delete(key.Bytes())
}
// DeleteAccountStorage clears all the storage state associated with the given address.
func (k Keeper) DeleteAccountStorage(addr common.Address) {
_ = k.ForEachStorage(addr, func(key, _ common.Hash) bool {
k.DeleteState(addr, key)
return false
})
}
// DeleteCode removes the contract code byte array from the store associated with
// the given address.
func (k Keeper) DeleteCode(addr common.Address) {
hash := k.GetCodeHash(addr)
if bytes.Equal(hash.Bytes(), common.BytesToHash(types.EmptyCodeHash).Bytes()) {
return
}
store := prefix.NewStore(k.Ctx().KVStore(k.storeKey), types.KeyPrefixCode)
store.Delete(hash.Bytes())
}
// ClearBalance subtracts the EVM all the balance denomination from the address
// balance while also updating the total supply.
func (k Keeper) ClearBalance(addr sdk.AccAddress) (prevBalance sdk.Coin, err error) {
params := k.GetParams(k.Ctx())
prevBalance = k.bankKeeper.GetBalance(k.Ctx(), addr, params.EvmDenom)
if prevBalance.IsPositive() {
if err := k.bankKeeper.SendCoinsFromAccountToModule(k.Ctx(), addr, types.ModuleName, sdk.Coins{prevBalance}); err != nil {
return sdk.Coin{}, stacktrace.Propagate(err, "failed to transfer to module account")
}
if err := k.bankKeeper.BurnCoins(k.Ctx(), types.ModuleName, sdk.Coins{prevBalance}); err != nil {
return sdk.Coin{}, stacktrace.Propagate(err, "failed to burn coins from evm module account")
}
}
return prevBalance, nil
}
// ResetAccount removes the code, storage state, but keep all the native tokens stored
// with the given address.
func (k Keeper) ResetAccount(addr common.Address) {
k.DeleteCode(addr)
k.DeleteAccountStorage(addr)
}