Skip to content

Commit

Permalink
imp(ante): MonoAnteHandler for EVM transactions (#2028)
Browse files Browse the repository at this point in the history
* imp(ante): MonoAnteHandler for EVM transactions

* cont refactor

* can transfer

* cont refactor

* fix from

* consume gas

* fixes

* update setup

* use struct

* add ante handler benchmarking tests

* check error on factory

* run make format

* make effective fee compatible with old ante

* add comment

* fix benchmark tests

* improve benchmarking

* add comments

* add licenses

* Update app/ante/evm.go

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

* add comment

* add changelog

* add changelog

* fix lint

* fix licenses

* fix werc20 tests

* fix lint

---------

Co-authored-by: Freddy Caceres <facs95@gmail.com>
Co-authored-by: facs95 <facs95@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 27, 2023
1 parent 84ca6b3 commit 8a46ed8
Show file tree
Hide file tree
Showing 30 changed files with 1,805 additions and 951 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (osmosis-outpost) [#2017](https://github.com/evmos/evmos/pull/2017) Refactor types, errors and precompile struct.
- (erc20) [#2023](https://github.com/evmos/evmos/pull/2023) Add tests for ERC20 precompile queries.
- (osmosis-outpost) [#2025](https://github.com/evmos/evmos/pull/2025) Use a struct to wrap parsed parameters from Solidity.
- (ante) [#2028](https://github.com/evmos/evmos/pull/2028) MonoAnteHandler for EVM transaction.
- (staking) [#2030](https://github.com/evmos/evmos/pull/2030) Implement the `CreateValidator` function for staking precompiled contract.
- (erc20) [#2037](https://github.com/evmos/evmos/pull/2037) Add IsTransactions and RequiredGas tests for the ERC20 extension.
- (bank) [#2040](https://github.com/evmos/evmos/pull/2040) Add bank extension unit tests for queries.
Expand Down Expand Up @@ -160,7 +161,6 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (gov) [#1981](https://github.com/evmos/evmos/pull/1981) Remove deprecated `cosmos.params.v1beta1/ParameterChangeProposal` handler
- (revenue) [#2032](https://github.com/evmos/evmos/pull/2032) Fixed the problem that users cannot send transactions with gasPrice of 0 to precompiled contracts.


## [v14.1.0] - 2023-09-25

### Bug Fixes
Expand Down
3 changes: 1 addition & 2 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)

package ante

import (
Expand All @@ -27,7 +26,7 @@ func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
switch typeURL := opts[0].GetTypeUrl(); typeURL {
case "/ethermint.evm.v1.ExtensionOptionsEthereumTx":
// handle as *evmtypes.MsgEthereumTx
anteHandler = newEVMAnteHandler(options)
anteHandler = newMonoEVMAnteHandler(options) // TODO: replace for mono EVM AnteHandler
case "/ethermint.types.v1.ExtensionOptionsWeb3Tx":
// handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation
anteHandler = newLegacyCosmosAnteHandlerEip712(options)
Expand Down
42 changes: 42 additions & 0 deletions app/ante/cosmos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)

package ante

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
ibcante "github.com/cosmos/ibc-go/v7/modules/core/ante"
cosmosante "github.com/evmos/evmos/v15/app/ante/cosmos"
evmante "github.com/evmos/evmos/v15/app/ante/evm"
evmtypes "github.com/evmos/evmos/v15/x/evm/types"
)

// newCosmosAnteHandler creates the default ante handler for Cosmos transactions
func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
cosmosante.RejectMessagesDecorator{}, // reject MsgEthereumTxs
cosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field
sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}),
sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}),
),
ante.NewSetUpContextDecorator(),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(options.AccountKeeper),
cosmosante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
cosmosante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.DistributionKeeper, options.FeegrantKeeper, options.StakingKeeper, options.TxFeeChecker),
cosmosante.NewVestingDelegationDecorator(options.AccountKeeper, options.StakingKeeper, options.BankKeeper, options.Cdc),
// SetPubKeyDecorator must be called before all signature verification decorators
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
)
}
79 changes: 79 additions & 0 deletions app/ante/evm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)

