d6a64a275a
* Problem: import/export roundtrip test fail contract storage Closes: #589 - don't hash the key again in InitGenesis * changelog * try to fix estimate-gas undeterministics Closes #536
834 lines
23 KiB
Go
834 lines
23 KiB
Go
package keeper
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
ethermint "github.com/tharsis/ethermint/types"
|
|
"github.com/tharsis/ethermint/x/evm/types"
|
|
)
|
|
|
|
var _ vm.StateDB = &Keeper{}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Account
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// CreateAccount creates a new EthAccount instance from the provided address and
|
|
// sets the value to store. If an account with the given address already exists,
|
|
// this function also resets any preexisting code and storage associated with that
|
|
// address.
|
|
func (k *Keeper) CreateAccount(addr common.Address) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
ctx := k.Ctx()
|
|
account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
|
|
log := ""
|
|
if account == nil {
|
|
log = "account created"
|
|
} else {
|
|
log = "account overwritten"
|
|
k.ResetAccount(addr)
|
|
}
|
|
|
|
account = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr)
|
|
k.accountKeeper.SetAccount(ctx, account)
|
|
|
|
k.Logger(ctx).Debug(
|
|
log,
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Balance
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// AddBalance adds the given amount to the address balance coin by minting new
|
|
// coins and transferring them to the address. The coin denomination is obtained
|
|
// from the module parameters.
|
|
func (k *Keeper) AddBalance(addr common.Address, amount *big.Int) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
if amount.Sign() != 1 {
|
|
k.Logger(ctx).Debug(
|
|
"ignored non-positive amount addition",
|
|
"ethereum-address", addr.Hex(),
|
|
"amount", amount.Int64(),
|
|
)
|
|
return
|
|
}
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
params := k.GetParams(ctx)
|
|
|
|
// Coin denom and amount already validated
|
|
coins := sdk.Coins{
|
|
{
|
|
Denom: params.EvmDenom,
|
|
Amount: sdk.NewIntFromBigInt(amount),
|
|
},
|
|
}
|
|
|
|
if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins); err != nil {
|
|
k.Logger(ctx).Error(
|
|
"failed to mint coins when adding balance",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"error", err,
|
|
)
|
|
k.stateErr = err
|
|
return
|
|
}
|
|
|
|
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, cosmosAddr, coins); err != nil {
|
|
k.Logger(ctx).Error(
|
|
"failed to send from module to account when adding balance",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"error", err,
|
|
)
|
|
k.stateErr = err
|
|
return
|
|
}
|
|
|
|
k.Logger(ctx).Debug(
|
|
"balance addition",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
)
|
|
}
|
|
|
|
// SubBalance subtracts the given amount from the address balance by transferring the
|
|
// coins to an escrow account and then burning them. The coin denomination is obtained
|
|
// from the module parameters. This function performs a no-op if the amount is negative
|
|
// or the user doesn't have enough funds for the transfer.
|
|
func (k *Keeper) SubBalance(addr common.Address, amount *big.Int) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
if amount.Sign() != 1 {
|
|
k.Logger(ctx).Debug(
|
|
"ignored non-positive amount addition",
|
|
"ethereum-address", addr.Hex(),
|
|
"amount", amount.Int64(),
|
|
)
|
|
return
|
|
}
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
|
|
params := k.GetParams(ctx)
|
|
|
|
// Coin denom and amount already validated
|
|
coins := sdk.Coins{
|
|
{
|
|
Denom: params.EvmDenom,
|
|
Amount: sdk.NewIntFromBigInt(amount),
|
|
},
|
|
}
|
|
|
|
if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, cosmosAddr, types.ModuleName, coins); err != nil {
|
|
k.Logger(ctx).Debug(
|
|
"failed to send from account to module when subtracting balance",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"error", err,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
if err := k.bankKeeper.BurnCoins(ctx, types.ModuleName, coins); err != nil {
|
|
k.Logger(ctx).Error(
|
|
"failed to burn coins when subtracting balance",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"error", err,
|
|
)
|
|
k.stateErr = err
|
|
return
|
|
}
|
|
|
|
k.Logger(ctx).Debug(
|
|
"balance subtraction",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
)
|
|
}
|
|
|
|
// GetBalance returns the EVM denomination balance of the provided address. The
|
|
// denomination is obtained from the module parameters.
|
|
func (k *Keeper) GetBalance(addr common.Address) *big.Int {
|
|
if k.HasStateError() {
|
|
return big.NewInt(0)
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
params := k.GetParams(ctx)
|
|
balance := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
|
|
|
|
return balance.Amount.BigInt()
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Nonce
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// GetNonce retrieves the account with the given address and returns the tx
|
|
// sequence (i.e nonce). The function performs a no-op if the account is not found.
|
|
func (k *Keeper) GetNonce(addr common.Address) uint64 {
|
|
if k.HasStateError() {
|
|
return 0
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
nonce, err := k.accountKeeper.GetSequence(ctx, cosmosAddr)
|
|
if err != nil {
|
|
k.Logger(ctx).Error(
|
|
"account not found",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"error", err,
|
|
)
|
|
// since adding state error here will break some logic in the go-ethereum (unwanted panic), no
|
|
// state error will be store here
|
|
// Refer panic: https://github.com/ethereum/go-ethereum/blob/991384a7f6719e1125ca0be7fb27d0c4d1c5d2d3/core/vm/operations_acl.go#L66
|
|
}
|
|
|
|
return nonce
|
|
}
|
|
|
|
// SetNonce sets the given nonce as the sequence of the address' account. If the
|
|
// account doesn't exist, a new one will be created from the address.
|
|
func (k *Keeper) SetNonce(addr common.Address, nonce uint64) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
|
|
if account == nil {
|
|
k.Logger(ctx).Debug(
|
|
"account not found",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
)
|
|
|
|
// create address if it doesn't exist
|
|
account = k.accountKeeper.NewAccountWithAddress(ctx, cosmosAddr)
|
|
}
|
|
|
|
if err := account.SetSequence(nonce); err != nil {
|
|
k.Logger(ctx).Error(
|
|
"failed to set nonce",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"nonce", nonce,
|
|
"error", err,
|
|
)
|
|
k.stateErr = err
|
|
return
|
|
}
|
|
|
|
k.accountKeeper.SetAccount(ctx, account)
|
|
|
|
k.Logger(ctx).Debug(
|
|
"nonce set",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"nonce", nonce,
|
|
)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Code
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// GetCodeHash fetches the account from the store and returns its code hash. If the account doesn't
|
|
// exist or is not an EthAccount type, GetCodeHash returns the empty code hash value.
|
|
func (k *Keeper) GetCodeHash(addr common.Address) common.Hash {
|
|
if k.HasStateError() {
|
|
return common.Hash{}
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
|
|
account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
|
|
if account == nil {
|
|
return common.BytesToHash(types.EmptyCodeHash)
|
|
}
|
|
|
|
ethAccount, isEthAccount := account.(*ethermint.EthAccount)
|
|
if !isEthAccount {
|
|
return common.BytesToHash(types.EmptyCodeHash)
|
|
}
|
|
|
|
return common.HexToHash(ethAccount.CodeHash)
|
|
}
|
|
|
|
// GetCode returns the code byte array associated with the given address.
|
|
// If the code hash from the account is empty, this function returns nil.
|
|
func (k *Keeper) GetCode(addr common.Address) []byte {
|
|
if k.HasStateError() {
|
|
return nil
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
hash := k.GetCodeHash(addr)
|
|
|
|
if bytes.Equal(hash.Bytes(), common.BytesToHash(types.EmptyCodeHash).Bytes()) {
|
|
return nil
|
|
}
|
|
|
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode)
|
|
code := store.Get(hash.Bytes())
|
|
|
|
if len(code) == 0 {
|
|
k.Logger(ctx).Debug(
|
|
"code not found",
|
|
"ethereum-address", addr.Hex(),
|
|
"code-hash", hash.Hex(),
|
|
)
|
|
}
|
|
|
|
return code
|
|
}
|
|
|
|
// SetCode stores the code byte array to the application KVStore and sets the
|
|
// code hash to the given account. The code is deleted from the store if it is empty.
|
|
func (k *Keeper) SetCode(addr common.Address, code []byte) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
if bytes.Equal(code, types.EmptyCodeHash) {
|
|
k.Logger(ctx).Debug("passed in EmptyCodeHash, but expected empty code")
|
|
}
|
|
hash := crypto.Keccak256Hash(code)
|
|
|
|
// update account code hash
|
|
account := k.accountKeeper.GetAccount(ctx, addr.Bytes())
|
|
if account == nil {
|
|
account = k.accountKeeper.NewAccountWithAddress(ctx, addr.Bytes())
|
|
k.accountKeeper.SetAccount(ctx, account)
|
|
}
|
|
|
|
ethAccount, isEthAccount := account.(*ethermint.EthAccount)
|
|
if !isEthAccount {
|
|
k.Logger(ctx).Error(
|
|
"invalid account type",
|
|
"ethereum-address", addr.Hex(),
|
|
"code-hash", hash.Hex(),
|
|
)
|
|
k.stateErr = fmt.Errorf("invalid account type, ethereum-address %v, code-hash %v", addr.Hex(), hash.Hex())
|
|
return
|
|
}
|
|
|
|
ethAccount.CodeHash = hash.Hex()
|
|
k.accountKeeper.SetAccount(ctx, ethAccount)
|
|
|
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixCode)
|
|
|
|
action := "updated"
|
|
|
|
// store or delete code
|
|
if len(code) == 0 {
|
|
store.Delete(hash.Bytes())
|
|
action = "deleted"
|
|
} else {
|
|
store.Set(hash.Bytes(), code)
|
|
}
|
|
|
|
k.Logger(ctx).Debug(
|
|
fmt.Sprintf("code %s", action),
|
|
"ethereum-address", addr.Hex(),
|
|
"code-hash", hash.Hex(),
|
|
)
|
|
}
|
|
|
|
// GetCodeSize returns the size of the contract code associated with this object,
|
|
// or zero if none.
|
|
func (k *Keeper) GetCodeSize(addr common.Address) int {
|
|
if k.HasStateError() {
|
|
return 0
|
|
}
|
|
|
|
code := k.GetCode(addr)
|
|
return len(code)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Refund
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// NOTE: gas refunded needs to be tracked and stored in a separate variable in
|
|
// order to add it subtract/add it from/to the gas used value after the EVM
|
|
// execution has finalized. The refund value is cleared on every transaction and
|
|
// at the end of every block.
|
|
|
|
// AddRefund adds the given amount of gas to the refund transient value.
|
|
func (k *Keeper) AddRefund(gas uint64) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
refund := k.GetRefund()
|
|
|
|
refund += gas
|
|
|
|
store := ctx.TransientStore(k.transientKey)
|
|
store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
|
|
}
|
|
|
|
// SubRefund subtracts the given amount of gas from the transient refund value. This function
|
|
// will panic if gas amount is greater than the stored refund.
|
|
func (k *Keeper) SubRefund(gas uint64) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
refund := k.GetRefund()
|
|
|
|
if gas > refund {
|
|
// TODO: (@fedekunze) set to 0?? Geth panics here
|
|
panic("refund counter below zero")
|
|
}
|
|
|
|
refund -= gas
|
|
|
|
store := ctx.TransientStore(k.transientKey)
|
|
store.Set(types.KeyPrefixTransientRefund, sdk.Uint64ToBigEndian(refund))
|
|
}
|
|
|
|
// GetRefund returns the amount of gas available for return after the tx execution
|
|
// finalizes. This value is reset to 0 on every transaction.
|
|
func (k *Keeper) GetRefund() uint64 {
|
|
if k.HasStateError() {
|
|
return 0
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
store := ctx.TransientStore(k.transientKey)
|
|
|
|
bz := store.Get(types.KeyPrefixTransientRefund)
|
|
if len(bz) == 0 {
|
|
return 0
|
|
}
|
|
|
|
return sdk.BigEndianToUint64(bz)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// State
|
|
// ----------------------------------------------------------------------------
|
|
|
|
func doGetState(ctx sdk.Context, storeKey sdk.StoreKey, addr common.Address, key common.Hash) common.Hash {
|
|
store := prefix.NewStore(ctx.KVStore(storeKey), types.AddressStoragePrefix(addr))
|
|
|
|
value := store.Get(key.Bytes())
|
|
if len(value) == 0 {
|
|
return common.Hash{}
|
|
}
|
|
|
|
return common.BytesToHash(value)
|
|
}
|
|
|
|
// GetCommittedState returns the value set in store for the given key hash. If the key is not registered
|
|
// this function returns the empty hash.
|
|
func (k *Keeper) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
|
if k.HasStateError() {
|
|
return common.Hash{}
|
|
}
|
|
|
|
return doGetState(k.ctxStack.initialCtx, k.storeKey, addr, hash)
|
|
}
|
|
|
|
// GetState returns the committed state for the given key hash, as all changes are committed directly
|
|
// to the KVStore.
|
|
func (k *Keeper) GetState(addr common.Address, hash common.Hash) common.Hash {
|
|
if k.HasStateError() {
|
|
return common.Hash{}
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
return doGetState(ctx, k.storeKey, addr, hash)
|
|
}
|
|
|
|
// SetState sets the given hashes (key, value) to the KVStore. If the value hash is empty, this
|
|
// function deletes the key from the store.
|
|
func (k *Keeper) SetState(addr common.Address, key, value common.Hash) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AddressStoragePrefix(addr))
|
|
|
|
action := "updated"
|
|
if ethermint.IsEmptyHash(value.Hex()) {
|
|
store.Delete(key.Bytes())
|
|
action = "deleted"
|
|
} else {
|
|
store.Set(key.Bytes(), value.Bytes())
|
|
}
|
|
|
|
k.Logger(ctx).Debug(
|
|
fmt.Sprintf("state %s", action),
|
|
"ethereum-address", addr.Hex(),
|
|
"key", key.Hex(),
|
|
)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Suicide
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Suicide marks the given account as suicided and clears the account balance of
|
|
// the EVM tokens.
|
|
func (k *Keeper) Suicide(addr common.Address) bool {
|
|
if k.HasStateError() {
|
|
return false
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
prev := k.HasSuicided(addr)
|
|
if prev {
|
|
return true
|
|
}
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
|
|
_, err := k.ClearBalance(cosmosAddr)
|
|
if err != nil {
|
|
k.Logger(ctx).Error(
|
|
"failed to subtract balance on suicide",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
"error", err,
|
|
)
|
|
k.stateErr = err
|
|
return false
|
|
}
|
|
|
|
// TODO: (@fedekunze) do we also need to delete the storage state and the code?
|
|
k.setSuicided(ctx, addr)
|
|
|
|
k.Logger(ctx).Debug(
|
|
"account suicided",
|
|
"ethereum-address", addr.Hex(),
|
|
"cosmos-address", cosmosAddr.String(),
|
|
)
|
|
|
|
return true
|
|
}
|
|
|
|
// setSuicided sets a single byte to the transient store and marks the address as suicided
|
|
func (k Keeper) setSuicided(ctx sdk.Context, addr common.Address) {
|
|
store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
|
|
store.Set(addr.Bytes(), []byte{1})
|
|
}
|
|
|
|
// HasSuicided queries the transient store to check if the account has been marked as suicided in the
|
|
// current block. Accounts that are suicided will be returned as non-nil during queries and "cleared"
|
|
// after the block has been committed.
|
|
func (k *Keeper) HasSuicided(addr common.Address) bool {
|
|
if k.HasStateError() {
|
|
return false
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
store := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientSuicided)
|
|
return store.Has(addr.Bytes())
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Account Exist / Empty
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Exist returns true if the given account exists in store or if it has been
|
|
// marked as suicided in the transient store.
|
|
func (k *Keeper) Exist(addr common.Address) bool {
|
|
if k.HasStateError() {
|
|
return false
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
// return true if the account has suicided
|
|
if k.HasSuicided(addr) {
|
|
return true
|
|
}
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
|
|
return account != nil
|
|
}
|
|
|
|
// Empty returns true if the address meets the following conditions:
|
|
// - nonce is 0
|
|
// - balance amount for evm denom is 0
|
|
// - account code hash is empty
|
|
//
|
|
// Non-ethereum accounts are considered not empty
|
|
func (k *Keeper) Empty(addr common.Address) bool {
|
|
if k.HasStateError() {
|
|
return false
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
nonce := uint64(0)
|
|
codeHash := types.EmptyCodeHash
|
|
|
|
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
account := k.accountKeeper.GetAccount(ctx, cosmosAddr)
|
|
|
|
if account != nil {
|
|
nonce = account.GetSequence()
|
|
ethAccount, isEthAccount := account.(*ethermint.EthAccount)
|
|
if !isEthAccount {
|
|
return false
|
|
}
|
|
|
|
codeHash = common.HexToHash(ethAccount.CodeHash).Bytes()
|
|
}
|
|
|
|
balance := k.GetBalance(addr)
|
|
hasZeroBalance := balance.Sign() == 0
|
|
hasEmptyCodeHash := bytes.Equal(codeHash, types.EmptyCodeHash)
|
|
|
|
return hasZeroBalance && nonce == 0 && hasEmptyCodeHash
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Access List
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// PrepareAccessList handles the preparatory steps for executing a state transition with
|
|
// regards to both EIP-2929 and EIP-2930:
|
|
//
|
|
// - Add sender to access list (2929)
|
|
// - Add destination to access list (2929)
|
|
// - Add precompiles to access list (2929)
|
|
// - Add the contents of the optional tx access list (2930)
|
|
//
|
|
// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
|
|
func (k *Keeper) PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses ethtypes.AccessList) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
k.AddAddressToAccessList(sender)
|
|
if dest != nil {
|
|
k.AddAddressToAccessList(*dest)
|
|
// If it's a create-tx, the destination will be added inside evm.create
|
|
}
|
|
for _, addr := range precompiles {
|
|
k.AddAddressToAccessList(addr)
|
|
}
|
|
for _, tuple := range txAccesses {
|
|
k.AddAddressToAccessList(tuple.Address)
|
|
for _, key := range tuple.StorageKeys {
|
|
k.AddSlotToAccessList(tuple.Address, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddressInAccessList returns true if the address is registered on the transient store.
|
|
func (k *Keeper) AddressInAccessList(addr common.Address) bool {
|
|
if k.HasStateError() {
|
|
return false
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
|
|
return ts.Has(addr.Bytes())
|
|
}
|
|
|
|
// SlotInAccessList checks if the address and the slots are registered in the transient store
|
|
func (k *Keeper) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk, slotOk bool) {
|
|
if k.HasStateError() {
|
|
return false, false
|
|
}
|
|
|
|
addressOk = k.AddressInAccessList(addr)
|
|
slotOk = k.addressSlotInAccessList(addr, slot)
|
|
return addressOk, slotOk
|
|
}
|
|
|
|
// addressSlotInAccessList returns true if the address's slot is registered on the transient store.
|
|
func (k *Keeper) addressSlotInAccessList(addr common.Address, slot common.Hash) bool {
|
|
ctx := k.Ctx()
|
|
ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
|
|
key := append(addr.Bytes(), slot.Bytes()...)
|
|
return ts.Has(key)
|
|
}
|
|
|
|
// AddAddressToAccessList adds the given address to the access list. If the address is already
|
|
// in the access list, this function performs a no-op.
|
|
func (k *Keeper) AddAddressToAccessList(addr common.Address) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
if k.AddressInAccessList(addr) {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListAddress)
|
|
ts.Set(addr.Bytes(), []byte{0x1})
|
|
}
|
|
|
|
// AddSlotToAccessList adds the given (address, slot) to the access list. If the address and slot are
|
|
// already in the access list, this function performs a no-op.
|
|
func (k *Keeper) AddSlotToAccessList(addr common.Address, slot common.Hash) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
k.AddAddressToAccessList(addr)
|
|
if k.addressSlotInAccessList(addr, slot) {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
ts := prefix.NewStore(ctx.TransientStore(k.transientKey), types.KeyPrefixTransientAccessListSlot)
|
|
key := append(addr.Bytes(), slot.Bytes()...)
|
|
ts.Set(key, []byte{0x1})
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Snapshotting
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// Snapshot return the index in the cached context stack
|
|
func (k *Keeper) Snapshot() int {
|
|
if k.HasStateError() {
|
|
return 0
|
|
}
|
|
|
|
return k.ctxStack.Snapshot()
|
|
}
|
|
|
|
// RevertToSnapshot pop all the cached contexts after(including) the snapshot
|
|
func (k *Keeper) RevertToSnapshot(target int) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
k.ctxStack.RevertToSnapshot(target)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Log
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// AddLog appends the given ethereum Log to the list of Logs associated with the transaction hash kept in the current
|
|
// context. This function also fills in the tx hash, block hash, tx index and log index fields before setting the log
|
|
// to store.
|
|
func (k *Keeper) AddLog(log *ethtypes.Log) {
|
|
if k.HasStateError() {
|
|
return
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
|
|
log.BlockHash = common.BytesToHash(ctx.HeaderHash())
|
|
log.TxIndex = uint(k.GetTxIndexTransient())
|
|
log.TxHash = k.GetTxHashTransient()
|
|
|
|
log.Index = uint(k.GetLogSizeTransient())
|
|
k.IncreaseLogSizeTransient()
|
|
k.AddLogTransient(log)
|
|
|
|
k.Logger(ctx).Debug(
|
|
"log added",
|
|
"tx-hash-ethereum", log.TxHash.Hex(),
|
|
"log-index", int(log.Index),
|
|
)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Trie
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// AddPreimage performs a no-op since the EnablePreimageRecording flag is disabled
|
|
// on the vm.Config during state transitions. No store trie preimages are written
|
|
// to the database.
|
|
func (k *Keeper) AddPreimage(_ common.Hash, _ []byte) {}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Iterator
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// ForEachStorage uses the store iterator to iterate over all the state keys and perform a callback
|
|
// function on each of them.
|
|
func (k *Keeper) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error {
|
|
if k.HasStateError() {
|
|
return k.stateErr
|
|
}
|
|
|
|
ctx := k.Ctx()
|
|
store := ctx.KVStore(k.storeKey)
|
|
prefix := types.AddressStoragePrefix(addr)
|
|
|
|
iterator := sdk.KVStorePrefixIterator(store, prefix)
|
|
defer iterator.Close()
|
|
|
|
for ; iterator.Valid(); iterator.Next() {
|
|
|
|
// TODO: check if the key prefix needs to be trimmed
|
|
key := common.BytesToHash(iterator.Key())
|
|
value := common.BytesToHash(iterator.Value())
|
|
|
|
// check if iteration stops
|
|
if cb(key, value) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasStateError return the previous error for any state operations
|
|
func (k *Keeper) HasStateError() bool {
|
|
return k.stateErr != nil
|
|
}
|
|
|
|
// ClearStateError reset the previous state operation error to nil
|
|
func (k *Keeper) ClearStateError() {
|
|
k.stateErr = nil
|
|
}
|