29d3abcf09
* Reuse cosmos-sdk client library to create keyring Extracted from https://github.com/evmos/ethermint/pull/1168 Cleanup cmd code for easier to migration to cosmos-sdk 0.46 * Update cosmos-sdk v0.46 prepare for implementing cosmos-sdk feemarket and tx prioritization changelog refactor cmd use sdkmath fix lint fix unit tests fix unit test genesis fix unit tests fix unit test env setup fix unit tests fix unit tests register PrivKey impl fix extension options fix lint fix unit tests make HandlerOption.Validate private gofumpt fix msg response decoding fix sim test bump cosmos-sdk version fix sim test sdk 46 fix unit test fix unit tests update ibc-go
528 lines
20 KiB
Go
528 lines
20 KiB
Go
package keeper
|
|
|
|
import (
|
|
"math"
|
|
"math/big"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
|
|
tmtypes "github.com/tendermint/tendermint/types"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
|
|
ethermint "github.com/evmos/ethermint/types"
|
|
"github.com/evmos/ethermint/x/evm/statedb"
|
|
"github.com/evmos/ethermint/x/evm/types"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
// GasToRefund calculates the amount of gas the state machine should refund to the sender. It is
|
|
// capped by the refund quotient value.
|
|
// Note: do not pass 0 to refundQuotient
|
|
func GasToRefund(availableRefund, gasConsumed, refundQuotient uint64) uint64 {
|
|
// Apply refund counter
|
|
refund := gasConsumed / refundQuotient
|
|
if refund > availableRefund {
|
|
return availableRefund
|
|
}
|
|
return refund
|
|
}
|
|
|
|
// EVMConfig creates the EVMConfig based on current state
|
|
func (k *Keeper) EVMConfig(ctx sdk.Context) (*types.EVMConfig, error) {
|
|
params := k.GetParams(ctx)
|
|
ethCfg := params.ChainConfig.EthereumConfig(k.eip155ChainID)
|
|
|
|
// get the coinbase address from the block proposer
|
|
coinbase, err := k.GetCoinbaseAddress(ctx)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed to obtain coinbase address")
|
|
}
|
|
|
|
baseFee := k.GetBaseFee(ctx, ethCfg)
|
|
return &types.EVMConfig{
|
|
Params: params,
|
|
ChainConfig: ethCfg,
|
|
CoinBase: coinbase,
|
|
BaseFee: baseFee,
|
|
}, nil
|
|
}
|
|
|
|
// TxConfig loads `TxConfig` from current transient storage
|
|
func (k *Keeper) TxConfig(ctx sdk.Context, txHash common.Hash) statedb.TxConfig {
|
|
return statedb.NewTxConfig(
|
|
common.BytesToHash(ctx.HeaderHash()), // BlockHash
|
|
txHash, // TxHash
|
|
uint(k.GetTxIndexTransient(ctx)), // TxIndex
|
|
uint(k.GetLogSizeTransient(ctx)), // LogIndex
|
|
)
|
|
}
|
|
|
|
// NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters
|
|
// (ChainConfig and module Params). It additionally sets the validator operator address as the
|
|
// coinbase address to make it available for the COINBASE opcode, even though there is no
|
|
// beneficiary of the coinbase transaction (since we're not mining).
|
|
func (k *Keeper) NewEVM(
|
|
ctx sdk.Context,
|
|
msg core.Message,
|
|
cfg *types.EVMConfig,
|
|
tracer vm.EVMLogger,
|
|
stateDB vm.StateDB,
|
|
) *vm.EVM {
|
|
blockCtx := vm.BlockContext{
|
|
CanTransfer: core.CanTransfer,
|
|
Transfer: core.Transfer,
|
|
GetHash: k.GetHashFn(ctx),
|
|
Coinbase: cfg.CoinBase,
|
|
GasLimit: ethermint.BlockGasLimit(ctx),
|
|
BlockNumber: big.NewInt(ctx.BlockHeight()),
|
|
Time: big.NewInt(ctx.BlockHeader().Time.Unix()),
|
|
Difficulty: big.NewInt(0), // unused. Only required in PoW context
|
|
BaseFee: cfg.BaseFee,
|
|
}
|
|
|
|
txCtx := core.NewEVMTxContext(msg)
|
|
if tracer == nil {
|
|
tracer = k.Tracer(ctx, msg, cfg.ChainConfig)
|
|
}
|
|
vmConfig := k.VMConfig(ctx, msg, cfg, tracer)
|
|
return vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig)
|
|
}
|
|
|
|
// VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the
|
|
// module parameters. The config generated uses the default JumpTable from the EVM.
|
|
func (k Keeper) VMConfig(ctx sdk.Context, msg core.Message, cfg *types.EVMConfig, tracer vm.EVMLogger) vm.Config {
|
|
noBaseFee := true
|
|
if types.IsLondon(cfg.ChainConfig, ctx.BlockHeight()) {
|
|
noBaseFee = k.feeMarketKeeper.GetParams(ctx).NoBaseFee
|
|
}
|
|
|
|
var debug bool
|
|
if _, ok := tracer.(types.NoOpTracer); !ok {
|
|
debug = true
|
|
}
|
|
|
|
return vm.Config{
|
|
Debug: debug,
|
|
Tracer: tracer,
|
|
NoBaseFee: noBaseFee,
|
|
ExtraEips: cfg.Params.EIPs(),
|
|
}
|
|
}
|
|
|
|
// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases:
|
|
// 1. The requested height matches the current height from context (and thus same epoch number)
|
|
// 2. The requested height is from an previous height from the same chain epoch
|
|
// 3. The requested height is from a height greater than the latest one
|
|
func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
|
|
return func(height uint64) common.Hash {
|
|
h, err := ethermint.SafeInt64(height)
|
|
if err != nil {
|
|
k.Logger(ctx).Error("failed to cast height to int64", "error", err)
|
|
return common.Hash{}
|
|
}
|
|
|
|
switch {
|
|
case ctx.BlockHeight() == h:
|
|
// Case 1: The requested height matches the one from the context so we can retrieve the header
|
|
// hash directly from the context.
|
|
// Note: The headerHash is only set at begin block, it will be nil in case of a query context
|
|
headerHash := ctx.HeaderHash()
|
|
if len(headerHash) != 0 {
|
|
return common.BytesToHash(headerHash)
|
|
}
|
|
|
|
// only recompute the hash if not set (eg: checkTxState)
|
|
contextBlockHeader := ctx.BlockHeader()
|
|
header, err := tmtypes.HeaderFromProto(&contextBlockHeader)
|
|
if err != nil {
|
|
k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err)
|
|
return common.Hash{}
|
|
}
|
|
|
|
headerHash = header.Hash()
|
|
return common.BytesToHash(headerHash)
|
|
|
|
case ctx.BlockHeight() > h:
|
|
// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
|
|
// current chain epoch. This only applies if the current height is greater than the requested height.
|
|
histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, h)
|
|
if !found {
|
|
k.Logger(ctx).Debug("historical info not found", "height", h)
|
|
return common.Hash{}
|
|
}
|
|
|
|
header, err := tmtypes.HeaderFromProto(&histInfo.Header)
|
|
if err != nil {
|
|
k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err)
|
|
return common.Hash{}
|
|
}
|
|
|
|
return common.BytesToHash(header.Hash())
|
|
default:
|
|
// Case 3: heights greater than the current one returns an empty hash.
|
|
return common.Hash{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will
|
|
// only be persisted (committed) to the underlying KVStore if the transaction does not fail.
|
|
//
|
|
// Gas tracking
|
|
//
|
|
// Ethereum consumes gas according to the EVM opcodes instead of general reads and writes to store. Because of this, the
|
|
// state transition needs to ignore the SDK gas consumption mechanism defined by the GasKVStore and instead consume the
|
|
// amount of gas used by the VM execution. The amount of gas used is tracked by the EVM and returned in the execution
|
|
// result.
|
|
//
|
|
// Prior to the execution, the starting tx gas meter is saved and replaced with an infinite gas meter in a new context
|
|
// in order to ignore the SDK gas consumption config values (read, write, has, delete).
|
|
// After the execution, the gas used from the message execution will be added to the starting gas consumed, taking into
|
|
// consideration the amount of gas returned. Finally, the context is updated with the EVM gas consumed value prior to
|
|
// returning.
|
|
//
|
|
// For relevant discussion see: https://github.com/cosmos/cosmos-sdk/discussions/9072
|
|
func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) {
|
|
var (
|
|
bloom *big.Int
|
|
bloomReceipt ethtypes.Bloom
|
|
)
|
|
|
|
cfg, err := k.EVMConfig(ctx)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed to load evm config")
|
|
}
|
|
txConfig := k.TxConfig(ctx, tx.Hash())
|
|
|
|
// get the signer according to the chain rules from the config and block height
|
|
signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight()))
|
|
msg, err := tx.AsMessage(signer, cfg.BaseFee)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed to return ethereum transaction as core message")
|
|
}
|
|
|
|
// snapshot to contain the tx processing and post processing in same scope
|
|
var commit func()
|
|
tmpCtx := ctx
|
|
if k.hooks != nil {
|
|
// Create a cache context to revert state when tx hooks fails,
|
|
// the cache context is only committed when both tx and hooks executed successfully.
|
|
// Didn't use `Snapshot` because the context stack has exponential complexity on certain operations,
|
|
// thus restricted to be used only inside `ApplyMessage`.
|
|
tmpCtx, commit = ctx.CacheContext()
|
|
}
|
|
|
|
// pass true to commit the StateDB
|
|
res, err := k.ApplyMessageWithConfig(tmpCtx, msg, nil, true, cfg, txConfig)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed to apply ethereum core message")
|
|
}
|
|
|
|
logs := types.LogsToEthereum(res.Logs)
|
|
|
|
// Compute block bloom filter
|
|
if len(logs) > 0 {
|
|
bloom = k.GetBlockBloomTransient(ctx)
|
|
bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs)))
|
|
bloomReceipt = ethtypes.BytesToBloom(bloom.Bytes())
|
|
}
|
|
|
|
cumulativeGasUsed := res.GasUsed
|
|
if ctx.BlockGasMeter() != nil {
|
|
limit := ctx.BlockGasMeter().Limit()
|
|
consumed := ctx.BlockGasMeter().GasConsumed()
|
|
cumulativeGasUsed = uint64(math.Min(float64(cumulativeGasUsed+consumed), float64(limit)))
|
|
}
|
|
|
|
var contractAddr common.Address
|
|
if msg.To() == nil {
|
|
contractAddr = crypto.CreateAddress(msg.From(), msg.Nonce())
|
|
}
|
|
|
|
receipt := ðtypes.Receipt{
|
|
Type: tx.Type(),
|
|
PostState: nil, // TODO: intermediate state root
|
|
CumulativeGasUsed: cumulativeGasUsed,
|
|
Bloom: bloomReceipt,
|
|
Logs: logs,
|
|
TxHash: txConfig.TxHash,
|
|
ContractAddress: contractAddr,
|
|
GasUsed: res.GasUsed,
|
|
BlockHash: txConfig.BlockHash,
|
|
BlockNumber: big.NewInt(ctx.BlockHeight()),
|
|
TransactionIndex: txConfig.TxIndex,
|
|
}
|
|
|
|
if !res.Failed() {
|
|
receipt.Status = ethtypes.ReceiptStatusSuccessful
|
|
// Only call hooks if tx executed successfully.
|
|
if err = k.PostTxProcessing(tmpCtx, msg, receipt); err != nil {
|
|
// If hooks return error, revert the whole tx.
|
|
res.VmError = types.ErrPostTxProcessing.Error()
|
|
k.Logger(ctx).Error("tx post processing failed", "error", err)
|
|
|
|
// If the tx failed in post processing hooks, we should clear the logs
|
|
res.Logs = nil
|
|
} else if commit != nil {
|
|
// PostTxProcessing is successful, commit the tmpCtx
|
|
commit()
|
|
// Since the post processing can alter the log, we need to update the result
|
|
res.Logs = types.NewLogsFromEth(receipt.Logs)
|
|
ctx.EventManager().EmitEvents(tmpCtx.EventManager().Events())
|
|
}
|
|
}
|
|
|
|
// refund gas in order to match the Ethereum gas consumption instead of the default SDK one.
|
|
if err = k.RefundGas(ctx, msg, msg.Gas()-res.GasUsed, cfg.Params.EvmDenom); err != nil {
|
|
return nil, sdkerrors.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From())
|
|
}
|
|
|
|
if len(receipt.Logs) > 0 {
|
|
// Update transient block bloom filter
|
|
k.SetBlockBloomTransient(ctx, receipt.Bloom.Big())
|
|
k.SetLogSizeTransient(ctx, uint64(txConfig.LogIndex)+uint64(len(receipt.Logs)))
|
|
}
|
|
|
|
k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1)
|
|
|
|
totalGasUsed, err := k.AddTransientGasUsed(ctx, res.GasUsed)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed to add transient gas used")
|
|
}
|
|
|
|
// reset the gas meter for current cosmos transaction
|
|
k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed)
|
|
return res, nil
|
|
}
|
|
|
|
// ApplyMessageWithConfig computes the new state by applying the given message against the existing state.
|
|
// If the message fails, the VM execution error with the reason will be returned to the client
|
|
// and the transaction won't be committed to the store.
|
|
//
|
|
// Reverted state
|
|
//
|
|
// The snapshot and rollback are supported by the `statedb.StateDB`.
|
|
//
|
|
// Different Callers
|
|
//
|
|
// It's called in three scenarios:
|
|
// 1. `ApplyTransaction`, in the transaction processing flow.
|
|
// 2. `EthCall/EthEstimateGas` grpc query handler.
|
|
// 3. Called by other native modules directly.
|
|
//
|
|
// Prechecks and Preprocessing
|
|
//
|
|
// All relevant state transition prechecks for the MsgEthereumTx are performed on the AnteHandler,
|
|
// prior to running the transaction against the state. The prechecks run are the following:
|
|
//
|
|
// 1. the nonce of the message caller is correct
|
|
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
|
|
// 3. the amount of gas required is available in the block
|
|
// 4. the purchased gas is enough to cover intrinsic usage
|
|
// 5. there is no overflow when calculating intrinsic gas
|
|
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
|
//
|
|
// The preprocessing steps performed by the AnteHandler are:
|
|
//
|
|
// 1. set up the initial access list (iff fork > Berlin)
|
|
//
|
|
// Tracer parameter
|
|
//
|
|
// It should be a `vm.Tracer` object or nil, if pass `nil`, it'll create a default one based on keeper options.
|
|
//
|
|
// Commit parameter
|
|
//
|
|
// If commit is true, the `StateDB` will be committed, otherwise discarded.
|
|
func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool, cfg *types.EVMConfig, txConfig statedb.TxConfig) (*types.MsgEthereumTxResponse, error) {
|
|
var (
|
|
ret []byte // return bytes from evm execution
|
|
vmErr error // vm errors do not effect consensus and are therefore not assigned to err
|
|
)
|
|
|
|
// return error if contract creation or call are disabled through governance
|
|
if !cfg.Params.EnableCreate && msg.To() == nil {
|
|
return nil, sdkerrors.Wrap(types.ErrCreateDisabled, "failed to create new contract")
|
|
} else if !cfg.Params.EnableCall && msg.To() != nil {
|
|
return nil, sdkerrors.Wrap(types.ErrCallDisabled, "failed to call contract")
|
|
}
|
|
|
|
stateDB := statedb.New(ctx, k, txConfig)
|
|
evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB)
|
|
|
|
leftoverGas := msg.Gas()
|
|
// Allow the tracer captures the tx level events, mainly the gas consumption.
|
|
if evm.Config.Debug {
|
|
evm.Config.Tracer.CaptureTxStart(leftoverGas)
|
|
defer func() {
|
|
evm.Config.Tracer.CaptureTxEnd(leftoverGas)
|
|
}()
|
|
}
|
|
|
|
sender := vm.AccountRef(msg.From())
|
|
contractCreation := msg.To() == nil
|
|
isLondon := cfg.ChainConfig.IsLondon(evm.Context.BlockNumber)
|
|
|
|
intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, cfg.ChainConfig, contractCreation)
|
|
if err != nil {
|
|
// should have already been checked on Ante Handler
|
|
return nil, sdkerrors.Wrap(err, "intrinsic gas failed")
|
|
}
|
|
|
|
// Should check again even if it is checked on Ante Handler, because eth_call don't go through Ante Handler.
|
|
if leftoverGas < intrinsicGas {
|
|
// eth_estimateGas will check for this exact error
|
|
return nil, sdkerrors.Wrap(core.ErrIntrinsicGas, "apply message")
|
|
}
|
|
leftoverGas -= intrinsicGas
|
|
|
|
// access list preparation is moved from ante handler to here, because it's needed when `ApplyMessage` is called
|
|
// under contexts where ante handlers are not run, for example `eth_call` and `eth_estimateGas`.
|
|
if rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil); rules.IsBerlin {
|
|
stateDB.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
|
|
}
|
|
|
|
if contractCreation {
|
|
// take over the nonce management from evm:
|
|
// - reset sender's nonce to msg.Nonce() before calling evm.
|
|
// - increase sender's nonce by one no matter the result.
|
|
stateDB.SetNonce(sender.Address(), msg.Nonce())
|
|
ret, _, leftoverGas, vmErr = evm.Create(sender, msg.Data(), leftoverGas, msg.Value())
|
|
stateDB.SetNonce(sender.Address(), msg.Nonce()+1)
|
|
} else {
|
|
ret, leftoverGas, vmErr = evm.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value())
|
|
}
|
|
|
|
refundQuotient := params.RefundQuotient
|
|
|
|
// After EIP-3529: refunds are capped to gasUsed / 5
|
|
if isLondon {
|
|
refundQuotient = params.RefundQuotientEIP3529
|
|
}
|
|
|
|
// calculate gas refund
|
|
if msg.Gas() < leftoverGas {
|
|
return nil, sdkerrors.Wrap(types.ErrGasOverflow, "apply message")
|
|
}
|
|
// refund gas
|
|
leftoverGas += GasToRefund(stateDB.GetRefund(), msg.Gas()-leftoverGas, refundQuotient)
|
|
|
|
// EVM execution error needs to be available for the JSON-RPC client
|
|
var vmError string
|
|
if vmErr != nil {
|
|
vmError = vmErr.Error()
|
|
}
|
|
|
|
// The dirty states in `StateDB` is either committed or discarded after return
|
|
if commit {
|
|
if err := stateDB.Commit(); err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed to commit stateDB")
|
|
}
|
|
}
|
|
|
|
// calculate a minimum amount of gas to be charged to sender if GasLimit
|
|
// is considerably higher than GasUsed to stay more aligned with Tendermint gas mechanics
|
|
// for more info https://github.com/evmos/ethermint/issues/1085
|
|
gasLimit := sdk.NewDec(int64(msg.Gas()))
|
|
minGasMultiplier := k.GetMinGasMultiplier(ctx)
|
|
minimumGasUsed := gasLimit.Mul(minGasMultiplier)
|
|
|
|
if msg.Gas() < leftoverGas {
|
|
return nil, sdkerrors.Wrapf(types.ErrGasOverflow, "message gas limit < leftover gas (%d < %d)", msg.Gas(), leftoverGas)
|
|
}
|
|
temporaryGasUsed := msg.Gas() - leftoverGas
|
|
gasUsed := sdk.MaxDec(minimumGasUsed, sdk.NewDec(int64(temporaryGasUsed))).TruncateInt().Uint64()
|
|
// reset leftoverGas, to be used by the tracer
|
|
leftoverGas = msg.Gas() - gasUsed
|
|
|
|
return &types.MsgEthereumTxResponse{
|
|
GasUsed: gasUsed,
|
|
VmError: vmError,
|
|
Ret: ret,
|
|
Logs: types.NewLogsFromEth(stateDB.Logs()),
|
|
Hash: txConfig.TxHash.Hex(),
|
|
}, nil
|
|
}
|
|
|
|
// ApplyMessage calls ApplyMessageWithConfig with default EVMConfig
|
|
func (k *Keeper) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*types.MsgEthereumTxResponse, error) {
|
|
cfg, err := k.EVMConfig(ctx)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed to load evm config")
|
|
}
|
|
txConfig := statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash()))
|
|
return k.ApplyMessageWithConfig(ctx, msg, tracer, commit, cfg, txConfig)
|
|
}
|
|
|
|
// GetEthIntrinsicGas returns the intrinsic gas cost for the transaction
|
|
func (k *Keeper) GetEthIntrinsicGas(ctx sdk.Context, msg core.Message, cfg *params.ChainConfig, isContractCreation bool) (uint64, error) {
|
|
height := big.NewInt(ctx.BlockHeight())
|
|
homestead := cfg.IsHomestead(height)
|
|
istanbul := cfg.IsIstanbul(height)
|
|
|
|
return core.IntrinsicGas(msg.Data(), msg.AccessList(), isContractCreation, homestead, istanbul)
|
|
}
|
|
|
|
// RefundGas transfers the leftover gas to the sender of the message, caped to half of the total gas
|
|
// consumed in the transaction. Additionally, the function sets the total gas consumed to the value
|
|
// returned by the EVM execution, thus ignoring the previous intrinsic gas consumed during in the
|
|
// AnteHandler.
|
|
func (k *Keeper) RefundGas(ctx sdk.Context, msg core.Message, leftoverGas uint64, denom string) error {
|
|
// Return EVM tokens for remaining gas, exchanged at the original rate.
|
|
remaining := new(big.Int).Mul(new(big.Int).SetUint64(leftoverGas), msg.GasPrice())
|
|
|
|
switch remaining.Sign() {
|
|
case -1:
|
|
// negative refund errors
|
|
return sdkerrors.Wrapf(types.ErrInvalidRefund, "refunded amount value cannot be negative %d", remaining.Int64())
|
|
case 1:
|
|
// positive amount refund
|
|
refundedCoins := sdk.Coins{sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(remaining))}
|
|
|
|
// refund to sender from the fee collector module account, which is the escrow account in charge of collecting tx fees
|
|
|
|
err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins)
|
|
if err != nil {
|
|
err = sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "fee collector account failed to refund fees: %s", err.Error())
|
|
return sdkerrors.Wrapf(err, "failed to refund %d leftover gas (%s)", leftoverGas, refundedCoins.String())
|
|
}
|
|
default:
|
|
// no refund, consume gas and update the tx gas meter
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ResetGasMeterAndConsumeGas reset first the gas meter consumed value to zero and set it back to the new value
|
|
// 'gasUsed'
|
|
func (k *Keeper) ResetGasMeterAndConsumeGas(ctx sdk.Context, gasUsed uint64) {
|
|
// reset the gas count
|
|
ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "reset the gas count")
|
|
ctx.GasMeter().ConsumeGas(gasUsed, "apply evm transaction")
|
|
}
|
|
|
|
// GetCoinbaseAddress returns the block proposer's validator operator address.
|
|
func (k Keeper) GetCoinbaseAddress(ctx sdk.Context) (common.Address, error) {
|
|
consAddr := sdk.ConsAddress(ctx.BlockHeader().ProposerAddress)
|
|
validator, found := k.stakingKeeper.GetValidatorByConsAddr(ctx, consAddr)
|
|
if !found {
|
|
return common.Address{}, sdkerrors.Wrapf(
|
|
stakingtypes.ErrNoValidatorFound,
|
|
"failed to retrieve validator from block proposer address %s",
|
|
consAddr.String(),
|
|
)
|
|
}
|
|
|
|
coinbase := common.BytesToAddress(validator.GetOperator())
|
|
return coinbase, nil
|
|
}
|