package ante

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
ibcante "github.com/cosmos/ibc-go/v7/modules/core/ante"
cosmosante "github.com/evmos/evmos/v15/app/ante/cosmos"
evmante "github.com/evmos/evmos/v15/app/ante/evm"
evmtypes "github.com/evmos/evmos/v15/x/evm/types"
)

func newMonoEVMAnteHandler(options HandlerOptions) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
evmante.NewMonoDecorator(
options.AccountKeeper,
options.BankKeeper,
options.FeeMarketKeeper,
options.EvmKeeper,
options.DistributionKeeper,
options.StakingKeeper,
options.MaxTxGasWanted,
),
)
}

// newEVMAnteHandler creates the default ante handler for Ethereum transactions
func newEVMAnteHandler(options HandlerOptions) sdk.AnteHandler { //nolint: unused
return sdk.ChainAnteDecorators(
// outermost AnteDecorator. SetUpContext must be called first
evmante.NewEthSetUpContextDecorator(options.EvmKeeper),
// Check eth effective gas price against the node's minimal-gas-prices config
evmante.NewEthMempoolFeeDecorator(options.EvmKeeper),
// Check eth effective gas price against the global MinGasPrice
evmante.NewEthMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
evmante.NewEthValidateBasicDecorator(options.EvmKeeper),
evmante.NewEthSigVerificationDecorator(options.EvmKeeper),
evmante.NewEthAccountVerificationDecorator(options.AccountKeeper, options.EvmKeeper),
evmante.NewCanTransferDecorator(options.EvmKeeper),
evmante.NewEthVestingTransactionDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper),
evmante.NewEthGasConsumeDecorator(options.BankKeeper, options.DistributionKeeper, options.EvmKeeper, options.StakingKeeper, options.MaxTxGasWanted),
evmante.NewEthIncrementSenderSequenceDecorator(options.AccountKeeper),
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
// emit eth tx hash and index at the very last ante handler.
evmante.NewEthEmitEventDecorator(options.EvmKeeper),
)
}

// newCosmosAnteHandlerEip712 creates the ante handler for transactions signed with EIP712
func newLegacyCosmosAnteHandlerEip712(options HandlerOptions) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
cosmosante.RejectMessagesDecorator{}, // reject MsgEthereumTxs
cosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field
sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}),
sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}),
),
ante.NewSetUpContextDecorator(),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
cosmosante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
cosmosante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.DistributionKeeper, options.FeegrantKeeper, options.StakingKeeper, options.TxFeeChecker),
cosmosante.NewVestingDelegationDecorator(options.AccountKeeper, options.StakingKeeper, options.BankKeeper, options.Cdc),
// SetPubKeyDecorator must be called before all signature verification decorators
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
// Note: signature verification uses EIP instead of the cosmos signature validator
//nolint: staticcheck
cosmosante.NewLegacyEip712SigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
)
}
52 changes: 52 additions & 0 deletions app/ante/evm/01_setup_ctx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)
package evm

import (
errorsmod "cosmossdk.io/errors"
storetypes "github.com/cosmos/cosmos-sdk/store/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"
)

var _ sdk.AnteDecorator = &EthSetupContextDecorator{}

// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption
// by setting the gas meter to infinite
type EthSetupContextDecorator struct {
evmKeeper EVMKeeper
}

func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) EthSetupContextDecorator {
return EthSetupContextDecorator{
evmKeeper: evmKeeper,
}
}

func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
newCtx, err = SetupContext(ctx, tx, esc.evmKeeper)
if err != nil {
return ctx, err
}
return next(newCtx, tx, simulate)
}

func SetupContext(ctx sdk.Context, tx sdk.Tx, evmKeeper EVMKeeper) (sdk.Context, error) {
// all transactions must implement GasTx
_, ok := tx.(authante.GasTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected GasTx", tx)
}

// We need to setup an empty gas config so that the gas is consistent with Ethereum.
newCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter()).
WithKVGasConfig(storetypes.GasConfig{}).
WithTransientKVGasConfig(storetypes.GasConfig{})

