package ante import ( "math/big" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" ethtypes "github.com/ethereum/go-ethereum/core/types" evmtypes "github.com/evmos/ethermint/x/evm/types" ) // MinGasPriceDecorator will check if the transaction's fee is at least as large // as the MinGasPrices param. If fee is too low, decorator returns error and tx // is rejected. This applies for both CheckTx and DeliverTx // If fee is high enough, then call next AnteHandler // CONTRACT: Tx must implement FeeTx to use MinGasPriceDecorator type MinGasPriceDecorator struct { feesKeeper FeeMarketKeeper evmKeeper EVMKeeper } // EthMinGasPriceDecorator will check if the transaction's fee is at least as large // as the MinGasPrices param. If fee is too low, decorator returns error and tx // is rejected. This applies to both CheckTx and DeliverTx and regardless // if London hard fork or fee market params (EIP-1559) are enabled. // If fee is high enough, then call next AnteHandler type EthMinGasPriceDecorator struct { feesKeeper FeeMarketKeeper evmKeeper EVMKeeper } // EthMempoolFeeDecorator will check if the transaction's effective fee is at least as large // as the local validator's minimum gasFee (defined in validator config). // If fee is too low, decorator returns error and tx is rejected from mempool. // Note this only applies when ctx.CheckTx = true // If fee is high enough or not CheckTx, then call next AnteHandler // CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator type EthMempoolFeeDecorator struct { evmKeeper EVMKeeper } // NewMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for // Cosmos transactions. func NewMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) MinGasPriceDecorator { return MinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} } // NewEthMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for // Ethereum transactions. func NewEthMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) EthMinGasPriceDecorator { return EthMinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} } // NewEthMempoolFeeDecorator creates a new NewEthMempoolFeeDecorator instance used only for // Ethereum transactions. func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator { return EthMempoolFeeDecorator{ evmKeeper: ek, } } func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { feeTx, ok := tx.(sdk.FeeTx) if !ok { return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected sdk.FeeTx", tx) } minGasPrice := mpd.feesKeeper.GetParams(ctx).MinGasPrice // Short-circuit if min gas price is 0 or if simulating if minGasPrice.IsZero() || simulate { return next(ctx, tx, simulate) } evmDenom := mpd.evmKeeper.GetEVMDenom(ctx) minGasPrices := sdk.DecCoins{ { Denom: evmDenom, Amount: minGasPrice, }, } feeCoins := feeTx.GetFee() gas := feeTx.GetGas() requiredFees := make(sdk.Coins, 0) // Determine the required fees by multiplying each required minimum gas // price by the gas limit, where fee = ceil(minGasPrice * gasLimit). gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(gas)) for _, gp := range minGasPrices { fee := gp.Amount.Mul(gasLimit).Ceil().RoundInt() if fee.IsPositive() { requiredFees = requiredFees.Add(sdk.Coin{Denom: gp.Denom, Amount: fee}) } } if !feeCoins.IsAnyGTE(requiredFees) { return ctx, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "provided fee < minimum global fee (%s < %s). Please increase the gas price.", feeCoins, requiredFees) } return next(ctx, tx, simulate) } // AnteHandle ensures that the that the effective fee from the transaction is greater than the // minimum global fee, which is defined by the MinGasPrice (parameter) * GasLimit (tx argument). func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { minGasPrice := empd.feesKeeper.GetParams(ctx).MinGasPrice // short-circuit if min gas price is 0 if minGasPrice.IsZero() { return next(ctx, tx, simulate) } chainCfg := empd.evmKeeper.GetChainConfig(ctx) ethCfg := chainCfg.EthereumConfig(empd.evmKeeper.ChainID()) baseFee := empd.evmKeeper.GetBaseFee(ctx, ethCfg) for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { return ctx, errorsmod.Wrapf( errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil), ) } feeAmt := ethMsg.GetFee() // For dynamic transactions, GetFee() uses the GasFeeCap value, which // is the maximum gas price that the signer can pay. In practice, the // signer can pay less, if the block's BaseFee is lower. So, in this case, // we use the EffectiveFee. If the feemarket formula results in a BaseFee // that lowers EffectivePrice until it is < MinGasPrices, the users must // increase the GasTipCap (priority fee) until EffectivePrice > MinGasPrices. // Transactions with MinGasPrices * gasUsed < tx fees < EffectiveFee are rejected // by the feemarket AnteHandle txData, err := evmtypes.UnpackTxData(ethMsg.Data) if err != nil { return ctx, errorsmod.Wrapf(err, "failed to unpack tx data %s", ethMsg.Hash) } if txData.TxType() != ethtypes.LegacyTxType { feeAmt = ethMsg.GetEffectiveFee(baseFee) } gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(ethMsg.GetGas())) requiredFee := minGasPrice.Mul(gasLimit) fee := sdk.NewDecFromBigInt(feeAmt) if fee.LT(requiredFee) { return ctx, errorsmod.Wrapf( errortypes.ErrInsufficientFee, "provided fee < minimum global fee (%d < %d). Please increase the priority tip (for EIP-1559 txs) or the gas prices (for access list or legacy txs)", //nolint:lll fee.TruncateInt().Int64(), requiredFee.TruncateInt().Int64(), ) } } return next(ctx, tx, simulate) } // AnteHandle ensures that the provided fees meet a minimum threshold for the validator. // This check only for local mempool purposes, and thus it is only run on (Re)CheckTx. // The logic is also skipped if the London hard fork and EIP-1559 are enabled. func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { if !ctx.IsCheckTx() || simulate { return next(ctx, tx, simulate) } chainCfg := mfd.evmKeeper.GetChainConfig(ctx) ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID()) baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg) // skip check as the London hard fork and EIP-1559 are enabled if baseFee != nil { return next(ctx, tx, simulate) } evmDenom := mfd.evmKeeper.GetEVMDenom(ctx) minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom) for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } fee := sdk.NewDecFromBigInt(ethMsg.GetFee()) gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(ethMsg.GetGas())) requiredFee := minGasPrice.Mul(gasLimit) if fee.LT(requiredFee) { return ctx, errorsmod.Wrapf( errortypes.ErrInsufficientFee, "insufficient fee; got: %s required: %s", fee, requiredFee, ) } } return next(ctx, tx, simulate) }