diff --git a/CHANGELOG.md b/CHANGELOG.md index 516956c387..58a58d9c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,7 +94,6 @@ 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. @@ -181,6 +180,7 @@ 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 diff --git a/app/ante/ante.go b/app/ante/ante.go index 92d9223b46..bf9ffcd6dc 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -1,5 +1,6 @@ // Copyright Tharsis Labs Ltd.(Evmos) // SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + package ante import ( @@ -26,7 +27,7 @@ func NewAnteHandler(options HandlerOptions) sdk.AnteHandler { switch typeURL := opts[0].GetTypeUrl(); typeURL { case "/ethermint.evm.v1.ExtensionOptionsEthereumTx": // handle as *evmtypes.MsgEthereumTx - anteHandler = newMonoEVMAnteHandler(options) // TODO: replace for mono EVM AnteHandler + anteHandler = newEVMAnteHandler(options) case "/ethermint.types.v1.ExtensionOptionsWeb3Tx": // handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation anteHandler = newLegacyCosmosAnteHandlerEip712(options) diff --git a/app/ante/cosmos.go b/app/ante/cosmos.go deleted file mode 100644 index a5212894bb..0000000000 --- a/app/ante/cosmos.go +++ /dev/null @@ -1,42 +0,0 @@ -// 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/v16/app/ante/cosmos" - evmante "github.com/evmos/evmos/v16/app/ante/evm" - evmtypes "github.com/evmos/evmos/v16/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), - ) -} diff --git a/app/ante/evm.go b/app/ante/evm.go deleted file mode 100644 index 6b58e016d4..0000000000 --- a/app/ante/evm.go +++ /dev/null @@ -1,79 +0,0 @@ -// 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/v16/app/ante/cosmos" - evmante "github.com/evmos/evmos/v16/app/ante/evm" - evmtypes "github.com/evmos/evmos/v16/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), - ) -} diff --git a/app/ante/evm/01_setup_ctx.go b/app/ante/evm/01_setup_ctx.go deleted file mode 100644 index 65a4861971..0000000000 --- a/app/ante/evm/01_setup_ctx.go +++ /dev/null @@ -1,52 +0,0 @@ -// 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 -} diff --git a/app/ante/evm/02_mempool_fee.go b/app/ante/evm/02_mempool_fee.go deleted file mode 100644 index fd2418f923..0000000000 --- a/app/ante/evm/02_mempool_fee.go +++ /dev/null @@ -1,88 +0,0 @@ -// 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/v16/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 -} diff --git a/app/ante/evm/03_global_fee.go b/app/ante/evm/03_global_fee.go deleted file mode 100644 index 65731480d4..0000000000 --- a/app/ante/evm/03_global_fee.go +++ /dev/null @@ -1,95 +0,0 @@ -// 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" - "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/v16/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 := math.LegacyNewDecFromBigInt(new(big.Int).SetUint64(txData.GetGas())) - fee := math.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 math.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 -} diff --git a/app/ante/evm/04_validate.go b/app/ante/evm/04_validate.go deleted file mode 100644 index 1e1d37aff6..0000000000 --- a/app/ante/evm/04_validate.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright Tharsis Labs Ltd.(Evmos) -// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) -package evm - -import ( - "errors" - "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" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - evmtypes "github.com/evmos/evmos/v16/x/evm/types" -) - -// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures -type EthValidateBasicDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator -func NewEthValidateBasicDecorator(ek EVMKeeper) EthValidateBasicDecorator { - return EthValidateBasicDecorator{ - evmKeeper: ek, - } -} - -// AnteHandle handles basic validation of tx -func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - // no need to validate basic on recheck tx, call next antehandler - if ctx.IsReCheckTx() { - return next(ctx, tx, simulate) - } - - txFeeInfo, err := ValidateTx(tx) - if err != nil { - return ctx, err - } - - txFee := sdk.Coins{} - txGasLimit := uint64(0) - - evmParams := vbd.evmKeeper.GetParams(ctx) - chainCfg := evmParams.GetChainConfig() - chainID := vbd.evmKeeper.ChainID() - ethCfg := chainCfg.EthereumConfig(chainID) - baseFee := vbd.evmKeeper.GetBaseFee(ctx, ethCfg) - enableCreate := evmParams.GetEnableCreate() - enableCall := evmParams.GetEnableCall() - evmDenom := evmParams.GetEvmDenom() - - for _, msg := range tx.GetMsgs() { - _, txData, from, err := evmtypes.UnpackEthMsg(msg) - if err != nil { - return ctx, err - } - - txFee, txGasLimit, err = CheckDisabledCreateCallAndUpdateTxFee( - txData.GetTo(), - from, - txGasLimit, - txData.GetGas(), - enableCreate, - enableCall, - baseFee, - txData.Fee(), - txData.TxType(), - evmDenom, - txFee, - ) - if err != nil { - return ctx, err - } - } - - if err := CheckTxFee(txFeeInfo, txFee, txGasLimit); err != nil { - return ctx, err - } - - return next(ctx, tx, simulate) -} - -// FIXME: split this function into multiple ones -// CheckDisabledCreateCallAndUpdateTxFee checks if contract creation or call are disabled through governance -// and updates the transaction fee by adding the message fee to the cumulative transaction fee -func CheckDisabledCreateCallAndUpdateTxFee( - to *common.Address, - from sdk.AccAddress, - txGasLimit, gasLimit uint64, - enableCreate, enableCall bool, - baseFee *big.Int, - msgFee *big.Int, - txType byte, - denom string, - txFee sdk.Coins, -) (sdk.Coins, uint64, error) { - // Validate `From` field - if from != nil { - return nil, 0, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid from address %s, expect empty string", from) - } - - txGasLimit += gasLimit - - // return error if contract creation or call are disabled through governance - if !enableCreate && to == nil { - return nil, 0, errorsmod.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract") - } else if !enableCall && to != nil { - return nil, 0, errorsmod.Wrap(evmtypes.ErrCallDisabled, "failed to call contract") - } - - if baseFee == nil && txType == ethtypes.DynamicFeeTxType { - return nil, 0, errorsmod.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported") - } - - txFee = txFee.Add(sdk.Coin{Denom: denom, Amount: sdkmath.NewIntFromBigInt(msgFee)}) - return txFee, txGasLimit, nil -} - -// FIXME: this shouldn't be required if the tx was an Ethereum transaction type -func ValidateTx(tx sdk.Tx) (*tx.Fee, error) { - err := tx.ValidateBasic() - // ErrNoSignatures is fine with eth tx - if err != nil && !errors.Is(err, errortypes.ErrNoSignatures) { - return nil, errorsmod.Wrap(err, "tx basic validation failed") - } - - // For eth type cosmos tx, some fields should be verified as zero values, - // since we will only verify the signature against the hash of the MsgEthereumTx.Data - wrapperTx, ok := tx.(protoTxProvider) - if !ok { - return nil, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx) - } - - protoTx := wrapperTx.GetProtoTx() - body := protoTx.Body - if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 { - return nil, errorsmod.Wrap(errortypes.ErrInvalidRequest, - "for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty") - } - - if len(body.ExtensionOptions) != 1 { - return nil, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1") - } - - authInfo := protoTx.AuthInfo - if len(authInfo.SignerInfos) > 0 { - return nil, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty") - } - - if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { - return nil, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty") - } - - sigs := protoTx.Signatures - if len(sigs) > 0 { - return nil, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx Signatures should be empty") - } - - return authInfo.Fee, nil -} - -func CheckTxFee(txFeeInfo *tx.Fee, txFee sdk.Coins, txGasLimit uint64) error { - if txFeeInfo == nil { - return nil - } - - if !txFeeInfo.Amount.IsEqual(txFee) { - return errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", txFeeInfo.Amount, txFee) - } - - if txFeeInfo.GasLimit != txGasLimit { - return errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", txFeeInfo.GasLimit, txGasLimit) - } - - return nil -} diff --git a/app/ante/evm/06_account_verification.go b/app/ante/evm/06_account_verification.go deleted file mode 100644 index f779339977..0000000000 --- a/app/ante/evm/06_account_verification.go +++ /dev/null @@ -1,88 +0,0 @@ -// 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" - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/common" - "github.com/evmos/evmos/v16/x/evm/keeper" - "github.com/evmos/evmos/v16/x/evm/statedb" - evmtypes "github.com/evmos/evmos/v16/x/evm/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 exist, 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 _, msg := range tx.GetMsgs() { - _, txData, from, err := evmtypes.UnpackEthMsg(msg) - if err != nil { - return ctx, err - } - - fromAddr := common.BytesToAddress(from) - account := avd.evmKeeper.GetAccount(ctx, fromAddr) - if err := VerifyAccountBalance(ctx, avd.ak, account, fromAddr, txData); err != nil { - return ctx, err - } - } - return next(ctx, tx, simulate) -} - -// VerifyAccountBalance checks that the sender balance is greater than the total transaction cost. -func VerifyAccountBalance( - ctx sdk.Context, - accountKeeper evmtypes.AccountKeeper, - account *statedb.Account, - from common.Address, - txData evmtypes.TxData, -) error { - // check whether the sender address is EOA - if account != nil && account.IsContract() { - return errorsmod.Wrapf( - errortypes.ErrInvalidType, - "the sender is not EOA: address %s", from, - ) - } - - if account == nil { - acc := accountKeeper.NewAccountWithAddress(ctx, from.Bytes()) - accountKeeper.SetAccount(ctx, acc) - account = statedb.NewEmptyAccount() - } - - if err := keeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(account.Balance), txData); err != nil { - return errorsmod.Wrap(err, "failed to check sender balance") - } - - return nil -} diff --git a/app/ante/evm/07_can_transfer.go b/app/ante/evm/07_can_transfer.go deleted file mode 100644 index 3749af59fc..0000000000 --- a/app/ante/evm/07_can_transfer.go +++ /dev/null @@ -1,111 +0,0 @@ -// 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" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" - "github.com/evmos/evmos/v16/x/evm/statedb" - evmtypes "github.com/evmos/evmos/v16/x/evm/types" -) - -// 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())) - baseFee := ctd.evmKeeper.GetBaseFee(ctx, ethCfg) - isLondon := evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) - - if isLondon && baseFee == nil { - return ctx, errorsmod.Wrap( - evmtypes.ErrInvalidBaseFee, - "base fee is supported but evm block context value is nil", - ) - } - - 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)) - } - - 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 err := CanTransfer(ctx, ctd.evmKeeper, coreMsg, baseFee, ethCfg, params, isLondon); err != nil { - return ctx, err - } - } - - return next(ctx, tx, simulate) -} - -// CanTransfer checks if the sender is allowed to transfer funds according to the EVM block -func CanTransfer( - ctx sdk.Context, - evmKeeper EVMKeeper, - msg core.Message, - baseFee *big.Int, - ethCfg *params.ChainConfig, - params evmtypes.Params, - isLondon bool, -) error { - if isLondon && msg.GasFeeCap().Cmp(baseFee) < 0 { - return errorsmod.Wrapf( - errortypes.ErrInsufficientFee, - "max fee per gas less than block base fee (%s < %s)", - msg.GasFeeCap(), baseFee, - ) - } - - // NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below - cfg := &statedb.EVMConfig{ - ChainConfig: ethCfg, - Params: params, - CoinBase: common.Address{}, - BaseFee: baseFee, - } - - stateDB := statedb.New(ctx, evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) - evm := evmKeeper.NewEVM(ctx, msg, 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 msg.Value().Sign() > 0 && !evm.Context.CanTransfer(stateDB, msg.From(), msg.Value()) { - return errorsmod.Wrapf( - errortypes.ErrInsufficientFunds, - "failed to transfer %s from address %s using the EVM block context transfer function", - msg.Value(), - msg.From(), - ) - } - - return nil -} diff --git a/app/ante/evm/09_gas_consume.go b/app/ante/evm/09_gas_consume.go deleted file mode 100644 index 4628bdb7cc..0000000000 --- a/app/ante/evm/09_gas_consume.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright Tharsis Labs Ltd.(Evmos) -// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) -package evm - -import ( - "math" - "math/big" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/common" - anteutils "github.com/evmos/evmos/v16/app/ante/utils" - "github.com/evmos/evmos/v16/types" - "github.com/evmos/evmos/v16/x/evm/keeper" - evmtypes "github.com/evmos/evmos/v16/x/evm/types" -) - -// EthGasConsumeDecorator validates enough intrinsic gas for the transaction and -// gas consumption. -type EthGasConsumeDecorator struct { - bankKeeper anteutils.BankKeeper - distributionKeeper anteutils.DistributionKeeper - evmKeeper EVMKeeper - stakingKeeper anteutils.StakingKeeper - maxGasWanted uint64 -} - -// NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator -func NewEthGasConsumeDecorator( - bankKeeper anteutils.BankKeeper, - distributionKeeper anteutils.DistributionKeeper, - evmKeeper EVMKeeper, - stakingKeeper anteutils.StakingKeeper, - maxGasWanted uint64, -) EthGasConsumeDecorator { - return EthGasConsumeDecorator{ - bankKeeper, - distributionKeeper, - evmKeeper, - stakingKeeper, - 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. -// If the balance is not sufficient, it will be attempted to withdraw enough staking rewards -// for the payment. -// -// 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 has neither enough balance nor staking rewards 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) { - gasWanted := uint64(0) - // gas consumption limit already checked during CheckTx so there's no need to - // verify it again during ReCheckTx - if ctx.IsReCheckTx() { - // Use new context with gasWanted = 0 - // Otherwise, there's an error on txmempool.postCheck (tendermint) - // that is not bubbled up. Thus, the Tx never runs on DeliverMode - // Error: "gas wanted -1 is negative" - // For more information, see issue #1554 - // https://github.com/evmos/ethermint/issues/1554 - newCtx := ctx.WithGasMeter(types.NewInfiniteGasMeterWithLimit(gasWanted)) - return next(newCtx, tx, simulate) - } - - evmParams := egcd.evmKeeper.GetParams(ctx) - evmDenom := evmParams.GetEvmDenom() - chainCfg := evmParams.GetChainConfig() - ethCfg := chainCfg.EthereumConfig(egcd.evmKeeper.ChainID()) - - blockHeight := big.NewInt(ctx.BlockHeight()) - homestead := ethCfg.IsHomestead(blockHeight) - istanbul := ethCfg.IsIstanbul(blockHeight) - - // 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() { - _, txData, from, err := evmtypes.UnpackEthMsg(msg) - if err != nil { - return ctx, err - } - - gasWanted, minPriority, err = ConsumeGas( - ctx, - egcd.bankKeeper, - egcd.distributionKeeper, - egcd.evmKeeper, - egcd.stakingKeeper, - from, - txData, - minPriority, - gasWanted, - egcd.maxGasWanted, - evmDenom, - baseFee, - homestead, - istanbul, - ) - - if err != nil { - return ctx, err - } - } - - newCtx, err := CheckBlockGasLimit(ctx, gasWanted, minPriority) - if err != nil { - return ctx, err - } - - return next(newCtx, tx, simulate) -} - -// ConsumeGas consumes the gas from the user balance and returns the updated gasWanted and minPriority. -func ConsumeGas( - ctx sdk.Context, - bankKeeper anteutils.BankKeeper, - distributionKeeper anteutils.DistributionKeeper, - evmKeeper EVMKeeper, - stakingKeeper anteutils.StakingKeeper, - from sdk.AccAddress, - txData evmtypes.TxData, - minPriority int64, - gasWanted, maxGasWanted uint64, - evmDenom string, - baseFee *big.Int, - isHomestead, isIstanbul bool, -) (uint64, int64, error) { - gas := txData.GetGas() - - if ctx.IsCheckTx() && maxGasWanted != 0 { - // We can't trust the tx gas limit, because we'll refund the unused gas. - if gas > maxGasWanted { - gasWanted += maxGasWanted - } else { - gasWanted += gas - } - } else { - gasWanted += gas - } - - fees, err := keeper.VerifyFee(txData, evmDenom, baseFee, isHomestead, isIstanbul, ctx.IsCheckTx()) - if err != nil { - return gasWanted, minPriority, errorsmod.Wrapf(err, "failed to verify the fees") - } - - if err = DeductFee( - ctx, - bankKeeper, - distributionKeeper, - evmKeeper, - stakingKeeper, - fees, - from, - ); err != nil { - return gasWanted, minPriority, err - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - sdk.EventTypeTx, - sdk.NewAttribute(sdk.AttributeKeyFee, fees.String()), - ), - ) - - priority := evmtypes.GetTxPriority(txData, baseFee) - - if priority < minPriority { - minPriority = priority - } - - return gasWanted, minPriority, nil -} - -// deductFee checks if the fee payer has enough funds to pay for the fees and deducts them. -// If the spendable balance is not enough, it tries to claim enough staking rewards to cover the fees. -func DeductFee( - ctx sdk.Context, - bankKeeper anteutils.BankKeeper, - distributionKeeper anteutils.DistributionKeeper, - evmKeeper EVMKeeper, - stakingKeeper anteutils.StakingKeeper, - fees sdk.Coins, - feePayer sdk.AccAddress, -) error { - if fees.IsZero() { - return nil - } - - // If the account balance is not sufficient, try to withdraw enough staking rewards - if err := anteutils.ClaimStakingRewardsIfNecessary(ctx, bankKeeper, distributionKeeper, stakingKeeper, feePayer, fees); err != nil { - return err - } - - if err := evmKeeper.DeductTxCostsFromUserBalance(ctx, fees, common.BytesToAddress(feePayer)); err != nil { - return errorsmod.Wrapf(err, "failed to deduct transaction costs from user balance") - } - return nil -} - -// TODO: (@fedekunze) Why is this necessary? This seems to be a duplicate from the CheckGasWanted function. -func CheckBlockGasLimit(ctx sdk.Context, gasWanted uint64, minPriority int64) (sdk.Context, error) { - blockGasLimit := types.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 latter 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. - ctx = ctx. - WithGasMeter(types.NewInfiniteGasMeterWithLimit(gasWanted)). - WithPriority(minPriority) - - return ctx, nil -} diff --git a/app/ante/evm/10_increment_sequence.go b/app/ante/evm/10_increment_sequence.go deleted file mode 100644 index 468cc89950..0000000000 --- a/app/ante/evm/10_increment_sequence.go +++ /dev/null @@ -1,82 +0,0 @@ -// 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" - - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - - evmtypes "github.com/evmos/evmos/v16/x/evm/types" - - "github.com/ethereum/go-ethereum/common" -) - -// 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() { - _, txData, from, err := evmtypes.UnpackEthMsg(msg) - if err != nil { - return ctx, err - } - - // increase sequence of sender - acc := issd.ak.GetAccount(ctx, from) - if acc == nil { - return ctx, errorsmod.Wrapf( - errortypes.ErrUnknownAddress, - "account %s is nil", common.BytesToAddress(from.Bytes()), - ) - } - - if err := IncrementNonce(ctx, issd.ak, acc, txData.GetNonce()); err != nil { - return ctx, err - } - } - - return next(ctx, tx, simulate) -} - -// IncrementNonce increments the sequence of the account. -func IncrementNonce( - ctx sdk.Context, - accountKeeper evmtypes.AccountKeeper, - account authtypes.AccountI, - txNonce uint64, -) error { - nonce := account.GetSequence() - // we merged the nonce verification to nonce increment, so when tx includes multiple messages - // with same sender, they'll be accepted. - if txNonce != nonce { - return errorsmod.Wrapf( - errortypes.ErrInvalidSequence, - "invalid nonce; got %d, expected %d", txNonce, nonce, - ) - } - - nonce++ - - if err := account.SetSequence(nonce); err != nil { - return errorsmod.Wrapf(err, "failed to set sequence to %d", nonce) - } - - accountKeeper.SetAccount(ctx, account) - return nil -} diff --git a/app/ante/evm/12_emit_event.go b/app/ante/evm/12_emit_event.go deleted file mode 100644 index 2a8a9a87fe..0000000000 --- a/app/ante/evm/12_emit_event.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright Tharsis Labs Ltd.(Evmos) -// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) - -package evm - -import ( - "strconv" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" - evmtypes "github.com/evmos/evmos/v16/x/evm/types" -) - -// EthEmitEventDecorator emit events in ante handler in case of tx execution failed (out of block gas limit). -type EthEmitEventDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthEmitEventDecorator creates a new EthEmitEventDecorator -func NewEthEmitEventDecorator(evmKeeper EVMKeeper) EthEmitEventDecorator { - return EthEmitEventDecorator{evmKeeper} -} - -// AnteHandle emits some basic events for the eth messages -func (eeed EthEmitEventDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - // After eth tx passed ante handler, the fee is deducted and nonce increased, it shouldn't be ignored by json-rpc, - // we need to emit some basic events at the very end of ante handler to be indexed by tendermint. - blockTxIndex := eeed.evmKeeper.GetTxIndexTransient(ctx) - - 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)) - } - - txIdx := uint64(i) // nosec: G701 - EmitTxHashEvent(ctx, msgEthTx, blockTxIndex, txIdx) - } - - return next(ctx, tx, simulate) -} - -// EmitTxHashEvent emits the Ethereum tx -// -// FIXME: This is Technical debt. Ideally the sdk.Tx hash should be the Ethereum -// tx hash (msg.Hash) instead of using events for indexing Eth txs. -// TxIndex should be included in the header vote extension as part of ABCI++ -func EmitTxHashEvent(ctx sdk.Context, msg *evmtypes.MsgEthereumTx, blockTxIndex, msgIndex uint64) { - // emit ethereum tx hash as an event so that it can be indexed by Tendermint for query purposes - // it's emitted in ante handler, so we can query failed transaction (out of block gas limit). - ctx.EventManager().EmitEvent( - sdk.NewEvent( - evmtypes.EventTypeEthereumTx, - sdk.NewAttribute(evmtypes.AttributeKeyEthereumTxHash, msg.Hash), - sdk.NewAttribute(evmtypes.AttributeKeyTxIndex, strconv.FormatUint(blockTxIndex+msgIndex, 10)), // #nosec G701 - ), - ) -} diff --git a/app/ante/evm/eth.go b/app/ante/evm/eth.go new file mode 100644 index 0000000000..e70e7cf5cf --- /dev/null +++ b/app/ante/evm/eth.go @@ -0,0 +1,394 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) + +package evm + +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" + + anteutils "github.com/evmos/evmos/v16/app/ante/utils" + "github.com/evmos/evmos/v16/types" + "github.com/evmos/evmos/v16/x/evm/keeper" + "github.com/evmos/evmos/v16/x/evm/statedb" + evmtypes "github.com/evmos/evmos/v16/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 exist, 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 { + bankKeeper anteutils.BankKeeper + distributionKeeper anteutils.DistributionKeeper + evmKeeper EVMKeeper + stakingKeeper anteutils.StakingKeeper + maxGasWanted uint64 +} + +// NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator +func NewEthGasConsumeDecorator( + bankKeeper anteutils.BankKeeper, + distributionKeeper anteutils.DistributionKeeper, + evmKeeper EVMKeeper, + stakingKeeper anteutils.StakingKeeper, + maxGasWanted uint64, +) EthGasConsumeDecorator { + return EthGasConsumeDecorator{ + bankKeeper, + distributionKeeper, + evmKeeper, + stakingKeeper, + 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. +// If the balance is not sufficient, it will be attempted to withdraw enough staking rewards +// for the payment. +// +// 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 has neither enough balance nor staking rewards 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) { + gasWanted := uint64(0) + // gas consumption limit already checked during CheckTx so there's no need to + // verify it again during ReCheckTx + if ctx.IsReCheckTx() { + // Use new context with gasWanted = 0 + // Otherwise, there's an error on txmempool.postCheck (tendermint) + // that is not bubbled up. Thus, the Tx never runs on DeliverMode + // Error: "gas wanted -1 is negative" + // For more information, see issue #1554 + // https://github.com/evmos/ethermint/issues/1554 + newCtx := ctx.WithGasMeter(types.NewInfiniteGasMeterWithLimit(gasWanted)) + return next(newCtx, tx, simulate) + } + + evmParams := egcd.evmKeeper.GetParams(ctx) + evmDenom := evmParams.GetEvmDenom() + chainCfg := evmParams.GetChainConfig() + ethCfg := chainCfg.EthereumConfig(egcd.evmKeeper.ChainID()) + + blockHeight := big.NewInt(ctx.BlockHeight()) + homestead := ethCfg.IsHomestead(blockHeight) + istanbul := ethCfg.IsIstanbul(blockHeight) + 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)) + } + from := msgEthTx.GetFrom() + + 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() + } + + fees, err := keeper.VerifyFee(txData, evmDenom, baseFee, homestead, istanbul, ctx.IsCheckTx()) + if err != nil { + return ctx, errorsmod.Wrapf(err, "failed to verify the fees") + } + + if err = egcd.deductFee(ctx, fees, from); err != nil { + return ctx, err + } + + 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 := types.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 latter 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(types.NewInfiniteGasMeterWithLimit(gasWanted)). + WithPriority(minPriority) + + // we know that we have enough gas on the pool to cover the intrinsic gas + return next(newCtx, tx, simulate) +} + +// deductFee checks if the fee payer has enough funds to pay for the fees and deducts them. +// If the spendable balance is not enough, it tries to claim enough staking rewards to cover the fees. +func (egcd EthGasConsumeDecorator) deductFee(ctx sdk.Context, fees sdk.Coins, feePayer sdk.AccAddress) error { + if fees.IsZero() { + return nil + } + + // If the account balance is not sufficient, try to withdraw enough staking rewards + if err := anteutils.ClaimStakingRewardsIfNecessary(ctx, egcd.bankKeeper, egcd.distributionKeeper, egcd.stakingKeeper, feePayer, fees); err != nil { + return err + } + + if err := egcd.evmKeeper.DeductTxCostsFromUserBalance(ctx, fees, common.BytesToAddress(feePayer)); err != nil { + return errorsmod.Wrapf(err, "failed to deduct transaction costs from user balance") + } + return nil +} + +// 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 := &statedb.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) +} diff --git a/app/ante/evm/fee_checker.go b/app/ante/evm/fee_checker.go index c69f5648df..b6ee7b77cd 100644 --- a/app/ante/evm/fee_checker.go +++ b/app/ante/evm/fee_checker.go @@ -11,7 +11,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" - "github.com/ethereum/go-ethereum/params" anteutils "github.com/evmos/evmos/v16/app/ante/utils" evmostypes "github.com/evmos/evmos/v16/types" "github.com/evmos/evmos/v16/x/evm/types" @@ -36,72 +35,61 @@ func NewDynamicFeeChecker(k DynamicFeeEVMKeeper) anteutils.TxFeeChecker { denom := params.EvmDenom ethCfg := params.ChainConfig.EthereumConfig(k.ChainID()) - return FeeChecker(ctx, k, denom, ethCfg, feeTx) - } -} - -// FeeChecker returns the effective fee and priority for a given transaction. -func FeeChecker( - ctx sdk.Context, - k DynamicFeeEVMKeeper, - denom string, - ethConfig *params.ChainConfig, - feeTx sdk.FeeTx, -) (sdk.Coins, int64, error) { - baseFee := k.GetBaseFee(ctx, ethConfig) - if baseFee == nil { - // london hardfork is not enabled: fallback to min-gas-prices logic - return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx) - } + 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) + // 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 := feeTx.(authante.HasExtensionOptionsTx); ok { - for _, opt := range hasExtOptsTx.GetExtensionOptions() { - if extOpt, ok := opt.GetCachedValue().(*evmostypes.ExtensionOptionDynamicFeeTx); ok { - maxPriorityPrice = extOpt.MaxPriorityPrice - break + // get the priority tip cap from the extension option. + if hasExtOptsTx, ok := feeTx.(authante.HasExtensionOptionsTx); ok { + for _, opt := range hasExtOptsTx.GetExtensionOptions() { + if extOpt, ok := opt.GetCachedValue().(*evmostypes.ExtensionOptionDynamicFeeTx); ok { + maxPriorityPrice = extOpt.MaxPriorityPrice + break + } } } - } - // priority fee cannot be negative - if maxPriorityPrice.IsNegative() { - return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "max priority price cannot be negative") - } + // priority fee cannot be negative + if maxPriorityPrice.IsNegative() { + return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "max priority price cannot be negative") + } - gas := feeTx.GetGas() - feeCoins := feeTx.GetFee() - fee := feeCoins.AmountOfNoDenomValidation(denom) + gas := feeTx.GetGas() + feeCoins := feeTx.GetFee() + fee := feeCoins.AmountOfNoDenomValidation(denom) - feeCap := fee.Quo(sdkmath.NewIntFromUint64(gas)) - baseFeeInt := sdkmath.NewIntFromBigInt(baseFee) + feeCap := fee.Quo(sdkmath.NewIntFromUint64(gas)) + baseFeeInt := sdkmath.NewIntFromBigInt(baseFee) - if feeCap.LT(baseFeeInt) { - return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "gas prices too low, got: %s%s required: %s%s. Please retry using a higher gas price or a higher fee", feeCap, denom, baseFeeInt, denom) - } + if feeCap.LT(baseFeeInt) { + return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "gas prices too low, got: %s%s required: %s%s. Please retry using a higher gas price or a higher fee", feeCap, denom, baseFeeInt, denom) + } - // calculate the effective gas price using the EIP-1559 logic. - effectivePrice := sdkmath.NewIntFromBigInt(types.EffectiveGasPrice(baseFeeInt.BigInt(), feeCap.BigInt(), maxPriorityPrice.BigInt())) + // 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)), - }, - } + // 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) + bigPriority := effectivePrice.Sub(baseFeeInt).Quo(types.DefaultPriorityReduction) + priority := int64(math.MaxInt64) - if bigPriority.IsInt64() { - priority = bigPriority.Int64() - } + if bigPriority.IsInt64() { + priority = bigPriority.Int64() + } - return effectiveFee, priority, nil + return effectiveFee, priority, nil + } } // checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per diff --git a/app/ante/evm/11_gas_wanted.go b/app/ante/evm/fee_market.go similarity index 74% rename from app/ante/evm/11_gas_wanted.go rename to app/ante/evm/fee_market.go index 8384e5c778..393c692f4a 100644 --- a/app/ante/evm/11_gas_wanted.go +++ b/app/ante/evm/fee_market.go @@ -38,29 +38,16 @@ func (gwd GasWantedDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo blockHeight := big.NewInt(ctx.BlockHeight()) isLondon := ethCfg.IsLondon(blockHeight) - if err := CheckGasWanted(ctx, gwd.feeMarketKeeper, tx, isLondon); err != nil { - return ctx, err - } - - return next(ctx, tx, simulate) -} - -func CheckGasWanted(ctx sdk.Context, feeMarketKeeper FeeMarketKeeper, tx sdk.Tx, isLondon bool) error { - if !isLondon { - return nil - } - feeTx, ok := tx.(sdk.FeeTx) - if !ok { - return nil + if !ok || !isLondon { + return next(ctx, tx, simulate) } gasWanted := feeTx.GetGas() - // return error if the tx gas is greater than the block limit (max gas) blockGasLimit := types.BlockGasLimit(ctx) if gasWanted > blockGasLimit { - return errorsmod.Wrapf( + return ctx, errorsmod.Wrapf( errortypes.ErrOutOfGas, "tx gas (%d) exceeds block gas limit (%d)", gasWanted, @@ -68,15 +55,14 @@ func CheckGasWanted(ctx sdk.Context, feeMarketKeeper FeeMarketKeeper, tx sdk.Tx, ) } - isBaseFeeEnabled := feeMarketKeeper.GetBaseFeeEnabled(ctx) - if !isBaseFeeEnabled { - return nil - } + isBaseFeeEnabled := gwd.feeMarketKeeper.GetBaseFeeEnabled(ctx) // Add total gasWanted to cumulative in block transientStore in FeeMarket module - if _, err := feeMarketKeeper.AddTransientGasWanted(ctx, gasWanted); err != nil { - return errorsmod.Wrapf(err, "failed to add gas wanted to transient store") + if isBaseFeeEnabled { + if _, err := gwd.feeMarketKeeper.AddTransientGasWanted(ctx, gasWanted); err != nil { + return ctx, errorsmod.Wrapf(err, "failed to add gas wanted to transient store") + } } - return nil + return next(ctx, tx, simulate) } diff --git a/app/ante/evm/fees.go b/app/ante/evm/fees.go new file mode 100644 index 0000000000..140a5af1c0 --- /dev/null +++ b/app/ante/evm/fees.go @@ -0,0 +1,152 @@ +// 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" + 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/v16/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 +} + +// 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 +} + +// 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, + } +} + +// 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() { + 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 (%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 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) + } + evmParams := mfd.evmKeeper.GetParams(ctx) + chainCfg := evmParams.GetChainConfig() + 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 := evmParams.GetEvmDenom() + 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) +} diff --git a/app/ante/evm/mono.go b/app/ante/evm/mono.go deleted file mode 100644 index f9bb421e3e..0000000000 --- a/app/ante/evm/mono.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright Tharsis Labs Ltd.(Evmos) -// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) - -package evm - -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" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" - anteutils "github.com/evmos/evmos/v16/app/ante/utils" - evmtypes "github.com/evmos/evmos/v16/x/evm/types" -) - -var _ sdk.AnteDecorator = &EthSetupContextDecorator{} - -// MonoDecorator is a single decorator that handles all the prechecks for -// ethereum transactions. -type MonoDecorator struct { - accountKeeper evmtypes.AccountKeeper - bankKeeper evmtypes.BankKeeper - feeMarketKeeper FeeMarketKeeper - evmKeeper EVMKeeper - distributionKeeper anteutils.DistributionKeeper - stakingKeeper anteutils.StakingKeeper - maxGasWanted uint64 -} - -type DecoratorUtils struct { - EvmParams evmtypes.Params - EthConfig *params.ChainConfig - Rules params.Rules - Signer ethtypes.Signer - BaseFee *big.Int - EvmDenom string - MempoolMinGasPrice sdkmath.LegacyDec - GlobalMinGasPrice sdkmath.LegacyDec - BlockTxIndex uint64 - TxGasLimit uint64 - GasWanted uint64 - MinPriority int64 - TxFee sdk.Coins -} - -// NewMonoDecorator creates a new MonoDecorator -func NewMonoDecorator( - accountKeeper evmtypes.AccountKeeper, - bankKeeper evmtypes.BankKeeper, - feeMarketKeeper FeeMarketKeeper, - evmKeeper EVMKeeper, - distributionKeeper anteutils.DistributionKeeper, - stakingKeeper anteutils.StakingKeeper, - maxGasWanted uint64, -) MonoDecorator { - return MonoDecorator{ - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, - feeMarketKeeper: feeMarketKeeper, - evmKeeper: evmKeeper, - distributionKeeper: distributionKeeper, - stakingKeeper: stakingKeeper, - maxGasWanted: maxGasWanted, - } -} - -// NewUtils returns a new DecoratorUtils instance. -func (md MonoDecorator) NewUtils(ctx sdk.Context) (*DecoratorUtils, error) { - evmParams := md.evmKeeper.GetParams(ctx) - chainCfg := evmParams.GetChainConfig() - ethCfg := chainCfg.EthereumConfig(md.evmKeeper.ChainID()) - blockHeight := big.NewInt(ctx.BlockHeight()) - rules := ethCfg.Rules(blockHeight, true) - baseFee := md.evmKeeper.GetBaseFee(ctx, ethCfg) - feeMarketParams := md.feeMarketKeeper.GetParams(ctx) - - if rules.IsLondon && baseFee == nil { - return nil, errorsmod.Wrap( - evmtypes.ErrInvalidBaseFee, - "base fee is supported but evm block context value is nil", - ) - } - - return &DecoratorUtils{ - EvmParams: evmParams, - EthConfig: ethCfg, - Rules: rules, - Signer: ethtypes.MakeSigner(ethCfg, blockHeight), - BaseFee: baseFee, - MempoolMinGasPrice: ctx.MinGasPrices().AmountOf(evmParams.EvmDenom), - GlobalMinGasPrice: feeMarketParams.MinGasPrice, - EvmDenom: evmParams.EvmDenom, - BlockTxIndex: md.evmKeeper.GetTxIndexTransient(ctx), - TxGasLimit: 0, - GasWanted: 0, - MinPriority: int64(math.MaxInt64), - TxFee: sdk.Coins{}, - }, nil -} - -// AnteHandle handles the entire decorator chain using a mono decorator. -func (md MonoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - accountExpenses := make(map[string]*EthVestingExpenseTracker) - - var txFeeInfo *txtypes.Fee - if !ctx.IsReCheckTx() { - txFeeInfo, err = ValidateTx(tx) - if err != nil { - return ctx, err - } - } - - // 1. setup ctx - ctx, err = SetupContext(ctx, tx, md.evmKeeper) - if err != nil { - return ctx, err - } - - // 2. get utils - decUtils, err := md.NewUtils(ctx) - if err != nil { - return ctx, err - } - - // Use the lowest priority of all the messages as the final one. - for i, msg := range tx.GetMsgs() { - ethMsg, txData, from, err := evmtypes.UnpackEthMsg(msg) - if err != nil { - return ctx, err - } - - feeAmt := txData.Fee() - gas := txData.GetGas() - fee := sdkmath.LegacyNewDecFromBigInt(feeAmt) - gasLimit := sdkmath.LegacyNewDecFromBigInt(new(big.Int).SetUint64(gas)) - - // 2. mempool inclusion fee - if ctx.IsCheckTx() && !simulate { - if err := CheckMempoolFee(fee, decUtils.MempoolMinGasPrice, gasLimit, decUtils.Rules.IsLondon); err != nil { - return ctx, err - } - } - - // 3. min gas price (global min fee) - if txData.TxType() == ethtypes.DynamicFeeTxType && decUtils.BaseFee != nil { - feeAmt = txData.EffectiveFee(decUtils.BaseFee) - fee = sdkmath.LegacyNewDecFromBigInt(feeAmt) - } - - if err := CheckGlobalFee(fee, decUtils.GlobalMinGasPrice, gasLimit); err != nil { - return ctx, err - } - - // 4. validate basic - txFee, txGasLimit, err := CheckDisabledCreateCallAndUpdateTxFee( - txData.GetTo(), - from, - decUtils.TxGasLimit, - gas, - decUtils.EvmParams.EnableCreate, - decUtils.EvmParams.EnableCall, - decUtils.BaseFee, - txData.Fee(), - txData.TxType(), - decUtils.EvmDenom, - decUtils.TxFee, - ) - if err != nil { - return ctx, err - } - decUtils.TxFee = txFee - decUtils.TxGasLimit = txGasLimit - - // 5. signature verification - if err := SignatureVerification(ethMsg, decUtils.Signer, decUtils.EvmParams.AllowUnprotectedTxs); err != nil { - return ctx, err - } - - // NOTE: sender address has been verified and cached - from = ethMsg.GetFrom() - - // 6. account balance verification - fromAddr := common.HexToAddress(ethMsg.From) - // // TODO: Use account from AccountKeeper instead - account := md.evmKeeper.GetAccount(ctx, fromAddr) - if err := VerifyAccountBalance(ctx, md.accountKeeper, account, fromAddr, txData); err != nil { - return ctx, err - } - - // 7. can transfer - coreMsg, err := ethMsg.AsMessage(decUtils.Signer, decUtils.BaseFee) - if err != nil { - return ctx, errorsmod.Wrapf( - err, - "failed to create an ethereum core.Message from signer %T", decUtils.Signer, - ) - } - - if err := CanTransfer(ctx, md.evmKeeper, coreMsg, decUtils.BaseFee, decUtils.EthConfig, decUtils.EvmParams, decUtils.Rules.IsLondon); err != nil { - return ctx, err - } - - // 8. vesting - value := txData.GetValue() - acc := md.accountKeeper.GetAccount(ctx, from) - if acc == nil { - // safety check: shouldn't happen - return ctx, errorsmod.Wrapf(errortypes.ErrUnknownAddress, - "account %s does not exist", acc) - } - - if err := CheckVesting(ctx, md.bankKeeper, acc, accountExpenses, value, decUtils.EvmDenom); err != nil { - return ctx, err - } - - // 9. gas consumption - gasWanted, minPriority, err := ConsumeGas( - ctx, - md.bankKeeper, - md.distributionKeeper, - md.evmKeeper, - md.stakingKeeper, - from, - txData, - decUtils.MinPriority, - decUtils.GasWanted, - md.maxGasWanted, - decUtils.EvmDenom, - decUtils.BaseFee, - decUtils.Rules.IsHomestead, - decUtils.Rules.IsIstanbul, - ) - if err != nil { - return ctx, err - } - - decUtils.GasWanted = gasWanted - decUtils.MinPriority = minPriority - - // 10. increment sequence - if err := IncrementNonce(ctx, md.accountKeeper, acc, txData.GetNonce()); err != nil { - return ctx, err - } - - // 11. gas wanted - if err := CheckGasWanted(ctx, md.feeMarketKeeper, tx, decUtils.Rules.IsLondon); err != nil { - return ctx, err - } - - // 12. emit events - txIdx := uint64(i) // nosec: G701 - EmitTxHashEvent(ctx, ethMsg, decUtils.BlockTxIndex, txIdx) - } - - if err := CheckTxFee(txFeeInfo, decUtils.TxFee, decUtils.TxGasLimit); err != nil { - return ctx, err - } - - ctx, err = CheckBlockGasLimit(ctx, decUtils.GasWanted, decUtils.MinPriority) - if err != nil { - return ctx, err - } - - return next(ctx, tx, simulate) -} diff --git a/app/ante/evm/setup_ctx.go b/app/ante/evm/setup_ctx.go new file mode 100644 index 0000000000..261dfd3d88 --- /dev/null +++ b/app/ante/evm/setup_ctx.go @@ -0,0 +1,193 @@ +// Copyright Tharsis Labs Ltd.(Evmos) +// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) +package evm + +import ( + "errors" + "strconv" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + 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" + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmtypes "github.com/evmos/evmos/v16/x/evm/types" +) + +// 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) { + // 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. + esc.evmKeeper.ResetTransientGasUsed(ctx) + return next(newCtx, tx, simulate) +} + +// EthEmitEventDecorator emit events in ante handler in case of tx execution failed (out of block gas limit). +type EthEmitEventDecorator struct { + evmKeeper EVMKeeper +} + +// NewEthEmitEventDecorator creates a new EthEmitEventDecorator +func NewEthEmitEventDecorator(evmKeeper EVMKeeper) EthEmitEventDecorator { + return EthEmitEventDecorator{evmKeeper} +} + +// AnteHandle emits some basic events for the eth messages +func (eeed EthEmitEventDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + // After eth tx passed ante handler, the fee is deducted and nonce increased, it shouldn't be ignored by json-rpc, + // we need to emit some basic events at the very end of ante handler to be indexed by tendermint. + txIndex := eeed.evmKeeper.GetTxIndexTransient(ctx) + + 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)) + } + + // emit ethereum tx hash as an event so that it can be indexed by Tendermint for query purposes + // it's emitted in ante handler, so we can query failed transaction (out of block gas limit). + ctx.EventManager().EmitEvent(sdk.NewEvent( + evmtypes.EventTypeEthereumTx, + sdk.NewAttribute(evmtypes.AttributeKeyEthereumTxHash, msgEthTx.Hash), + sdk.NewAttribute(evmtypes.AttributeKeyTxIndex, strconv.FormatUint(txIndex+uint64(i), 10)), // #nosec G701 + )) + } + + return next(ctx, tx, simulate) +} + +// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures +type EthValidateBasicDecorator struct { + evmKeeper EVMKeeper +} + +// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator +func NewEthValidateBasicDecorator(ek EVMKeeper) EthValidateBasicDecorator { + return EthValidateBasicDecorator{ + evmKeeper: ek, + } +} + +// AnteHandle handles basic validation of tx +func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // no need to validate basic on recheck tx, call next antehandler + if ctx.IsReCheckTx() { + return next(ctx, tx, simulate) + } + + err := tx.ValidateBasic() + // ErrNoSignatures is fine with eth tx + if err != nil && !errors.Is(err, errortypes.ErrNoSignatures) { + return ctx, errorsmod.Wrap(err, "tx basic validation failed") + } + + // For eth type cosmos tx, some fields should be verified as zero values, + // since we will only verify the signature against the hash of the MsgEthereumTx.Data + wrapperTx, ok := tx.(protoTxProvider) + if !ok { + return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx) + } + + protoTx := wrapperTx.GetProtoTx() + body := protoTx.Body + if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 { + return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, + "for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty") + } + + if len(body.ExtensionOptions) != 1 { + return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1") + } + + authInfo := protoTx.AuthInfo + if len(authInfo.SignerInfos) > 0 { + return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty") + } + + if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { + return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty") + } + + sigs := protoTx.Signatures + if len(sigs) > 0 { + return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx Signatures should be empty") + } + + txFee := sdk.Coins{} + txGasLimit := uint64(0) + + evmParams := vbd.evmKeeper.GetParams(ctx) + chainCfg := evmParams.GetChainConfig() + chainID := vbd.evmKeeper.ChainID() + ethCfg := chainCfg.EthereumConfig(chainID) + baseFee := vbd.evmKeeper.GetBaseFee(ctx, ethCfg) + enableCreate := evmParams.GetEnableCreate() + enableCall := evmParams.GetEnableCall() + evmDenom := evmParams.GetEvmDenom() + + for _, msg := range protoTx.GetMsgs() { + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } + + // Validate `From` field + if msgEthTx.From != "" { + return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid From %s, expect empty string", msgEthTx.From) + } + + txGasLimit += msgEthTx.GetGas() + + txData, err := evmtypes.UnpackTxData(msgEthTx.Data) + if err != nil { + return ctx, errorsmod.Wrap(err, "failed to unpack MsgEthereumTx Data") + } + + // return error if contract creation or call are disabled through governance + if !enableCreate && txData.GetTo() == nil { + return ctx, errorsmod.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract") + } else if !enableCall && txData.GetTo() != nil { + return ctx, errorsmod.Wrap(evmtypes.ErrCallDisabled, "failed to call contract") + } + + if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType { + return ctx, errorsmod.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported") + } + + txFee = txFee.Add(sdk.Coin{Denom: evmDenom, Amount: sdkmath.NewIntFromBigInt(txData.Fee())}) + } + + if !authInfo.Fee.Amount.IsEqual(txFee) { + return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee) + } + + if authInfo.Fee.GasLimit != txGasLimit { + return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit) + } + + return next(ctx, tx, simulate) +} diff --git a/app/ante/evm/05_signature_verification.go b/app/ante/evm/sigverify.go similarity index 65% rename from app/ante/evm/05_signature_verification.go rename to app/ante/evm/sigverify.go index a82add44a5..a427a332ca 100644 --- a/app/ante/evm/05_signature_verification.go +++ b/app/ante/evm/sigverify.go @@ -36,7 +36,6 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s ethCfg := chainCfg.EthereumConfig(chainID) blockNum := big.NewInt(ctx.BlockHeight()) signer := ethtypes.MakeSigner(ethCfg, blockNum) - allowUnprotectedTxs := evmParams.GetAllowUnprotectedTxs() for _, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) @@ -44,40 +43,26 @@ func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) } - err := SignatureVerification(msgEthTx, signer, allowUnprotectedTxs) - if err != nil { - return ctx, err + allowUnprotectedTxs := evmParams.GetAllowUnprotectedTxs() + ethTx := msgEthTx.AsTransaction() + if !allowUnprotectedTxs && !ethTx.Protected() { + return ctx, errorsmod.Wrapf( + errortypes.ErrNotSupported, + "rejected unprotected Ethereum transaction. Please EIP155 sign your transaction to protect it against replay-attacks") } - } - - return next(ctx, tx, simulate) -} -// SignatureVerification checks that the registered chain id is the same as the one on the message, and -// that the signer address matches the one defined on the message. -func SignatureVerification( - msg *evmtypes.MsgEthereumTx, - signer ethtypes.Signer, - allowUnprotectedTxs bool, -) error { - ethTx := msg.AsTransaction() - - if !allowUnprotectedTxs && !ethTx.Protected() { - return errorsmod.Wrapf( - errortypes.ErrNotSupported, - "rejected unprotected Ethereum transaction. Please EIP155 sign your transaction to protect it against replay-attacks") - } + sender, err := signer.Sender(ethTx) + if err != nil { + return ctx, errorsmod.Wrapf( + errortypes.ErrorInvalidSigner, + "couldn't retrieve sender address from the ethereum transaction: %s", + err.Error(), + ) + } - sender, err := signer.Sender(ethTx) - if err != nil { - return errorsmod.Wrapf( - errortypes.ErrorInvalidSigner, - "couldn't retrieve sender address from the ethereum transaction: %s", - err.Error(), - ) + // set up the sender to the transaction field if not already + msgEthTx.From = sender.Hex() } - // set up the sender to the transaction field if not already - msg.From = sender.Hex() - return nil + return next(ctx, tx, simulate) } diff --git a/app/ante/evm/08_vesting.go b/app/ante/evm/vesting.go similarity index 64% rename from app/ante/evm/08_vesting.go rename to app/ante/evm/vesting.go index 9ba5bd5896..6d8e52dc0a 100644 --- a/app/ante/evm/08_vesting.go +++ b/app/ante/evm/vesting.go @@ -9,7 +9,6 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" evmtypes "github.com/evmos/evmos/v16/x/evm/types" vestingtypes "github.com/evmos/evmos/v16/x/vesting/types" ) @@ -22,9 +21,9 @@ type EthVestingTransactionDecorator struct { ek EVMKeeper } -// EthVestingExpenseTracker tracks both the total transaction value to be sent across Ethereum +// ethVestingExpenseTracker tracks both the total transaction value to be sent across Ethereum // messages and the maximum spendable value for a given account. -type EthVestingExpenseTracker struct { +type ethVestingExpenseTracker struct { // total is the total value to be spent across a transaction with one or more Ethereum message calls total *big.Int // spendable is the maximum value that can be spent @@ -50,78 +49,61 @@ func NewEthVestingTransactionDecorator(ak evmtypes.AccountKeeper, bk evmtypes.Ba func (vtd EthVestingTransactionDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { // Track the total value to be spent by each address across all messages and ensure // that no account can exceed its spendable balance. - accountExpenses := make(map[string]*EthVestingExpenseTracker) + accountExpenses := make(map[string]*ethVestingExpenseTracker) denom := vtd.ek.GetParams(ctx).EvmDenom for _, msg := range tx.GetMsgs() { - _, txData, from, err := evmtypes.UnpackEthMsg(msg) - if err != nil { - return ctx, err + msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, + "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil), + ) } - value := txData.GetValue() - - acc := vtd.ak.GetAccount(ctx, from) + acc := vtd.ak.GetAccount(ctx, msgEthTx.GetFrom()) if acc == nil { return ctx, errorsmod.Wrapf(errortypes.ErrUnknownAddress, "account %s does not exist", acc) } - if err := CheckVesting(ctx, vtd.bk, acc, accountExpenses, value, denom); err != nil { - return ctx, err + // Check that this decorator only applies to clawback vesting accounts + clawbackAccount, isClawback := acc.(*vestingtypes.ClawbackVestingAccount) + if !isClawback { + continue } - } - return next(ctx, tx, simulate) -} + // Check to make sure that the account does not exceed its spendable balances. + // This transaction would fail in processing, so we should prevent it from + // moving past the AnteHandler. + msgValue := msgEthTx.AsTransaction().Value() -// CheckVesting checks if the account is a clawback vesting account and if so, -// checks that the account has sufficient unlocked balances to cover the -// transaction. -func CheckVesting( - ctx sdk.Context, - bankKeeper evmtypes.BankKeeper, - account authtypes.AccountI, - accountExpenses map[string]*EthVestingExpenseTracker, - addedExpense *big.Int, - denom string, -) error { - clawbackAccount, isClawback := account.(*vestingtypes.ClawbackVestingAccount) - if !isClawback { - return nil - } - - // Check to make sure that the account does not exceed its spendable balances. - // This transaction would fail in processing, so we should prevent it from - // moving past the AnteHandler. - - expenses, err := UpdateAccountExpenses(ctx, bankKeeper, accountExpenses, clawbackAccount, addedExpense, denom) - if err != nil { - return err - } + expenses, err := vtd.updateAccountExpenses(ctx, accountExpenses, clawbackAccount, msgValue, denom) + if err != nil { + return ctx, err + } - total := expenses.total - spendable := expenses.spendable + total := expenses.total + spendable := expenses.spendable - if total.Cmp(spendable) > 0 { - return errorsmod.Wrapf(vestingtypes.ErrInsufficientUnlockedCoins, - "clawback vesting account has insufficient unlocked tokens to execute transaction: %s < %s", spendable.String(), total.String(), - ) + if total.Cmp(spendable) > 0 { + return ctx, errorsmod.Wrapf(vestingtypes.ErrInsufficientUnlockedCoins, + "clawback vesting account has insufficient unlocked tokens to execute transaction: %s < %s", spendable.String(), total.String(), + ) + } } - return nil + return next(ctx, tx, simulate) } -// UpdateAccountExpenses updates or sets the totalSpend for the given account, then +// updateAccountExpenses updates or sets the totalSpend for the given account, then // returns the new value. -func UpdateAccountExpenses( +func (vtd EthVestingTransactionDecorator) updateAccountExpenses( ctx sdk.Context, - bankKeeper evmtypes.BankKeeper, - accountExpenses map[string]*EthVestingExpenseTracker, + accountExpenses map[string]*ethVestingExpenseTracker, account *vestingtypes.ClawbackVestingAccount, addedExpense *big.Int, denom string, -) (*EthVestingExpenseTracker, error) { +) (*ethVestingExpenseTracker, error) { address := account.GetAddress() addrStr := address.String() @@ -132,7 +114,7 @@ func UpdateAccountExpenses( return expenses, nil } - balance := bankKeeper.GetBalance(ctx, address, denom) + balance := vtd.bk.GetBalance(ctx, address, denom) // Short-circuit if the balance is zero, since we require a non-zero balance to cover // gas fees at a minimum (these are defined to be non-zero). Note that this check @@ -153,7 +135,7 @@ func UpdateAccountExpenses( spendableValue = spendableBalance.Amount.BigInt() } - expenses = &EthVestingExpenseTracker{ + expenses = ðVestingExpenseTracker{ total: addedExpense, spendable: spendableValue, } diff --git a/app/ante/evm_benchmark_test.go b/app/ante/evm_benchmark_test.go deleted file mode 100644 index c602bcf3c0..0000000000 --- a/app/ante/evm_benchmark_test.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright Tharsis Labs Ltd.(Evmos) -// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) -package ante_test - -import ( - "fmt" - "math/big" - "testing" - - sdktypes "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/evmos/evmos/v16/app" - "github.com/evmos/evmos/v16/app/ante" - ethante "github.com/evmos/evmos/v16/app/ante/evm" - "github.com/evmos/evmos/v16/encoding" - cmmnfactory "github.com/evmos/evmos/v16/testutil/integration/common/factory" - "github.com/evmos/evmos/v16/testutil/integration/evmos/factory" - "github.com/evmos/evmos/v16/testutil/integration/evmos/grpc" - testkeyring "github.com/evmos/evmos/v16/testutil/integration/evmos/keyring" - "github.com/evmos/evmos/v16/testutil/integration/evmos/network" - evmostypes "github.com/evmos/evmos/v16/types" - evmtypes "github.com/evmos/evmos/v16/x/evm/types" -) - -type benchmarkSuite struct { - network *network.UnitTestNetwork - grpcHandler grpc.Handler - txFactory factory.TxFactory - keyring testkeyring.Keyring -} - -// Setup -var table = []struct { - name string - txType string - simulate bool -}{ - { - "evm_transfer_sim", - "evm_transfer", - true, - }, - { - "evm_transfer", - "evm_transfer", - false, - }, - { - "bank_msg_send_sim", - "bank_msg_send", - true, - }, - { - "bank_msg_send", - "bank_msg_send", - false, - }, -} - -func BenchmarkAnteHandler(b *testing.B) { - for _, v := range table { - // Reset chain on every tx type to have a clean state - // and a fair benchmark - b.StopTimer() - keyring := testkeyring.New(2) - unitNetwork := network.NewUnitTestNetwork( - network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...), - ) - grpcHandler := grpc.NewIntegrationHandler(unitNetwork) - txFactory := factory.New(unitNetwork, grpcHandler) - suite := benchmarkSuite{ - network: unitNetwork, - grpcHandler: grpcHandler, - txFactory: txFactory, - keyring: keyring, - } - - handlerOptions := suite.generateHandlerOptions() - ante := ante.NewAnteHandler(handlerOptions) - b.StartTimer() - - b.Run(fmt.Sprintf("tx_type_%v", v.name), func(b *testing.B) { - for i := 0; i < b.N; i++ { - // Stop timer while building the tx setup - b.StopTimer() - // Start with a clean block - if err := unitNetwork.NextBlock(); err != nil { - fmt.Println(err) - break - } - ctx := unitNetwork.GetContext() - - // Generate fresh tx type - tx, err := suite.generateTxType(v.txType) - if err != nil { - fmt.Println(err) - break - } - b.StartTimer() - - // Run benchmark - _, err = ante(ctx, tx, v.simulate) - if err != nil { - fmt.Println(err) - break - } - } - }) - } -} - -func (s *benchmarkSuite) generateTxType(txType string) (sdktypes.Tx, error) { - switch txType { - case "evm_transfer": - senderPriv := s.keyring.GetPrivKey(0) - receiver := s.keyring.GetKey(1) - txArgs := evmtypes.EvmTxArgs{ - To: &receiver.Addr, - Amount: big.NewInt(1000), - } - return s.txFactory.GenerateSignedEthTx(senderPriv, txArgs) - case "bank_msg_send": - sender := s.keyring.GetKey(1) - receiver := s.keyring.GetAccAddr(0) - bankmsg := banktypes.NewMsgSend( - sender.AccAddr, - receiver, - sdktypes.NewCoins( - sdktypes.NewCoin( - s.network.GetDenom(), - sdktypes.NewInt(1000), - ), - ), - ) - txArgs := cmmnfactory.CosmosTxArgs{Msgs: []sdktypes.Msg{bankmsg}} - return s.txFactory.BuildCosmosTx(sender.Priv, txArgs) - default: - return nil, fmt.Errorf("invalid tx type") - } -} - -func (s *benchmarkSuite) generateHandlerOptions() ante.HandlerOptions { - encCfg := encoding.MakeConfig(app.ModuleBasics) - return ante.HandlerOptions{ - Cdc: s.network.App.AppCodec(), - AccountKeeper: s.network.App.AccountKeeper, - BankKeeper: s.network.App.BankKeeper, - ExtensionOptionChecker: evmostypes.HasDynamicFeeExtensionOption, - EvmKeeper: s.network.App.EvmKeeper, - StakingKeeper: s.network.App.StakingKeeper, - FeegrantKeeper: s.network.App.FeeGrantKeeper, - DistributionKeeper: s.network.App.DistrKeeper, - IBCKeeper: s.network.App.IBCKeeper, - FeeMarketKeeper: s.network.App.FeeMarketKeeper, - SignModeHandler: encCfg.TxConfig.SignModeHandler(), - SigGasConsumer: ante.SigVerificationGasConsumer, - MaxTxGasWanted: 1_000_000_000, - TxFeeChecker: ethante.NewDynamicFeeChecker(s.network.App.EvmKeeper), - } -} diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 39e71840dd..d64bf6607d 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -12,12 +12,15 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + ibcante "github.com/cosmos/ibc-go/v7/modules/core/ante" ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" + cosmosante "github.com/evmos/evmos/v16/app/ante/cosmos" evmante "github.com/evmos/evmos/v16/app/ante/evm" anteutils "github.com/evmos/evmos/v16/app/ante/utils" evmtypes "github.com/evmos/evmos/v16/x/evm/types" + sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" vestingtypes "github.com/evmos/evmos/v16/x/vesting/types" ) @@ -77,3 +80,82 @@ func (options HandlerOptions) Validate() error { } return nil } + +// newEVMAnteHandler creates the default ante handler for Ethereum transactions +func newEVMAnteHandler(options HandlerOptions) sdk.AnteHandler { + 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), + ) +} + +// 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), + ) +} + +// 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), + ) +} diff --git a/precompiles/werc20/integration_test.go b/precompiles/werc20/integration_test.go index 61856cd28f..f2cf8885e4 100644 --- a/precompiles/werc20/integration_test.go +++ b/precompiles/werc20/integration_test.go @@ -289,15 +289,12 @@ var _ = Describe("WEVMOS Extension -", func() { It("should return the same error", func() { depositCheck := passCheck.WithExpPass(true).WithExpEvents(werc20.EventTypeDeposit) txArgsPrecompile, callArgsPrecompile := s.getTxAndCallArgs(erc20Call, contractData, werc20.DepositMethod) - // Increase the amount to 9e18 to trigger the insufficient balance error txArgsPrecompile.Amount = big.NewInt(9e18) - txArgsPrecompile.GasLimit = 50_000 _, _, errPrecompile := s.factory.CallContractAndCheckLogs(sender.Priv, txArgsPrecompile, callArgsPrecompile, depositCheck) Expect(errPrecompile).To(HaveOccurred(), "unexpected result calling contract") txArgsContract, callArgsContract := s.getTxAndCallArgs(erc20Call, contractDataOriginal, werc20.DepositMethod) - // Increase the amount to 9e18 to trigger the insufficient balance error txArgsContract.Amount = big.NewInt(9e18) txArgsContract.GasLimit = 50_000 @@ -538,12 +535,7 @@ var _ = Describe("WEVMOS Extension -", func() { // Transfer tokens txArgs, transferArgs := s.getTxAndCallArgs(directCall, contractData, erc20.TransferMethod, receiver.Addr, amount) - // Prefilling the gas price with the base fee to calculate expected balances after - // the transfer - baseFeeRes, err := s.grpcHandler.GetBaseFee() - Expect(err).ToNot(HaveOccurred(), "unexpected error querying base fee") - txArgs.GasPrice = baseFeeRes.BaseFee.BigInt() - + txArgs.GasPrice = big.NewInt(765625000) transferCoins := sdk.Coins{sdk.NewInt64Coin(s.tokenDenom, amount.Int64())} transferCheck := passCheck.WithExpEvents(erc20.EventTypeTransfer) @@ -569,12 +561,7 @@ var _ = Describe("WEVMOS Extension -", func() { // Transfer tokens txArgs, transferArgs := s.getTxAndCallArgs(directCall, contractData, erc20.TransferFromMethod, sender.Addr, receiver.Addr, amount) - // Prefilling the gas price with the base fee to calculate expected balances after - // the transfer - baseFeeRes, err := s.grpcHandler.GetBaseFee() - Expect(err).ToNot(HaveOccurred(), "unexpected error querying base fee") - txArgs.GasPrice = baseFeeRes.BaseFee.BigInt() - + txArgs.GasPrice = big.NewInt(765625000) transferCoins := sdk.Coins{sdk.NewInt64Coin(s.tokenDenom, amount.Int64())} transferCheck := passCheck.WithExpEvents(erc20.EventTypeTransfer, auth.EventTypeApproval) diff --git a/testutil/integration/common/factory/factory.go b/testutil/integration/common/factory/factory.go index 95532518f4..16a1448006 100644 --- a/testutil/integration/common/factory/factory.go +++ b/testutil/integration/common/factory/factory.go @@ -6,7 +6,6 @@ import ( abcitypes "github.com/cometbft/cometbft/abci/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" testutiltypes "github.com/cosmos/cosmos-sdk/types/module/testutil" - "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/evmos/evmos/v16/testutil/integration/evmos/grpc" "github.com/evmos/evmos/v16/testutil/integration/evmos/network" @@ -20,8 +19,6 @@ const ( // TxFactory is the interface that wraps the common methods to build and broadcast transactions // within cosmos chains type TxFactory interface { - // BuildCosmosTx builds a Cosmos tx with the provided private key and txArgs - BuildCosmosTx(privKey cryptotypes.PrivKey, txArgs CosmosTxArgs) (signing.Tx, error) // ExecuteCosmosTx builds, signs and broadcasts a Cosmos tx with the provided private key and txArgs ExecuteCosmosTx(privKey cryptotypes.PrivKey, txArgs CosmosTxArgs) (abcitypes.ResponseDeliverTx, error) } @@ -49,22 +46,14 @@ func New( } } -func (tf *IntegrationTxFactory) BuildCosmosTx(privKey cryptotypes.PrivKey, txArgs CosmosTxArgs) (signing.Tx, error) { - txBuilder, err := tf.buildTx(privKey, txArgs) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to build tx") - } - return txBuilder.GetTx(), nil -} - // ExecuteCosmosTx creates, signs and broadcasts a Cosmos transaction func (tf *IntegrationTxFactory) ExecuteCosmosTx(privKey cryptotypes.PrivKey, txArgs CosmosTxArgs) (abcitypes.ResponseDeliverTx, error) { - signedTx, err := tf.BuildCosmosTx(privKey, txArgs) + txBuilder, err := tf.buildTx(privKey, txArgs) if err != nil { - return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to generate tx") + return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to build tx") } - txBytes, err := tf.encodeTx(signedTx) + txBytes, err := tf.encodeTx(txBuilder) if err != nil { return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to encode tx") } diff --git a/testutil/integration/common/factory/helper.go b/testutil/integration/common/factory/helper.go index 0137a2c513..c2df55ba73 100644 --- a/testutil/integration/common/factory/helper.go +++ b/testutil/integration/common/factory/helper.go @@ -135,9 +135,9 @@ func (tf *IntegrationTxFactory) estimateGas(txArgs CosmosTxArgs, txBuilder clien } // encodeTx encodes the tx using the txConfig's encoder. -func (tf *IntegrationTxFactory) encodeTx(tx sdktypes.Tx) ([]byte, error) { +func (tf *IntegrationTxFactory) encodeTx(txBuilder client.TxBuilder) ([]byte, error) { txConfig := tf.ec.TxConfig - txBytes, err := txConfig.TxEncoder()(tx) + txBytes, err := txConfig.TxEncoder()(txBuilder.GetTx()) if err != nil { return nil, errorsmod.Wrap(err, "failed to encode tx") } diff --git a/testutil/integration/evmos/factory/factory.go b/testutil/integration/evmos/factory/factory.go index 3a4a17bd6e..58af40d26b 100644 --- a/testutil/integration/evmos/factory/factory.go +++ b/testutil/integration/evmos/factory/factory.go @@ -14,7 +14,6 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdktypes "github.com/cosmos/cosmos-sdk/types" testutiltypes "github.com/cosmos/cosmos-sdk/types/module/testutil" - "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/gogoproto/proto" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -43,8 +42,6 @@ type TxFactory interface { DeployContract(privKey cryptotypes.PrivKey, txArgs evmtypes.EvmTxArgs, deploymentData ContractDeploymentData) (common.Address, error) // ExecuteContractCall executes a contract call with the provided private key ExecuteContractCall(privKey cryptotypes.PrivKey, txArgs evmtypes.EvmTxArgs, callArgs CallArgs) (abcitypes.ResponseDeliverTx, error) - // GenerateSignedEthTx generates an Ethereum tx with the provided private key and txArgs but does not broadcast it. - GenerateSignedEthTx(privKey cryptotypes.PrivKey, txArgs evmtypes.EvmTxArgs) (signing.Tx, error) // ExecuteEthTx builds, signs and broadcasts an Ethereum tx with the provided private key and txArgs. // If the txArgs are not provided, they will be populated with default values or gas estimations. ExecuteEthTx(privKey cryptotypes.PrivKey, txArgs evmtypes.EvmTxArgs) (abcitypes.ResponseDeliverTx, error) @@ -77,21 +74,6 @@ func New( } } -// GenerateSignedEthTx generates an Ethereum tx with the provided private key and txArgs but does not broadcast it. -func (tf *IntegrationTxFactory) GenerateSignedEthTx(privKey cryptotypes.PrivKey, txArgs evmtypes.EvmTxArgs) (signing.Tx, error) { - msgEthereumTx, err := tf.createMsgEthereumTx(privKey, txArgs) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to create ethereum tx") - } - - signedMsg, err := signMsgEthereumTx(msgEthereumTx, privKey, tf.network.GetChainID()) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to sign ethereum tx") - } - - return tf.buildSignedTx(signedMsg) -} - // CallContractAndCheckLogs is a helper function to call a contract and check the logs using // the integration test utilities. // @@ -188,14 +170,19 @@ func (tf *IntegrationTxFactory) ExecuteEthTx( priv cryptotypes.PrivKey, txArgs evmtypes.EvmTxArgs, ) (abcitypes.ResponseDeliverTx, error) { - signedMsg, err := tf.GenerateSignedEthTx(priv, txArgs) + msgEthereumTx, err := tf.createMsgEthereumTx(priv, txArgs) if err != nil { - return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to generate signed ethereum tx") + return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to create ethereum tx") } - txBytes, err := tf.encodeTx(signedMsg) + signedMsg, err := signMsgEthereumTx(msgEthereumTx, priv, tf.network.GetChainID()) if err != nil { - return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to encode ethereum tx") + return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to sign ethereum tx") + } + + txBytes, err := tf.buildAndEncodeEthTx(signedMsg) + if err != nil { + return abcitypes.ResponseDeliverTx{}, errorsmod.Wrap(err, "failed to build and encode ethereum tx") } res, err := tf.network.BroadcastTxSync(txBytes) @@ -296,21 +283,21 @@ func (tf *IntegrationTxFactory) populateEvmTxArgs( return txArgs, nil } -func (tf *IntegrationTxFactory) encodeTx(tx sdktypes.Tx) ([]byte, error) { +func (tf *IntegrationTxFactory) buildAndEncodeEthTx(msg evmtypes.MsgEthereumTx) ([]byte, error) { txConfig := tf.ec.TxConfig - txBytes, err := txConfig.TxEncoder()(tx) + txBuilder := txConfig.NewTxBuilder() + signingTx, err := msg.BuildTx(txBuilder, tf.network.GetDenom()) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to build tx") + } + + txBytes, err := txConfig.TxEncoder()(signingTx) if err != nil { return nil, errorsmod.Wrap(err, "failed to encode tx") } return txBytes, nil } -func (tf *IntegrationTxFactory) buildSignedTx(msg evmtypes.MsgEthereumTx) (signing.Tx, error) { - txConfig := tf.ec.TxConfig - txBuilder := txConfig.NewTxBuilder() - return msg.BuildTx(txBuilder, tf.network.GetDenom()) -} - // checkEthTxResponse checks if the response is valid and returns the MsgEthereumTxResponse func (tf *IntegrationTxFactory) checkEthTxResponse(res *abcitypes.ResponseDeliverTx) error { var txData sdktypes.TxMsgData diff --git a/testutil/integration/evmos/network/abci.go b/testutil/integration/evmos/network/abci.go index a2ffb842e9..554f4e414f 100644 --- a/testutil/integration/evmos/network/abci.go +++ b/testutil/integration/evmos/network/abci.go @@ -6,7 +6,6 @@ import ( "time" abci "github.com/cometbft/cometbft/abci/types" - sdktypes "github.com/cosmos/cosmos-sdk/store/types" ) // NextBlock is a private helper function that runs the EndBlocker logic, commits the changes, @@ -40,9 +39,6 @@ func (n *IntegrationNetwork) NextBlockAfter(duration time.Duration) error { newCtx = newCtx.WithEventManager(n.ctx.EventManager()) newCtx = newCtx.WithKVGasConfig(n.ctx.KVGasConfig()) newCtx = newCtx.WithTransientKVGasConfig(n.ctx.TransientKVGasConfig()) - newCtx = newCtx.WithConsensusParams(n.ctx.ConsensusParams()) - // This might have to be changed with time if we want to test gas limits - newCtx = newCtx.WithBlockGasMeter(sdktypes.NewInfiniteGasMeter()) n.ctx = newCtx return nil diff --git a/testutil/integration/evmos/network/network.go b/testutil/integration/evmos/network/network.go index a69448d974..e916d1c662 100644 --- a/testutil/integration/evmos/network/network.go +++ b/testutil/integration/evmos/network/network.go @@ -154,7 +154,6 @@ func (n *IntegrationNetwork) configureAndInitChain() error { return err } - consnsusParams := app.DefaultConsensusParams evmosApp.InitChain( abcitypes.RequestInitChain{ ChainId: n.cfg.chainID, @@ -180,9 +179,6 @@ func (n *IntegrationNetwork) configureAndInitChain() error { n.app = evmosApp // TODO - this might not be the best way to initilize the context n.ctx = evmosApp.BaseApp.NewContext(false, header) - n.ctx = n.ctx.WithConsensusParams(consnsusParams) - n.ctx = n.ctx.WithBlockGasMeter(sdktypes.NewInfiniteGasMeter()) - n.validators = validators n.valSet = valSet n.valSigners = valSigners diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index 9d2560a3a8..5703db4865 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -10,7 +10,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -79,28 +78,6 @@ func UnwrapEthereumMsg(tx *sdk.Tx, ethHash common.Hash) (*MsgEthereumTx, error) return nil, fmt.Errorf("eth tx not found: %s", ethHash) } -// UnpackEthMsg unpacks an Ethereum message from a Cosmos SDK message -func UnpackEthMsg(msg sdk.Msg) ( - ethMsg *MsgEthereumTx, - txData TxData, - from sdk.AccAddress, - err error, -) { - msgEthTx, ok := msg.(*MsgEthereumTx) - if !ok { - return nil, nil, nil, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*MsgEthereumTx)(nil)) - } - - txData, err = UnpackTxData(msgEthTx.Data) - if err != nil { - return nil, nil, nil, errorsmod.Wrap(err, "failed to unpack tx data any for tx") - } - - // sender address should be in the tx cache from the previous AnteHandle call - from = msgEthTx.GetFrom() - return msgEthTx, txData, from, nil -} - // BinSearch executes the binary search and hone in on an executable gas limit func BinSearch(lo, hi uint64, executable func(uint64) (bool, *MsgEthereumTxResponse, error)) (uint64, error) { for lo+1 < hi {