fe5fefbd8e
* fix panic when transaction is reverted * update changelog * Update x/evm/keeper/context_stack.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
380 lines
12 KiB
Go
380 lines
12 KiB
Go
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"
|
|
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"
|
|
"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 {
|
|
// 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
|
|
|
|
// 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
|
|
// fetch EIP1559 base fee and parameters
|
|
feeMarketKeeper types.FeeMarketKeeper
|
|
|
|
// 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
|
|
|
|
// EVM Hooks for tx post-processing
|
|
hooks types.EvmHooks
|
|
|
|
// error from previous state operation
|
|
stateErr error
|
|
}
|
|
|
|
// 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,
|
|
fmk types.FeeMarketKeeper,
|
|
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,
|
|
bankKeeper: bankKeeper,
|
|
stakingKeeper: sk,
|
|
feeMarketKeeper: fmk,
|
|
storeKey: storeKey,
|
|
transientKey: transientKey,
|
|
tracer: tracer,
|
|
debug: debug,
|
|
stateErr: nil,
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
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
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Block Bloom
|
|
// Required by Web3 API.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// EmitBlockBloomEvent emit block bloom events
|
|
func (k Keeper) EmitBlockBloomEvent(ctx sdk.Context, bloom ethtypes.Bloom) {
|
|
ctx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
types.EventTypeBlockBloom,
|
|
sdk.NewAttribute(types.AttributeKeyEthereumBloom, string(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())
|
|
}
|
|
|
|
// SetTxIndexTransient set the index of processing transaction
|
|
func (k Keeper) SetTxIndexTransient(index uint64) {
|
|
store := k.Ctx().TransientStore(k.transientKey)
|
|
store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(index))
|
|
}
|
|
|
|
// 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()
|
|
k.SetTxIndexTransient(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
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// GetTxLogsTransient 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) GetTxLogsTransient(txHash common.Hash) []*ethtypes.Log {
|
|
store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientTxLogs)
|
|
|
|
// We store the logs with key equal to txHash.Bytes() | sdk.Uint64ToBigEndian(uint64(log.Index)),
|
|
// therefore, we set the end boundary(excluded) to txHash.Bytes() | uint64.Max -> []byte
|
|
end := txHash.Bytes()
|
|
end = append(end, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}...)
|
|
|
|
iter := store.Iterator(txHash.Bytes(), end)
|
|
defer iter.Close()
|
|
|
|
logs := []*ethtypes.Log{}
|
|
for ; iter.Valid(); iter.Next() {
|
|
var log types.Log
|
|
k.cdc.MustUnmarshal(iter.Value(), &log)
|
|
logs = append(logs, log.ToEthereum())
|
|
}
|
|
|
|
return logs
|
|
}
|
|
|
|
// SetLog sets the log for a transaction in the KVStore.
|
|
func (k Keeper) AddLogTransient(log *ethtypes.Log) {
|
|
store := prefix.NewStore(k.Ctx().TransientStore(k.transientKey), types.KeyPrefixTransientTxLogs)
|
|
|
|
key := log.TxHash.Bytes()
|
|
key = append(key, sdk.Uint64ToBigEndian(uint64(log.Index))...)
|
|
|
|
txIndexLog := types.NewLogFromEth(log)
|
|
bz := k.cdc.MustMarshal(txIndexLog)
|
|
store.Set(key, bz)
|
|
}
|
|
|
|
// 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))
|
|
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)
|
|
}
|
|
|
|
// SetHooks sets the hooks for the EVM module
|
|
func (k *Keeper) SetHooks(eh types.EvmHooks) *Keeper {
|
|
if k.hooks != nil {
|
|
panic("cannot set evm hooks twice")
|
|
}
|
|
|
|
k.hooks = eh
|
|
return k
|
|
}
|
|
|
|
// PostTxProcessing delegate the call to the hooks. If no hook has been registered, this function returns with a `nil` error
|
|
func (k *Keeper) PostTxProcessing(txHash common.Hash, logs []*ethtypes.Log) error {
|
|
if k.hooks == nil {
|
|
return nil
|
|
}
|
|
return k.hooks.PostTxProcessing(k.Ctx(), txHash, logs)
|
|
}
|