7f196ce451
* chore: verify fees refactor * adjust call structure in rest of repo after splitting up DeductTxCostsFromUserBalance * adjust test logic after splitting DeductTxCostsFromUserBalance up * remove outdated TODO * address PR comments - remove import name for evm keeper * remove misleading comment * address review comments - only handover boolean instead of context * remove TODO Co-authored-by: MalteHerrmann <malteherrmann.mail@web.de> Co-authored-by: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com>
354 lines
12 KiB
Go
354 lines
12 KiB
Go
package ante
|
|
|
|
import (
|
|
"math"
|
|
"math/big"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
sdkmath "cosmossdk.io/math"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
ethermint "github.com/evmos/ethermint/types"
|
|
"github.com/evmos/ethermint/x/evm/keeper"
|
|
"github.com/evmos/ethermint/x/evm/statedb"
|
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
)
|
|
|
|
// EthAccountVerificationDecorator validates an account balance checks
|
|
type EthAccountVerificationDecorator struct {
|
|
ak evmtypes.AccountKeeper
|
|
evmKeeper EVMKeeper
|
|
}
|
|
|
|
// NewEthAccountVerificationDecorator creates a new EthAccountVerificationDecorator
|
|
func NewEthAccountVerificationDecorator(ak evmtypes.AccountKeeper, ek EVMKeeper) EthAccountVerificationDecorator {
|
|
return EthAccountVerificationDecorator{
|
|
ak: ak,
|
|
evmKeeper: ek,
|
|
}
|
|
}
|
|
|
|
// AnteHandle validates checks that the sender balance is greater than the total transaction cost.
|
|
// The account will be set to store if it doesn't exis, i.e cannot be found on store.
|
|
// This AnteHandler decorator will fail if:
|
|
// - any of the msgs is not a MsgEthereumTx
|
|
// - from address is empty
|
|
// - account balance is lower than the transaction cost
|
|
func (avd EthAccountVerificationDecorator) AnteHandle(
|
|
ctx sdk.Context,
|
|
tx sdk.Tx,
|
|
simulate bool,
|
|
next sdk.AnteHandler,
|
|
) (newCtx sdk.Context, err error) {
|
|
if !ctx.IsCheckTx() {
|
|
return next(ctx, tx, simulate)
|
|
}
|
|
|
|
for i, msg := range tx.GetMsgs() {
|
|
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
|
if !ok {
|
|
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
|
}
|
|
|
|
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
|
if err != nil {
|
|
return ctx, errorsmod.Wrapf(err, "failed to unpack tx data any for tx %d", i)
|
|
}
|
|
|
|
// sender address should be in the tx cache from the previous AnteHandle call
|
|
from := msgEthTx.GetFrom()
|
|
if from.Empty() {
|
|
return ctx, errorsmod.Wrap(errortypes.ErrInvalidAddress, "from address cannot be empty")
|
|
}
|
|
|
|
// check whether the sender address is EOA
|
|
fromAddr := common.BytesToAddress(from)
|
|
acct := avd.evmKeeper.GetAccount(ctx, fromAddr)
|
|
|
|
if acct == nil {
|
|
acc := avd.ak.NewAccountWithAddress(ctx, from)
|
|
avd.ak.SetAccount(ctx, acc)
|
|
acct = statedb.NewEmptyAccount()
|
|
} else if acct.IsContract() {
|
|
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType,
|
|
"the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash)
|
|
}
|
|
|
|
if err := keeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(acct.Balance), txData); err != nil {
|
|
return ctx, errorsmod.Wrap(err, "failed to check sender balance")
|
|
}
|
|
}
|
|
return next(ctx, tx, simulate)
|
|
}
|
|
|
|
// EthGasConsumeDecorator validates enough intrinsic gas for the transaction and
|
|
// gas consumption.
|
|
type EthGasConsumeDecorator struct {
|
|
evmKeeper EVMKeeper
|
|
maxGasWanted uint64
|
|
}
|
|
|
|
// NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator
|
|
func NewEthGasConsumeDecorator(
|
|
evmKeeper EVMKeeper,
|
|
maxGasWanted uint64,
|
|
) EthGasConsumeDecorator {
|
|
return EthGasConsumeDecorator{
|
|
evmKeeper,
|
|
maxGasWanted,
|
|
}
|
|
}
|
|
|
|
// AnteHandle validates that the Ethereum tx message has enough to cover intrinsic gas
|
|
// (during CheckTx only) and that the sender has enough balance to pay for the gas cost.
|
|
//
|
|
// Intrinsic gas for a transaction is the amount of gas that the transaction uses before the
|
|
// transaction is executed. The gas is a constant value plus any cost incurred by additional bytes
|
|
// of data supplied with the transaction.
|
|
//
|
|
// This AnteHandler decorator will fail if:
|
|
// - the message is not a MsgEthereumTx
|
|
// - sender account cannot be found
|
|
// - transaction's gas limit is lower than the intrinsic gas
|
|
// - user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price)
|
|
// - transaction or block gas meter runs out of gas
|
|
// - sets the gas meter limit
|
|
// - gas limit is greater than the block gas meter limit
|
|
func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
|
// gas consumption limit already checked during CheckTx so there's no need to
|
|
// verify it again during ReCheckTx
|
|
if ctx.IsReCheckTx() {
|
|
return next(ctx, tx, simulate)
|
|
}
|
|
|
|
chainCfg := egcd.evmKeeper.GetChainConfig(ctx)
|
|
ethCfg := chainCfg.EthereumConfig(egcd.evmKeeper.ChainID())
|
|
|
|
blockHeight := big.NewInt(ctx.BlockHeight())
|
|
homestead := ethCfg.IsHomestead(blockHeight)
|
|
istanbul := ethCfg.IsIstanbul(blockHeight)
|
|
gasWanted := uint64(0)
|
|
var events sdk.Events
|
|
|
|
// Use the lowest priority of all the messages as the final one.
|
|
minPriority := int64(math.MaxInt64)
|
|
baseFee := egcd.evmKeeper.GetBaseFee(ctx, ethCfg)
|
|
|
|
for _, msg := range tx.GetMsgs() {
|
|
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
|
if !ok {
|
|
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
|
}
|
|
|
|
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
|
if err != nil {
|
|
return ctx, errorsmod.Wrap(err, "failed to unpack tx data")
|
|
}
|
|
|
|
if ctx.IsCheckTx() && egcd.maxGasWanted != 0 {
|
|
// We can't trust the tx gas limit, because we'll refund the unused gas.
|
|
if txData.GetGas() > egcd.maxGasWanted {
|
|
gasWanted += egcd.maxGasWanted
|
|
} else {
|
|
gasWanted += txData.GetGas()
|
|
}
|
|
} else {
|
|
gasWanted += txData.GetGas()
|
|
}
|
|
|
|
evmDenom := egcd.evmKeeper.GetEVMDenom(ctx)
|
|
|
|
fees, err := keeper.VerifyFee(txData, evmDenom, baseFee, homestead, istanbul, ctx.IsCheckTx())
|
|
if err != nil {
|
|
return ctx, errorsmod.Wrapf(err, "failed to verify the fees")
|
|
}
|
|
|
|
err = egcd.evmKeeper.DeductTxCostsFromUserBalance(ctx, fees, common.HexToAddress(msgEthTx.From))
|
|
if err != nil {
|
|
return ctx, errorsmod.Wrapf(err, "failed to deduct transaction costs from user balance")
|
|
}
|
|
|
|
events = append(events,
|
|
sdk.NewEvent(
|
|
sdk.EventTypeTx,
|
|
sdk.NewAttribute(sdk.AttributeKeyFee, fees.String()),
|
|
),
|
|
)
|
|
|
|
priority := evmtypes.GetTxPriority(txData, baseFee)
|
|
|
|
if priority < minPriority {
|
|
minPriority = priority
|
|
}
|
|
}
|
|
|
|
ctx.EventManager().EmitEvents(events)
|
|
|
|
blockGasLimit := ethermint.BlockGasLimit(ctx)
|
|
|
|
// return error if the tx gas is greater than the block limit (max gas)
|
|
|
|
// NOTE: it's important here to use the gas wanted instead of the gas consumed
|
|
// from the tx gas pool. The later only has the value so far since the
|
|
// EthSetupContextDecorator so it will never exceed the block gas limit.
|
|
if gasWanted > blockGasLimit {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrOutOfGas,
|
|
"tx gas (%d) exceeds block gas limit (%d)",
|
|
gasWanted,
|
|
blockGasLimit,
|
|
)
|
|
}
|
|
|
|
// Set tx GasMeter with a limit of GasWanted (i.e gas limit from the Ethereum tx).
|
|
// The gas consumed will be then reset to the gas used by the state transition
|
|
// in the EVM.
|
|
|
|
// FIXME: use a custom gas configuration that doesn't add any additional gas and only
|
|
// takes into account the gas consumed at the end of the EVM transaction.
|
|
newCtx := ctx.
|
|
WithGasMeter(ethermint.NewInfiniteGasMeterWithLimit(gasWanted)).
|
|
WithPriority(minPriority)
|
|
|
|
// we know that we have enough gas on the pool to cover the intrinsic gas
|
|
return next(newCtx, tx, simulate)
|
|
}
|
|
|
|
// CanTransferDecorator checks if the sender is allowed to transfer funds according to the EVM block
|
|
// context rules.
|
|
type CanTransferDecorator struct {
|
|
evmKeeper EVMKeeper
|
|
}
|
|
|
|
// NewCanTransferDecorator creates a new CanTransferDecorator instance.
|
|
func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator {
|
|
return CanTransferDecorator{
|
|
evmKeeper: evmKeeper,
|
|
}
|
|
}
|
|
|
|
// AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to
|
|
// see if the address can execute the transaction.
|
|
func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
|
params := ctd.evmKeeper.GetParams(ctx)
|
|
ethCfg := params.ChainConfig.EthereumConfig(ctd.evmKeeper.ChainID())
|
|
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(ctx.BlockHeight()))
|
|
|
|
for _, msg := range tx.GetMsgs() {
|
|
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
|
if !ok {
|
|
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
|
}
|
|
|
|
baseFee := ctd.evmKeeper.GetBaseFee(ctx, ethCfg)
|
|
|
|
coreMsg, err := msgEthTx.AsMessage(signer, baseFee)
|
|
if err != nil {
|
|
return ctx, errorsmod.Wrapf(
|
|
err,
|
|
"failed to create an ethereum core.Message from signer %T", signer,
|
|
)
|
|
}
|
|
|
|
if evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) {
|
|
if baseFee == nil {
|
|
return ctx, errorsmod.Wrap(
|
|
evmtypes.ErrInvalidBaseFee,
|
|
"base fee is supported but evm block context value is nil",
|
|
)
|
|
}
|
|
if coreMsg.GasFeeCap().Cmp(baseFee) < 0 {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrInsufficientFee,
|
|
"max fee per gas less than block base fee (%s < %s)",
|
|
coreMsg.GasFeeCap(), baseFee,
|
|
)
|
|
}
|
|
}
|
|
|
|
// NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below
|
|
cfg := &evmtypes.EVMConfig{
|
|
ChainConfig: ethCfg,
|
|
Params: params,
|
|
CoinBase: common.Address{},
|
|
BaseFee: baseFee,
|
|
}
|
|
stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())))
|
|
evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB)
|
|
|
|
// check that caller has enough balance to cover asset transfer for **topmost** call
|
|
// NOTE: here the gas consumed is from the context with the infinite gas meter
|
|
if coreMsg.Value().Sign() > 0 && !evm.Context().CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrInsufficientFunds,
|
|
"failed to transfer %s from address %s using the EVM block context transfer function",
|
|
coreMsg.Value(),
|
|
coreMsg.From(),
|
|
)
|
|
}
|
|
}
|
|
|
|
return next(ctx, tx, simulate)
|
|
}
|
|
|
|
// EthIncrementSenderSequenceDecorator increments the sequence of the signers.
|
|
type EthIncrementSenderSequenceDecorator struct {
|
|
ak evmtypes.AccountKeeper
|
|
}
|
|
|
|
// NewEthIncrementSenderSequenceDecorator creates a new EthIncrementSenderSequenceDecorator.
|
|
func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) EthIncrementSenderSequenceDecorator {
|
|
return EthIncrementSenderSequenceDecorator{
|
|
ak: ak,
|
|
}
|
|
}
|
|
|
|
// AnteHandle handles incrementing the sequence of the signer (i.e sender). If the transaction is a
|
|
// contract creation, the nonce will be incremented during the transaction execution and not within
|
|
// this AnteHandler decorator.
|
|
func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
|
for _, msg := range tx.GetMsgs() {
|
|
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
|
if !ok {
|
|
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
|
}
|
|
|
|
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
|
if err != nil {
|
|
return ctx, errorsmod.Wrap(err, "failed to unpack tx data")
|
|
}
|
|
|
|
// increase sequence of sender
|
|
acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom())
|
|
if acc == nil {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrUnknownAddress,
|
|
"account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()),
|
|
)
|
|
}
|
|
nonce := acc.GetSequence()
|
|
|
|
// we merged the nonce verification to nonce increment, so when tx includes multiple messages
|
|
// with same sender, they'll be accepted.
|
|
if txData.GetNonce() != nonce {
|
|
return ctx, errorsmod.Wrapf(
|
|
errortypes.ErrInvalidSequence,
|
|
"invalid nonce; got %d, expected %d", txData.GetNonce(), nonce,
|
|
)
|
|
}
|
|
|
|
if err := acc.SetSequence(nonce + 1); err != nil {
|
|
return ctx, errorsmod.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1)
|
|
}
|
|
|
|
issd.ak.SetAccount(ctx, acc)
|
|
}
|
|
|
|
return next(ctx, tx, simulate)
|
|
}
|