Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
feat!: Apply feemarket to native cosmos tx (#1194)
Browse files Browse the repository at this point in the history
* Problem: feemarket's query cli has redundant height parameter

Soluton:
- remove the positional height parameter, since there's a flag already.

Update CHANGELOG.md

* Apply feemarket to native cosmos tx

- add tx extension option for user to input tip price
- apply feemarket's base fee to native tx

comments and cleanup

fallback to default sdk logic when london hardfork not enabled

integration test

cleanup feemarket query cli commands

Update CHANGELOG.md

update unit tests

disable feemarket in simulation tests for now

fix lint

Update app/simulation_test.go

fix python lint

fix lint

Update x/evm/types/extension_option.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

address review suggestions

* fix unit tests

* fix integration test

* improve unit test coverage

* fix go lint

* refactor

* fix integration test

* fix simulation tests

* fix go linter

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
  • Loading branch information
yihuang and fedekunze authored Aug 10, 2022
1 parent 42abb25 commit b1cd16e
Show file tree
Hide file tree
Showing 24 changed files with 1,785 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (ante) [#1176](https://github.com/evmos/ethermint/pull/1176) Fix invalid tx hashes; Remove `Size_` field and validate `Hash`/`From` fields in ante handler,
recompute eth tx hashes in JSON-RPC APIs to fix old blocks.
* (deps) [#1168](https://github.com/evmos/ethermint/pull/1168) Upgrade cosmos-sdk to v0.46.
* (feemarket) [#1194](https://github.com/evmos/ethermint/pull/1194) Apply feemarket to native cosmos tx.

### API Breaking

Expand Down
4 changes: 4 additions & 0 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if err := options.validate(); err != nil {
return nil, err
}

return func(
ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, err error) {
Expand All @@ -45,6 +46,9 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
case "/ethermint.types.v1.ExtensionOptionsWeb3Tx":
// handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation
anteHandler = newCosmosAnteHandlerEip712(options)
case "/ethermint.types.v1.ExtensionOptionDynamicFeeTx":
// cosmos-sdk tx with dynamic fee extension
anteHandler = newCosmosAnteHandler(options)
default:
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrUnknownExtensionOptions,
Expand Down
16 changes: 8 additions & 8 deletions app/ante/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"success - DeliverTx EIP712 signed Cosmos Tx with MsgSend",
func() sdk.Tx {
from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9000-1", gas, amount)
return txBuilder.GetTx()
}, false, false, true,
Expand All @@ -315,9 +315,9 @@ func (suite AnteTestSuite) TestAnteHandler() {
"success - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg",
func() sdk.Tx {
from := acc.GetAddress()
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20))
amount := sdk.NewCoins(coinAmount)
gas := uint64(200000)
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))
amount := sdk.NewCoins(coinAmount)
txBuilder := suite.CreateTestEIP712TxBuilderMsgDelegate(from, privKey, "ethermint_9000-1", gas, amount)
return txBuilder.GetTx()
}, false, false, true,
Expand All @@ -326,8 +326,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID",
func() sdk.Tx {
from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9002-1", gas, amount)
return txBuilder.GetTx()
}, false, false, false,
Expand All @@ -336,8 +336,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with different gas fees",
func() sdk.Tx {
from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
txBuilder.SetGasLimit(uint64(300000))
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(30))))
Expand All @@ -348,8 +348,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with empty signature",
func() sdk.Tx {
from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
sigsV2 := signing.SignatureV2{}
txBuilder.SetSignatures(sigsV2)
Expand All @@ -360,8 +360,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid sequence",
func() sdk.Tx {
from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
suite.Require().NoError(err)
Expand All @@ -380,8 +380,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid signMode",
func() sdk.Tx {
from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
suite.Require().NoError(err)
Expand Down
7 changes: 3 additions & 4 deletions app/ante/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/evmos/ethermint/app/ante"
"github.com/evmos/ethermint/server/config"
"github.com/evmos/ethermint/tests"
evmkeeper "github.com/evmos/ethermint/x/evm/keeper"
"github.com/evmos/ethermint/x/evm/statedb"
evmtypes "github.com/evmos/ethermint/x/evm/types"

Expand Down Expand Up @@ -227,7 +226,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg)
suite.Require().Equal(int64(1000000000), baseFee.Int64())

gasPrice := new(big.Int).Add(baseFee, evmkeeper.DefaultPriorityReduction.BigInt())
gasPrice := new(big.Int).Add(baseFee, evmtypes.DefaultPriorityReduction.BigInt())

tx2GasLimit := uint64(1000000)
tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit, gasPrice, nil, nil, nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}})
Expand All @@ -236,8 +235,8 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {

dynamicFeeTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit,
nil, // gasPrice
new(big.Int).Add(baseFee, big.NewInt(evmkeeper.DefaultPriorityReduction.Int64()*2)), // gasFeeCap
evmkeeper.DefaultPriorityReduction.BigInt(), // gasTipCap
new(big.Int).Add(baseFee, big.NewInt(evmtypes.DefaultPriorityReduction.Int64()*2)), // gasFeeCap
evmtypes.DefaultPriorityReduction.BigInt(), // gasTipCap
nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}})
dynamicFeeTx.From = addr.Hex()
dynamicFeeTxPriority := int64(1)
Expand Down
142 changes: 142 additions & 0 deletions app/ante/fee_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package ante

import (
"fmt"
"math"

sdkmath "cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/types"
)

// 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, sdkerrors.Wrapf(sdkerrors.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, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
}
}

priority := getTxPriority(feeCoins)
return feeCoins, priority, nil
}

// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the fee
// provided in a transaction.
func getTxPriority(fees sdk.Coins) int64 {
var priority int64

for _, fee := range fees {
amt := fee.Amount.Quo(types.DefaultPriorityReduction)
p := int64(math.MaxInt64)

if amt.IsInt64() {
p = amt.Int64()
}

if priority == 0 || p < priority {
priority = p
}
}

return priority
}
Loading

0 comments on commit b1cd16e

Please sign in to comment.