// Reset transient gas used to prepare the execution of current cosmos tx.
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
evmKeeper.ResetTransientGasUsed(ctx)

return newCtx, nil
}
88 changes: 88 additions & 0 deletions app/ante/evm/02_mempool_fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)
package evm

import (
"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"
evmtypes "github.com/evmos/evmos/v15/x/evm/types"
)

// 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
}

// NewEthMempoolFeeDecorator creates a new NewEthMempoolFeeDecorator instance used only for
// Ethereum transactions.
func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator {
return EthMempoolFeeDecorator{
evmKeeper: ek,
}
}

// 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)
}

evmParams := mfd.evmKeeper.GetParams(ctx)
chainCfg := evmParams.GetChainConfig()
ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID())
isLondon := ethCfg.IsLondon(big.NewInt(ctx.BlockHeight()))

// skip check as the London hard fork and EIP-1559 are enabled
if isLondon {
return next(ctx, tx, simulate)
}

evmDenom := evmParams.GetEvmDenom()
minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom)

for _, msg := range tx.GetMsgs() {
_, txData, _, err := evmtypes.UnpackEthMsg(msg)
if err != nil {
return ctx, err
}

gasLimit := sdkmath.LegacyNewDecFromBigInt(new(big.Int).SetUint64(txData.GetGas()))
fee := sdkmath.LegacyNewDecFromBigInt(txData.Fee())

if err := CheckMempoolFee(fee, minGasPrice, gasLimit, isLondon); err != nil {
return ctx, err
}
}

return next(ctx, tx, simulate)
}

// CheckMempoolFee checks if the provided fee is at least as large as the local validator's
func CheckMempoolFee(fee, mempoolMinGasPrice, gasLimit sdkmath.LegacyDec, isLondon bool) error {
if isLondon {
return nil
}

requiredFee := mempoolMinGasPrice.Mul(gasLimit)

if fee.LT(requiredFee) {
return errorsmod.Wrapf(
errortypes.ErrInsufficientFee,
"insufficient fee; got: %s required: %s",
fee, requiredFee,
)
}

return nil
}
95 changes: 95 additions & 0 deletions app/ante/evm/03_global_fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright Tharsis Labs Ltd.(Evmos)
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE)
package evm

import (
"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"

ethtypes "github.com/ethereum/go-ethereum/core/types"
evmtypes "github.com/evmos/evmos/v15/x/evm/types"
)

// 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
}

// NewEthMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for
// Ethereum transactions.
func NewEthMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) EthMinGasPriceDecorator {
return EthMinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek}
}

// AnteHandle ensures 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)
}

evmParams := empd.evmKeeper.GetParams(ctx)
chainCfg := evmParams.GetChainConfig()
ethCfg := chainCfg.EthereumConfig(empd.evmKeeper.ChainID())
baseFee := empd.evmKeeper.GetBaseFee(ctx, ethCfg)

for _, msg := range tx.GetMsgs() {
_, txData, _, err := evmtypes.UnpackEthMsg(msg)
if err != nil {
return ctx, err
}

feeAmt := txData.Fee()

if txData.TxType() != ethtypes.LegacyTxType {
feeAmt = txData.EffectiveFee(baseFee)
}

gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(txData.GetGas()))
fee := sdkmath.LegacyNewDecFromBigInt(feeAmt)

if err := CheckGlobalFee(fee, minGasPrice, gasLimit); err != nil {
return ctx, err
}
}

return next(ctx, tx, simulate)
}

// 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
func CheckGlobalFee(fee, globalMinGasPrice, gasLimit sdkmath.LegacyDec) error {
if globalMinGasPrice.IsZero() {
return nil
}

requiredFee := globalMinGasPrice.Mul(gasLimit)

if fee.LT(requiredFee) {
return errorsmod.Wrapf(
errortypes.ErrInsufficientFee,
"provided fee < minimum global fee (%s < %s). Please increase the priority tip (for EIP-1559 txs) or the gas prices (for access list or legacy txs)", //nolint:lll
fee.TruncateInt().String(), requiredFee.TruncateInt().String(),
)
}

return nil
}
Loading

0 comments on commit 8a46ed8

Please sign in to comment.