160 lines
5.5 KiB
Go
160 lines
5.5 KiB
Go
// Copyright 2021 Evmos Foundation
|
|
// This file is part of Evmos' Ethermint library.
|
|
//
|
|
// The Ethermint library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The Ethermint library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE
|
|
package ante
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
sdkmath "cosmossdk.io/math"
|
|
|
|
ethermint "github.com/cerc-io/laconicd/types"
|
|
"github.com/cerc-io/laconicd/x/evm/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
|
|
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
|
|
)
|
|
|
|
// NewDynamicFeeChecker returns a `TxFeeChecker` that applies a dynamic fee to
|
|
// Cosmos txs using the EIP-1559 fee market logic.
|
|
// This can be called in both CheckTx and deliverTx modes.
|
|
// a) feeCap = tx.fees / tx.gas
|
|
// b) tipFeeCap = tx.MaxPriorityPrice (default) or MaxInt64
|
|
// - when `ExtensionOptionDynamicFeeTx` is omitted, `tipFeeCap` defaults to `MaxInt64`.
|
|
// - when london hardfork is not enabled, it fallbacks to SDK default behavior (validator min-gas-prices).
|
|
// - Tx priority is set to `effectiveGasPrice / DefaultPriorityReduction`.
|
|
func NewDynamicFeeChecker(k DynamicFeeEVMKeeper) authante.TxFeeChecker {
|
|
return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) {
|
|
feeTx, ok := tx.(sdk.FeeTx)
|
|
if !ok {
|
|
return nil, 0, fmt.Errorf("tx must be a FeeTx")
|
|
}
|
|
|
|
if ctx.BlockHeight() == 0 {
|
|
// genesis transactions: fallback to min-gas-price logic
|
|
return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx)
|
|
}
|
|
|
|
params := k.GetParams(ctx)
|
|
denom := params.EvmDenom
|
|
ethCfg := params.ChainConfig.EthereumConfig(k.ChainID())
|
|
|
|
baseFee := k.GetBaseFee(ctx, ethCfg)
|
|
if baseFee == nil {
|
|
// london hardfork is not enabled: fallback to min-gas-prices logic
|
|
return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx)
|
|
}
|
|
|
|
// default to `MaxInt64` when there's no extension option.
|
|
maxPriorityPrice := sdkmath.NewInt(math.MaxInt64)
|
|
|
|
// get the priority tip cap from the extension option.
|
|
if hasExtOptsTx, ok := tx.(authante.HasExtensionOptionsTx); ok {
|
|
for _, opt := range hasExtOptsTx.GetExtensionOptions() {
|
|
if extOpt, ok := opt.GetCachedValue().(*ethermint.ExtensionOptionDynamicFeeTx); ok {
|
|
maxPriorityPrice = extOpt.MaxPriorityPrice
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
gas := feeTx.GetGas()
|
|
feeCoins := feeTx.GetFee()
|
|
fee := feeCoins.AmountOfNoDenomValidation(denom)
|
|
|
|
feeCap := fee.Quo(sdkmath.NewIntFromUint64(gas))
|
|
baseFeeInt := sdkmath.NewIntFromBigInt(baseFee)
|
|
|
|
if feeCap.LT(baseFeeInt) {
|
|
return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "insufficient gas prices; got: %s required: %s", feeCap, baseFeeInt)
|
|
}
|
|
|
|
// calculate the effective gas price using the EIP-1559 logic.
|
|
effectivePrice := sdkmath.NewIntFromBigInt(types.EffectiveGasPrice(baseFeeInt.BigInt(), feeCap.BigInt(), maxPriorityPrice.BigInt()))
|
|
|
|
// NOTE: create a new coins slice without having to validate the denom
|
|
effectiveFee := sdk.Coins{
|
|
{
|
|
Denom: denom,
|
|
Amount: effectivePrice.Mul(sdkmath.NewIntFromUint64(gas)),
|
|
},
|
|
}
|
|
|
|
bigPriority := effectivePrice.Sub(baseFeeInt).Quo(types.DefaultPriorityReduction)
|
|
priority := int64(math.MaxInt64)
|
|
|
|
if bigPriority.IsInt64() {
|
|
priority = bigPriority.Int64()
|
|
}
|
|
|
|
return effectiveFee, priority, nil
|
|
}
|
|
}
|
|
|
|
// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per
|
|
// unit of gas is fixed and set by each validator, and the tx priority is computed from the gas price.
|
|
func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.FeeTx) (sdk.Coins, int64, error) {
|
|
feeCoins := tx.GetFee()
|
|
gas := tx.GetGas()
|
|
minGasPrices := ctx.MinGasPrices()
|
|
|
|
// Ensure that the provided fees meet a minimum threshold for the validator,
|
|
// if this is a CheckTx. This is only for local mempool purposes, and thus
|
|
// is only ran on check tx.
|
|
if ctx.IsCheckTx() && !minGasPrices.IsZero() {
|
|
requiredFees := make(sdk.Coins, len(minGasPrices))
|
|
|
|
// Determine the required fees by multiplying each required minimum gas
|
|
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
|
|
glDec := sdk.NewDec(int64(gas))
|
|
|
|
for i, gp := range minGasPrices {
|
|
fee := gp.Amount.Mul(glDec)
|
|
requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
|
|
}
|
|
|
|
if !feeCoins.IsAnyGTE(requiredFees) {
|
|
return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
|
|
}
|
|
}
|
|
|
|
priority := getTxPriority(feeCoins, int64(gas))
|
|
return feeCoins, priority, nil
|
|
}
|
|
|
|
// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price
|
|
// provided in a transaction.
|
|
func getTxPriority(fees sdk.Coins, gas int64) int64 {
|
|
var priority int64
|
|
|
|
for _, fee := range fees {
|
|
gasPrice := fee.Amount.QuoRaw(gas)
|
|
amt := gasPrice.Quo(types.DefaultPriorityReduction)
|
|
p := int64(math.MaxInt64)
|
|
|
|
if amt.IsInt64() {
|
|
p = amt.Int64()
|
|
}
|
|
|
|
if priority == 0 || p < priority {
|
|
priority = p
|
|
}
|
|
}
|
|
|
|
return priority
|
|
}
|