diff --git a/app/ante/ante.go b/app/ante/ante.go index 20770690..88bb9d0e 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -6,9 +6,10 @@ import ( tmlog "github.com/tendermint/tendermint/libs/log" + errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/crypto/types/multisig" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -51,8 +52,8 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { // cosmos-sdk tx with dynamic fee extension anteHandler = newCosmosAnteHandler(options) default: - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrUnknownExtensionOptions, + return ctx, errorsmod.Wrapf( + errortypes.ErrUnknownExtensionOptions, "rejecting tx with unsupported extension option: %s", typeURL, ) } @@ -66,7 +67,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { case sdk.Tx: anteHandler = newCosmosAnteHandler(options) default: - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx) + return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx) } return anteHandler(ctx, tx, sim) @@ -75,7 +76,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { func Recover(logger tmlog.Logger, err *error) { if r := recover(); r != nil { - *err = sdkerrors.Wrapf(sdkerrors.ErrPanic, "%v", r) + *err = errorsmod.Wrapf(errortypes.ErrPanic, "%v", r) if e, ok := r.(error); ok { logger.Error( diff --git a/app/ante/eip712.go b/app/ante/eip712.go index f12b96fe..96f6e373 100644 --- a/app/ante/eip712.go +++ b/app/ante/eip712.go @@ -3,11 +3,12 @@ package ante import ( "fmt" + errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" @@ -63,12 +64,12 @@ func (svd Eip712SigVerificationDecorator) AnteHandle(ctx sdk.Context, sigTx, ok := tx.(authsigning.SigVerifiableTx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement authsigning.SigVerifiableTx", tx) + return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "tx %T doesn't implement authsigning.SigVerifiableTx", tx) } authSignTx, ok := tx.(authsigning.Tx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement the authsigning.Tx interface", tx) + return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "tx %T doesn't implement the authsigning.Tx interface", tx) } // stdSigs contains the sequence number, account number, and signatures. @@ -82,12 +83,16 @@ func (svd Eip712SigVerificationDecorator) AnteHandle(ctx sdk.Context, // EIP712 allows just one signature if len(sigs) != 1 { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signers (%d); EIP712 signatures allows just one signature", len(sigs)) + return ctx, errorsmod.Wrapf( + errortypes.ErrTooManySignatures, + "invalid number of signers (%d); EIP712 signatures allows just one signature", + len(sigs), + ) } // check that signer length and signature length are the same if len(sigs) != len(signerAddrs) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs)) + return ctx, errorsmod.Wrapf(errortypes.ErrorInvalidSigner, "invalid number of signers; expected: %d, got %d", len(signerAddrs), len(sigs)) } // EIP712 has just one signature, avoid looping here and only read index 0 @@ -102,13 +107,13 @@ func (svd Eip712SigVerificationDecorator) AnteHandle(ctx sdk.Context, // retrieve pubkey pubKey := acc.GetPubKey() if !simulate && pubKey == nil { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set") + return ctx, errorsmod.Wrap(errortypes.ErrInvalidPubKey, "pubkey on account is not set") } // Check account sequence number. if sig.Sequence != acc.GetSequence() { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrWrongSequence, + return ctx, errorsmod.Wrapf( + errortypes.ErrWrongSequence, "account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence, ) } @@ -134,7 +139,7 @@ func (svd Eip712SigVerificationDecorator) AnteHandle(ctx sdk.Context, if err := VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, authSignTx); err != nil { errMsg := fmt.Errorf("signature verification failed; please verify account number (%d) and chain-id (%s): %w", accNum, chainID, err) - return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg.Error()) + return ctx, errorsmod.Wrap(errortypes.ErrUnauthorized, errMsg.Error()) } return next(ctx, tx, simulate) @@ -152,12 +157,12 @@ func VerifySignature( switch data := sigData.(type) { case *signing.SingleSignatureData: if data.SignMode != signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { - return sdkerrors.Wrapf(sdkerrors.ErrNotSupported, "unexpected SignatureData %T: wrong SignMode", sigData) + return errorsmod.Wrapf(errortypes.ErrNotSupported, "unexpected SignatureData %T: wrong SignMode", sigData) } - // Note: this prevents the user from sending thrash data in the signature field + // Note: this prevents the user from sending trash data in the signature field if len(data.Signature) != 0 { - return sdkerrors.Wrap(sdkerrors.ErrTooManySignatures, "invalid signature value; EIP712 must have the cosmos transaction signature empty") + return errorsmod.Wrap(errortypes.ErrTooManySignatures, "invalid signature value; EIP712 must have the cosmos transaction signature empty") } // @contract: this code is reached only when Msg has Web3Tx extension (so this custom Ante handler flow), @@ -165,7 +170,7 @@ func VerifySignature( msgs := tx.GetMsgs() if len(msgs) == 0 { - return sdkerrors.Wrap(sdkerrors.ErrNoSignatures, "tx doesn't contain any msgs to verify signature") + return errorsmod.Wrap(errortypes.ErrNoSignatures, "tx doesn't contain any msgs to verify signature") } txBytes := legacytx.StdSignBytes( @@ -182,33 +187,33 @@ func VerifySignature( signerChainID, err := ethermint.ParseChainID(signerData.ChainID) if err != nil { - return sdkerrors.Wrapf(err, "failed to parse chainID: %s", signerData.ChainID) + return errorsmod.Wrapf(err, "failed to parse chain-id: %s", signerData.ChainID) } txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx) if !ok { - return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "tx doesnt contain any extensions") + return errorsmod.Wrap(errortypes.ErrUnknownExtensionOptions, "tx doesnt contain any extensions") } opts := txWithExtensions.GetExtensionOptions() if len(opts) != 1 { - return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "tx doesnt contain expected amount of extension options") + return errorsmod.Wrap(errortypes.ErrUnknownExtensionOptions, "tx doesnt contain expected amount of extension options") } extOpt, ok := opts[0].GetCachedValue().(*ethermint.ExtensionOptionsWeb3Tx) if !ok { - return sdkerrors.Wrap(sdkerrors.ErrInvalidChainID, "unknown extension option") + return errorsmod.Wrap(errortypes.ErrUnknownExtensionOptions, "unknown extension option") } if extOpt.TypedDataChainID != signerChainID.Uint64() { - return sdkerrors.Wrap(sdkerrors.ErrInvalidChainID, "invalid chainID") + return errorsmod.Wrap(errortypes.ErrInvalidChainID, "invalid chain-id") } if len(extOpt.FeePayer) == 0 { - return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "no feePayer on ExtensionOptionsWeb3Tx") + return errorsmod.Wrap(errortypes.ErrUnknownExtensionOptions, "no feePayer on ExtensionOptionsWeb3Tx") } feePayer, err := sdk.AccAddressFromBech32(extOpt.FeePayer) if err != nil { - return sdkerrors.Wrap(err, "failed to parse feePayer from ExtensionOptionsWeb3Tx") + return errorsmod.Wrap(err, "failed to parse feePayer from ExtensionOptionsWeb3Tx") } feeDelegation := &eip712.FeeDelegationOptions{ @@ -217,7 +222,7 @@ func VerifySignature( typedData, err := eip712.WrapTxToTypedData(ethermintCodec, extOpt.TypedDataChainID, msgs[0], txBytes, feeDelegation) if err != nil { - return sdkerrors.Wrap(err, "failed to pack tx data in EIP712 object") + return errorsmod.Wrap(err, "failed to create EIP-712 typed data from tx") } sigHash, _, err := apitypes.TypedDataAndHash(typedData) @@ -227,7 +232,7 @@ func VerifySignature( feePayerSig := extOpt.FeePayerSig if len(feePayerSig) != ethcrypto.SignatureLength { - return sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "signature length doesn't match typical [R||S||V] signature 65 bytes") + return errorsmod.Wrap(errortypes.ErrorInvalidSigner, "signature length doesn't match typical [R||S||V] signature 65 bytes") } // Remove the recovery offset if needed (ie. Metamask eip712 signature) @@ -237,12 +242,12 @@ func VerifySignature( feePayerPubkey, err := secp256k1.RecoverPubkey(sigHash, feePayerSig) if err != nil { - return sdkerrors.Wrap(err, "failed to recover delegated fee payer from sig") + return errorsmod.Wrap(err, "failed to recover delegated fee payer from sig") } ecPubKey, err := ethcrypto.UnmarshalPubkey(feePayerPubkey) if err != nil { - return sdkerrors.Wrap(err, "failed to unmarshal recovered fee payer pubkey") + return errorsmod.Wrap(err, "failed to unmarshal recovered fee payer pubkey") } pk := ðsecp256k1.PubKey{ @@ -250,23 +255,23 @@ func VerifySignature( } if !pubKey.Equals(pk) { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "feePayer pubkey %s is different from transaction pubkey %s", pubKey, pk) + return errorsmod.Wrapf(errortypes.ErrInvalidPubKey, "feePayer pubkey %s is different from transaction pubkey %s", pubKey, pk) } recoveredFeePayerAcc := sdk.AccAddress(pk.Address().Bytes()) if !recoveredFeePayerAcc.Equals(feePayer) { - return sdkerrors.Wrapf(sdkerrors.ErrorInvalidSigner, "failed to verify delegated fee payer %s signature", recoveredFeePayerAcc) + return errorsmod.Wrapf(errortypes.ErrorInvalidSigner, "failed to verify delegated fee payer %s signature", recoveredFeePayerAcc) } // VerifySignature of ethsecp256k1 accepts 64 byte signature [R||S] // WARNING! Under NO CIRCUMSTANCES try to use pubKey.VerifySignature there if !secp256k1.VerifySignature(pubKey.Bytes(), sigHash, feePayerSig[:len(feePayerSig)-1]) { - return sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "unable to verify signer signature of EIP712 typed data") + return errorsmod.Wrap(errortypes.ErrorInvalidSigner, "unable to verify signer signature of EIP712 typed data") } return nil default: - return sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures, "unexpected SignatureData %T", sigData) + return errorsmod.Wrapf(errortypes.ErrTooManySignatures, "unexpected SignatureData %T", sigData) } } diff --git a/app/ante/eth.go b/app/ante/eth.go index 9f495596..684dd8f9 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -1,17 +1,14 @@ package ante import ( - "errors" "math" "math/big" - "strconv" + errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" ethermint "github.com/evmos/ethermint/types" evmkeeper "github.com/evmos/ethermint/x/evm/keeper" @@ -22,60 +19,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" ) -// EthSigVerificationDecorator validates an ethereum signatures -type EthSigVerificationDecorator struct { - evmKeeper EVMKeeper -} - -// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator -func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator { - return EthSigVerificationDecorator{ - evmKeeper: ek, - } -} - -// AnteHandle validates 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. -// It's not skipped for RecheckTx, because it set `From` address which is critical from other ante handler to work. -// Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user -// won't see the error message. -func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { - chainID := esvd.evmKeeper.ChainID() - chainCfg := esvd.evmKeeper.GetChainConfig(ctx) - ethCfg := chainCfg.EthereumConfig(chainID) - blockNum := big.NewInt(ctx.BlockHeight()) - signer := ethtypes.MakeSigner(ethCfg, blockNum) - - for _, msg := range tx.GetMsgs() { - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) - } - - allowUnprotectedTxs := esvd.evmKeeper.GetAllowUnprotectedTxs(ctx) - ethTx := msgEthTx.AsTransaction() - if !allowUnprotectedTxs && !ethTx.Protected() { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrNotSupported, - "rejected unprotected Ethereum txs. Please EIP155 sign your transaction to protect it against replay-attacks") - } - - sender, err := signer.Sender(ethTx) - if err != nil { - return ctx, sdkerrors.Wrapf( - sdkerrors.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() - } - - return next(ctx, tx, simulate) -} - // EthAccountVerificationDecorator validates an account balance checks type EthAccountVerificationDecorator struct { ak evmtypes.AccountKeeper @@ -109,18 +52,18 @@ func (avd EthAccountVerificationDecorator) AnteHandle( for i, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + 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, sdkerrors.Wrapf(err, "failed to unpack tx data any for tx %d", i) + 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, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "from address cannot be empty") + return ctx, errorsmod.Wrap(errortypes.ErrInvalidAddress, "from address cannot be empty") } // check whether the sender address is EOA @@ -132,12 +75,12 @@ func (avd EthAccountVerificationDecorator) AnteHandle( avd.ak.SetAccount(ctx, acc) acct = statedb.NewEmptyAccount() } else if acct.IsContract() { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, + return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash) } if err := evmkeeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(acct.Balance), txData); err != nil { - return ctx, sdkerrors.Wrap(err, "failed to check sender balance") + return ctx, errorsmod.Wrap(err, "failed to check sender balance") } } return next(ctx, tx, simulate) @@ -195,16 +138,17 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // 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, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + 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, sdkerrors.Wrap(err, "failed to unpack tx data") + return ctx, errorsmod.Wrap(err, "failed to unpack tx data") } if ctx.IsCheckTx() && egcd.maxGasWanted != 0 { @@ -219,17 +163,19 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula } evmDenom := egcd.evmKeeper.GetEVMDenom(ctx) - fees, priority, err := egcd.evmKeeper.DeductTxCostsFromUserBalance( + + fees, err := egcd.evmKeeper.DeductTxCostsFromUserBalance( ctx, *msgEthTx, txData, evmDenom, + baseFee, homestead, istanbul, london, ) if err != nil { - return ctx, sdkerrors.Wrapf(err, "failed to deduct transaction costs from user balance") + return ctx, errorsmod.Wrapf(err, "failed to deduct transaction costs from user balance") } events = append(events, @@ -239,6 +185,8 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula ), ) + priority := evmtypes.GetTxPriority(txData, baseFee) + if priority < minPriority { minPriority = priority } @@ -254,8 +202,8 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula // from the tx gas pool. The later only has the value so far since the // EthSetupContextDecorator so it will never exceed the block gas limit. if gasWanted > blockGasLimit { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrOutOfGas, + return ctx, errorsmod.Wrapf( + errortypes.ErrOutOfGas, "tx gas (%d) exceeds block gas limit (%d)", gasWanted, blockGasLimit, @@ -299,14 +247,14 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate for _, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + 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, sdkerrors.Wrapf( + return ctx, errorsmod.Wrapf( err, "failed to create an ethereum core.Message from signer %T", signer, ) @@ -314,14 +262,14 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate if evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) { if baseFee == nil { - return ctx, sdkerrors.Wrap( + 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, sdkerrors.Wrapf( - sdkerrors.ErrInsufficientFee, + return ctx, errorsmod.Wrapf( + errortypes.ErrInsufficientFee, "max fee per gas less than block base fee (%s < %s)", coreMsg.GasFeeCap(), baseFee, ) @@ -341,8 +289,8 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate // 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, sdkerrors.Wrapf( - sdkerrors.ErrInsufficientFunds, + return ctx, errorsmod.Wrapf( + errortypes.ErrInsufficientFunds, "failed to transfer %s from address %s using the EVM block context transfer function", coreMsg.Value(), coreMsg.From(), @@ -372,19 +320,19 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s for _, msg := range tx.GetMsgs() { msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + 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, sdkerrors.Wrap(err, "failed to unpack tx data") + 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, sdkerrors.Wrapf( - sdkerrors.ErrUnknownAddress, + return ctx, errorsmod.Wrapf( + errortypes.ErrUnknownAddress, "account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()), ) } @@ -393,14 +341,14 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s // 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, sdkerrors.Wrapf( - sdkerrors.ErrInvalidSequence, + 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, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) + return ctx, errorsmod.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1) } issd.ak.SetAccount(ctx, acc) @@ -408,234 +356,3 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s 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, sdkerrors.ErrNoSignatures) { - return ctx, sdkerrors.Wrap(err, "tx basic validation failed") - } - - // For eth type cosmos tx, some fields should be veified 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, sdkerrors.Wrapf(sdkerrors.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, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, - "for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty") - } - - if len(body.ExtensionOptions) != 1 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1") - } - - txFee := sdk.Coins{} - txGasLimit := uint64(0) - - chainCfg := vbd.evmKeeper.GetChainConfig(ctx) - chainID := vbd.evmKeeper.ChainID() - ethCfg := chainCfg.EthereumConfig(chainID) - baseFee := vbd.evmKeeper.GetBaseFee(ctx, ethCfg) - enableCreate := vbd.evmKeeper.GetEnableCreate(ctx) - enableCall := vbd.evmKeeper.GetEnableCall(ctx) - evmDenom := vbd.evmKeeper.GetEVMDenom(ctx) - - for _, msg := range protoTx.GetMsgs() { - msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) - } - - // Validate `From` field - if msgEthTx.From != "" { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid From %s, expect empty string", msgEthTx.From) - } - - txGasLimit += msgEthTx.GetGas() - - txData, err := evmtypes.UnpackTxData(msgEthTx.Data) - if err != nil { - return ctx, sdkerrors.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, sdkerrors.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract") - } else if !enableCall && txData.GetTo() != nil { - return ctx, sdkerrors.Wrap(evmtypes.ErrCallDisabled, "failed to call contract") - } - - if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType { - return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported") - } - - txFee = txFee.Add(sdk.NewCoin(evmDenom, sdkmath.NewIntFromBigInt(txData.Fee()))) - } - - authInfo := protoTx.AuthInfo - if len(authInfo.SignerInfos) > 0 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty") - } - - if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty") - } - - if !authInfo.Fee.Amount.IsEqual(txFee) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee) - } - - if authInfo.Fee.GasLimit != txGasLimit { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit) - } - - sigs := protoTx.Signatures - if len(sigs) > 0 { - return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx Signatures should be empty") - } - - return next(ctx, tx, simulate) -} - -// 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 newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx") - } - - // 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) -} - -// 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 -} - -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) - } - chainCfg := mfd.evmKeeper.GetChainConfig(ctx) - ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID()) - - baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg) - // skip check as the London hard fork and EIP-1559 are enabled - if baseFee != nil { - return next(ctx, tx, simulate) - } - - evmDenom := mfd.evmKeeper.GetEVMDenom(ctx) - minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom) - - for _, msg := range tx.GetMsgs() { - ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) - if !ok { - return ctx, sdkerrors.Wrapf(sdkerrors.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, sdkerrors.Wrapf( - sdkerrors.ErrInsufficientFee, - "insufficient fees; got: %s required: %s", - fee, requiredFee, - ) - } - } - - return next(ctx, 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, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) - } - - // emit ethereum tx hash as event, should be indexed by tm tx indexer for query purpose. - // 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)), - )) - } - - return next(ctx, tx, simulate) -} diff --git a/app/ante/eth_test.go b/app/ante/eth_test.go index ff2224e8..1f591845 100644 --- a/app/ante/eth_test.go +++ b/app/ante/eth_test.go @@ -6,7 +6,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/evmos/ethermint/app/ante" "github.com/evmos/ethermint/server/config" "github.com/evmos/ethermint/tests" @@ -16,59 +15,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" ) -func (suite AnteTestSuite) TestEthSigVerificationDecorator() { - addr, privKey := tests.NewAddrKey() - - signedTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) - signedTx.From = addr.Hex() - err := signedTx.Sign(suite.ethSigner, tests.NewSigner(privKey)) - suite.Require().NoError(err) - - unprotectedTx := evmtypes.NewTxContract(nil, 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) - unprotectedTx.From = addr.Hex() - err = unprotectedTx.Sign(ethtypes.HomesteadSigner{}, tests.NewSigner(privKey)) - suite.Require().NoError(err) - - testCases := []struct { - name string - tx sdk.Tx - allowUnprotectedTxs bool - reCheckTx bool - expPass bool - }{ - {"ReCheckTx", &invalidTx{}, false, true, false}, - {"invalid transaction type", &invalidTx{}, false, false, false}, - { - "invalid sender", - evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &addr, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), - true, - false, - false, - }, - {"successful signature verification", signedTx, false, false, true}, - {"invalid, reject unprotected txs", unprotectedTx, false, false, false}, - {"successful, allow unprotected txs", unprotectedTx, true, false, true}, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - suite.evmParamsOption = func(params *evmtypes.Params) { - params.AllowUnprotectedTxs = tc.allowUnprotectedTxs - } - suite.SetupTest() - dec := ante.NewEthSigVerificationDecorator(suite.app.EvmKeeper) - _, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, NextFn) - - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) - } - suite.evmParamsOption = nil -} - func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() { dec := ante.NewEthAccountVerificationDecorator( suite.app.AccountKeeper, suite.app.EvmKeeper, @@ -522,35 +468,3 @@ func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() { }) } } - -func (suite AnteTestSuite) TestEthSetupContextDecorator() { - dec := ante.NewEthSetUpContextDecorator(suite.app.EvmKeeper) - tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) - - testCases := []struct { - name string - tx sdk.Tx - expPass bool - }{ - {"invalid transaction type - does not implement GasTx", &invalidTx{}, false}, - { - "success - transaction implement GasTx", - tx, - true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - ctx, err := dec.AnteHandle(suite.ctx, tc.tx, false, NextFn) - - if tc.expPass { - suite.Require().NoError(err) - suite.Equal(storetypes.GasConfig{}, ctx.KVGasConfig()) - suite.Equal(storetypes.GasConfig{}, ctx.TransientKVGasConfig()) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/app/ante/fee_checker.go b/app/ante/fee_checker.go index 7d9a9ea9..885aeac1 100644 --- a/app/ante/fee_checker.go +++ b/app/ante/fee_checker.go @@ -4,10 +4,11 @@ import ( "fmt" "math" + errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" authante "github.com/cosmos/cosmos-sdk/x/auth/ante" ethermint "github.com/evmos/ethermint/types" "github.com/evmos/ethermint/x/evm/types" @@ -64,7 +65,7 @@ func NewDynamicFeeChecker(k DynamicFeeEVMKeeper) authante.TxFeeChecker { baseFeeInt := sdkmath.NewIntFromBigInt(baseFee) if feeCap.LT(baseFeeInt) { - return nil, 0, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient gas prices; got: %s required: %s", feeCap, baseFeeInt) + return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "insufficient gas prices; got: %s required: %s", feeCap, baseFeeInt) } // calculate the effective gas price using the EIP-1559 logic. @@ -112,7 +113,7 @@ func checkTxFeeWithValidatorMinGasPrices(ctx sdk.Context, tx sdk.FeeTx) (sdk.Coi } if !feeCoins.IsAnyGTE(requiredFees) { - return nil, 0, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) + return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) } } diff --git a/app/ante/fee_market.go b/app/ante/fee_market.go index 24cc504b..c81a0ce1 100644 --- a/app/ante/fee_market.go +++ b/app/ante/fee_market.go @@ -3,8 +3,8 @@ package ante import ( "math/big" + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // GasWantedDecorator keeps track of the gasWanted amount on the current block in transient store @@ -44,7 +44,7 @@ func (gwd GasWantedDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo // Add total gasWanted to cumulative in block transientStore in FeeMarket module if isBaseFeeEnabled { if _, err := gwd.feeMarketKeeper.AddTransientGasWanted(ctx, gasWanted); err != nil { - return ctx, sdkerrors.Wrapf(err, "failed to add gas wanted to transient store") + return ctx, errorsmod.Wrapf(err, "failed to add gas wanted to transient store") } } diff --git a/app/ante/fees.go b/app/ante/fees.go index 784ead5c..db15f301 100644 --- a/app/ante/fees.go +++ b/app/ante/fees.go @@ -3,8 +3,9 @@ package ante import ( "math/big" + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" ethtypes "github.com/ethereum/go-ethereum/core/types" evmtypes "github.com/evmos/ethermint/x/evm/types" @@ -20,14 +21,50 @@ type MinGasPriceDecorator struct { evmKeeper EVMKeeper } +// EthMinGasPriceDecorator will check if the transaction's fee is at least as large +// as the MinGasPrices param. If fee is too low, decorator returns error and tx +// is rejected. This applies to both CheckTx and DeliverTx and regardless +// if London hard fork or fee market params (EIP-1559) are enabled. +// If fee is high enough, then call next AnteHandler +type EthMinGasPriceDecorator struct { + feesKeeper FeeMarketKeeper + evmKeeper EVMKeeper +} + +// EthMempoolFeeDecorator will check if the transaction's effective fee is at least as large +// as the local validator's minimum gasFee (defined in validator config). +// If fee is too low, decorator returns error and tx is rejected from mempool. +// Note this only applies when ctx.CheckTx = true +// If fee is high enough or not CheckTx, then call next AnteHandler +// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator +type EthMempoolFeeDecorator struct { + evmKeeper EVMKeeper +} + +// NewMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for +// Cosmos transactions. func NewMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) MinGasPriceDecorator { return MinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} } +// NewEthMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for +// Ethereum transactions. +func NewEthMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) EthMinGasPriceDecorator { + return EthMinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} +} + +// NewEthMempoolFeeDecorator creates a new NewEthMempoolFeeDecorator instance used only for +// Ethereum transactions. +func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator { + return EthMempoolFeeDecorator{ + evmKeeper: ek, + } +} + func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { feeTx, ok := tx.(sdk.FeeTx) if !ok { - return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected sdk.FeeTx", tx) } minGasPrice := mpd.feesKeeper.GetParams(ctx).MinGasPrice @@ -62,7 +99,7 @@ func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate } if !feeCoins.IsAnyGTE(requiredFees) { - return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, + return ctx, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "provided fee < minimum global fee (%s < %s). Please increase the gas price.", feeCoins, requiredFees) @@ -71,20 +108,8 @@ func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate return next(ctx, tx, simulate) } -// 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 -} - -func NewEthMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) EthMinGasPriceDecorator { - return EthMinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} -} - +// AnteHandle ensures that the that the effective fee from the transaction is greater than the +// minimum global fee, which is defined by the MinGasPrice (parameter) * GasLimit (tx argument). func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { minGasPrice := empd.feesKeeper.GetParams(ctx).MinGasPrice @@ -100,8 +125,8 @@ func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrUnknownRequest, + return ctx, errorsmod.Wrapf( + errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil), ) @@ -120,7 +145,7 @@ func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul txData, err := evmtypes.UnpackTxData(ethMsg.Data) if err != nil { - return ctx, sdkerrors.Wrapf(err, "failed to unpack tx data %s", ethMsg.Hash) + return ctx, errorsmod.Wrapf(err, "failed to unpack tx data %s", ethMsg.Hash) } if txData.TxType() != ethtypes.LegacyTxType { @@ -133,8 +158,8 @@ func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul fee := sdk.NewDecFromBigInt(feeAmt) if fee.LT(requiredFee) { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrInsufficientFee, + return ctx, errorsmod.Wrapf( + errortypes.ErrInsufficientFee, "provided fee < minimum global fee (%d < %d). Please increase the priority tip (for EIP-1559 txs) or the gas prices (for access list or legacy txs)", //nolint:lll fee.TruncateInt().Int64(), requiredFee.TruncateInt().Int64(), ) @@ -143,3 +168,44 @@ func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul return next(ctx, tx, simulate) } + +// AnteHandle ensures that the provided fees meet a minimum threshold for the validator. +// This check only for local mempool purposes, and thus it is only run on (Re)CheckTx. +// The logic is also skipped if the London hard fork and EIP-1559 are enabled. +func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if !ctx.IsCheckTx() || simulate { + return next(ctx, tx, simulate) + } + chainCfg := mfd.evmKeeper.GetChainConfig(ctx) + ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID()) + + baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg) + // skip check as the London hard fork and EIP-1559 are enabled + if baseFee != nil { + return next(ctx, tx, simulate) + } + + evmDenom := mfd.evmKeeper.GetEVMDenom(ctx) + minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom) + + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if !ok { + return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil)) + } + + fee := sdk.NewDecFromBigInt(ethMsg.GetFee()) + gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(ethMsg.GetGas())) + requiredFee := minGasPrice.Mul(gasLimit) + + if fee.LT(requiredFee) { + return ctx, errorsmod.Wrapf( + errortypes.ErrInsufficientFee, + "insufficient fee; got: %s required: %s", + fee, requiredFee, + ) + } + } + + return next(ctx, tx, simulate) +} diff --git a/app/ante/fees_test.go b/app/ante/fees_test.go index 15a8cb31..e8fb5d5d 100644 --- a/app/ante/fees_test.go +++ b/app/ante/fees_test.go @@ -42,7 +42,7 @@ func (s AnteTestSuite) TestMinGasPriceDecorator() { return &invalidTx{} }, false, - "must be a FeeTx", + "invalid transaction type", false, }, { @@ -345,3 +345,7 @@ func (s AnteTestSuite) TestEthMinGasPriceDecorator() { } } } + +func (suite AnteTestSuite) TestEthMempoolFeeDecorator() { + // TODO: add test +} diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index 78f95404..96387aa6 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -1,8 +1,9 @@ package ante import ( + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/ante" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" @@ -32,19 +33,19 @@ type HandlerOptions struct { func (options HandlerOptions) validate() error { if options.AccountKeeper == nil { - return sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + return errorsmod.Wrap(errortypes.ErrLogic, "account keeper is required for AnteHandler") } if options.BankKeeper == nil { - return sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + return errorsmod.Wrap(errortypes.ErrLogic, "bank keeper is required for AnteHandler") } if options.SignModeHandler == nil { - return sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + return errorsmod.Wrap(errortypes.ErrLogic, "sign mode handler is required for ante builder") } if options.FeeMarketKeeper == nil { - return sdkerrors.Wrap(sdkerrors.ErrLogic, "fee market keeper is required for AnteHandler") + return errorsmod.Wrap(errortypes.ErrLogic, "fee market keeper is required for AnteHandler") } if options.EvmKeeper == nil { - return sdkerrors.Wrap(sdkerrors.ErrLogic, "evm keeper is required for AnteHandler") + return errorsmod.Wrap(errortypes.ErrLogic, "evm keeper is required for AnteHandler") } return nil } diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index dbc980ee..b0d62039 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -29,8 +29,8 @@ type EVMKeeper interface { NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) evm.EVM DeductTxCostsFromUserBalance( - ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool, - ) (fees sdk.Coins, priority int64, err error) + ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, baseFee *big.Int, homestead, istanbul, london bool, + ) (fees sdk.Coins, err error) GetBalance(ctx sdk.Context, addr common.Address) *big.Int ResetTransientGasUsed(ctx sdk.Context) GetTxIndexTransient(ctx sdk.Context) uint64 diff --git a/app/ante/reject_msgs.go b/app/ante/reject_msgs.go index ce752cc0..f2728895 100644 --- a/app/ante/reject_msgs.go +++ b/app/ante/reject_msgs.go @@ -1,8 +1,9 @@ package ante import ( + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" evmtypes "github.com/evmos/ethermint/x/evm/types" ) @@ -15,8 +16,8 @@ type RejectMessagesDecorator struct{} func (rmd RejectMessagesDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { for _, msg := range tx.GetMsgs() { if _, ok := msg.(*evmtypes.MsgEthereumTx); ok { - return ctx, sdkerrors.Wrapf( - sdkerrors.ErrInvalidType, + return ctx, errorsmod.Wrapf( + errortypes.ErrInvalidType, "MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option", ) } diff --git a/app/ante/setup.go b/app/ante/setup.go new file mode 100644 index 00000000..da69f9b5 --- /dev/null +++ b/app/ante/setup.go @@ -0,0 +1,189 @@ +package ante + +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/ethermint/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)), + )) + } + + 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) + + chainCfg := vbd.evmKeeper.GetChainConfig(ctx) + chainID := vbd.evmKeeper.ChainID() + ethCfg := chainCfg.EthereumConfig(chainID) + baseFee := vbd.evmKeeper.GetBaseFee(ctx, ethCfg) + enableCreate := vbd.evmKeeper.GetEnableCreate(ctx) + enableCall := vbd.evmKeeper.GetEnableCall(ctx) + evmDenom := vbd.evmKeeper.GetEVMDenom(ctx) + + 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/setup_test.go b/app/ante/setup_test.go new file mode 100644 index 00000000..350a5d7d --- /dev/null +++ b/app/ante/setup_test.go @@ -0,0 +1,42 @@ +package ante_test + +import ( + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/evmos/ethermint/app/ante" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + +func (suite AnteTestSuite) TestEthSetupContextDecorator() { + dec := ante.NewEthSetUpContextDecorator(suite.app.EvmKeeper) + tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) + + testCases := []struct { + name string + tx sdk.Tx + expPass bool + }{ + {"invalid transaction type - does not implement GasTx", &invalidTx{}, false}, + { + "success - transaction implement GasTx", + tx, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + ctx, err := dec.AnteHandle(suite.ctx, tc.tx, false, NextFn) + + if tc.expPass { + suite.Require().NoError(err) + suite.Equal(storetypes.GasConfig{}, ctx.KVGasConfig()) + suite.Equal(storetypes.GasConfig{}, ctx.TransientKVGasConfig()) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/app/ante/signverify_test.go b/app/ante/signverify_test.go new file mode 100644 index 00000000..526c2867 --- /dev/null +++ b/app/ante/signverify_test.go @@ -0,0 +1,65 @@ +package ante_test + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/evmos/ethermint/app/ante" + "github.com/evmos/ethermint/tests" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + + +func (suite AnteTestSuite) TestEthSigVerificationDecorator() { + addr, privKey := tests.NewAddrKey() + + signedTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) + signedTx.From = addr.Hex() + err := signedTx.Sign(suite.ethSigner, tests.NewSigner(privKey)) + suite.Require().NoError(err) + + unprotectedTx := evmtypes.NewTxContract(nil, 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil) + unprotectedTx.From = addr.Hex() + err = unprotectedTx.Sign(ethtypes.HomesteadSigner{}, tests.NewSigner(privKey)) + suite.Require().NoError(err) + + testCases := []struct { + name string + tx sdk.Tx + allowUnprotectedTxs bool + reCheckTx bool + expPass bool + }{ + {"ReCheckTx", &invalidTx{}, false, true, false}, + {"invalid transaction type", &invalidTx{}, false, false, false}, + { + "invalid sender", + evmtypes.NewTx(suite.app.EvmKeeper.ChainID(), 1, &addr, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil), + true, + false, + false, + }, + {"successful signature verification", signedTx, false, false, true}, + {"invalid, reject unprotected txs", unprotectedTx, false, false, false}, + {"successful, allow unprotected txs", unprotectedTx, true, false, true}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.evmParamsOption = func(params *evmtypes.Params) { + params.AllowUnprotectedTxs = tc.allowUnprotectedTxs + } + suite.SetupTest() + dec := ante.NewEthSigVerificationDecorator(suite.app.EvmKeeper) + _, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, NextFn) + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } + suite.evmParamsOption = nil +} diff --git a/app/ante/sigverify.go b/app/ante/sigverify.go new file mode 100644 index 00000000..11c9d1db --- /dev/null +++ b/app/ante/sigverify.go @@ -0,0 +1,65 @@ +package ante + +import ( + "math/big" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + ethtypes "github.com/ethereum/go-ethereum/core/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + +// EthSigVerificationDecorator validates an ethereum signatures +type EthSigVerificationDecorator struct { + evmKeeper EVMKeeper +} + +// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator +func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator { + return EthSigVerificationDecorator{ + evmKeeper: ek, + } +} + +// AnteHandle validates 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. +// It's not skipped for RecheckTx, because it set `From` address which is critical from other ante handler to work. +// Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user +// won't see the error message. +func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + chainID := esvd.evmKeeper.ChainID() + chainCfg := esvd.evmKeeper.GetChainConfig(ctx) + ethCfg := chainCfg.EthereumConfig(chainID) + blockNum := big.NewInt(ctx.BlockHeight()) + signer := ethtypes.MakeSigner(ethCfg, blockNum) + + 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)) + } + + allowUnprotectedTxs := esvd.evmKeeper.GetAllowUnprotectedTxs(ctx) + 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") + } + + 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(), + ) + } + + // set up the sender to the transaction field if not already + msgEthTx.From = sender.Hex() + } + + return next(ctx, tx, simulate) +} diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 8d998f2e..55ae77a9 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -594,9 +594,12 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() { before := k.GetBalance(suite.ctx, suite.from) + ethCfg := suite.app.EvmKeeper.GetChainConfig(suite.ctx).EthereumConfig(nil) + baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg) + txData, err := types.UnpackTxData(tx.Data) suite.Require().NoError(err) - _, _, err = k.DeductTxCostsFromUserBalance(suite.ctx, *tx, txData, "aphoton", true, true, true) + _, err = k.DeductTxCostsFromUserBalance(suite.ctx, *tx, txData, "aphoton", baseFee, true, true, true) suite.Require().NoError(err) res, err := k.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx) diff --git a/x/evm/keeper/utils.go b/x/evm/keeper/utils.go index f72632c8..beffb5e8 100644 --- a/x/evm/keeper/utils.go +++ b/x/evm/keeper/utils.go @@ -1,7 +1,6 @@ package keeper import ( - "math" "math/big" errorsmod "cosmossdk.io/errors" @@ -17,22 +16,16 @@ import ( ) // DeductTxCostsFromUserBalance it calculates the tx costs and deducts the fees -// returns (effectiveFee, priority, error) func (k Keeper) DeductTxCostsFromUserBalance( ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, + baseFee *big.Int, homestead, istanbul, london bool, -) (fees sdk.Coins, priority int64, err error) { +) (fees sdk.Coins, err error) { isContractCreation := txData.GetTo() == nil - // fetch sender account from signature - signerAcc, err := authante.GetSignerAcc(ctx, k.accountKeeper, msgEthTx.GetFrom()) - if err != nil { - return nil, 0, errorsmod.Wrapf(err, "account not found for sender %s", msgEthTx.From) - } - gasLimit := txData.GetGas() var accessList ethtypes.AccessList @@ -42,7 +35,7 @@ func (k Keeper) DeductTxCostsFromUserBalance( intrinsicGas, err := core.IntrinsicGas(txData.GetData(), accessList, isContractCreation, homestead, istanbul) if err != nil { - return nil, 0, errorsmod.Wrapf( + return nil, errorsmod.Wrapf( err, "failed to retrieve intrinsic gas, contract creation = %t; homestead = %t, istanbul = %t", isContractCreation, homestead, istanbul, @@ -51,53 +44,43 @@ func (k Keeper) DeductTxCostsFromUserBalance( // intrinsic gas verification during CheckTx if ctx.IsCheckTx() && gasLimit < intrinsicGas { - return nil, 0, errorsmod.Wrapf( + return nil, errorsmod.Wrapf( errortypes.ErrOutOfGas, "gas limit too low: %d (gas limit) < %d (intrinsic gas)", gasLimit, intrinsicGas, ) } - var feeAmt *big.Int - - baseFee := k.getBaseFee(ctx, london) if baseFee != nil && txData.GetGasFeeCap().Cmp(baseFee) < 0 { - return nil, 0, errorsmod.Wrapf(errortypes.ErrInsufficientFee, + return nil, errorsmod.Wrapf(errortypes.ErrInsufficientFee, "the tx gasfeecap is lower than the tx baseFee: %s (gasfeecap), %s (basefee) ", txData.GetGasFeeCap(), baseFee) } - feeAmt = txData.EffectiveFee(baseFee) + feeAmt := txData.EffectiveFee(baseFee) if feeAmt.Sign() == 0 { // zero fee, no need to deduct - return sdk.Coins{}, 0, nil + return sdk.Coins{}, nil } - fees = sdk.Coins{sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(feeAmt))} + fees = sdk.Coins{{Denom: denom, Amount: sdkmath.NewIntFromBigInt(feeAmt)}} + + // fetch sender account from signature + signerAcc, err := authante.GetSignerAcc(ctx, k.accountKeeper, msgEthTx.GetFrom()) + if err != nil { + return nil, errorsmod.Wrapf(err, "account not found for sender %s", msgEthTx.From) + } // deduct the full gas cost from the user balance if err := authante.DeductFees(k.bankKeeper, ctx, signerAcc, fees); err != nil { - return nil, 0, errorsmod.Wrapf( + return nil, errorsmod.Wrapf( err, "failed to deduct full gas cost %s from the user %s balance", fees, msgEthTx.From, ) } - // calculate priority based on effective gas price - tipPrice := txData.EffectiveGasPrice(baseFee) - // if london hardfork is not enabled, tipPrice is the gasPrice - if baseFee != nil { - tipPrice = new(big.Int).Sub(tipPrice, baseFee) - } - priorityBig := new(big.Int).Quo(tipPrice, evmtypes.DefaultPriorityReduction.BigInt()) - if !priorityBig.IsInt64() { - priority = math.MaxInt64 - } else { - priority = priorityBig.Int64() - } - - return fees, priority, nil + return fees, nil } // CheckSenderBalance validates that the tx cost value is positive and that the diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go index f24cdeea..00a52a73 100644 --- a/x/evm/keeper/utils_test.go +++ b/x/evm/keeper/utils_test.go @@ -453,11 +453,16 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() { txData, _ := evmtypes.UnpackTxData(tx.Data) - fees, priority, err := suite.app.EvmKeeper.DeductTxCostsFromUserBalance( + ethCfg := suite.app.EvmKeeper.GetChainConfig(suite.ctx).EthereumConfig(nil) + baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg) + priority := evmtypes.GetTxPriority(txData, baseFee) + + fees, err := suite.app.EvmKeeper.DeductTxCostsFromUserBalance( suite.ctx, *tx, txData, evmtypes.DefaultEVMDenom, + baseFee, false, false, suite.enableFeemarket, // london diff --git a/x/evm/types/tx.go b/x/evm/types/tx.go index f5cfccb9..8909eaf2 100644 --- a/x/evm/types/tx.go +++ b/x/evm/types/tx.go @@ -1,10 +1,37 @@ package types import ( + "math" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" ) +// GetTxPriority returns the priority of a given Ethereum tx. It relies of the +// priority reduction global variable to calculate the tx priority given the tx +// tip price: +// +// tx_priority = tip_price / priority_reduction +func GetTxPriority(txData TxData, baseFee *big.Int) (priority int64) { + // calculate priority based on effective gas price + tipPrice := txData.EffectiveGasPrice(baseFee) + // if london hardfork is not enabled, tipPrice is the gasPrice + if baseFee != nil { + tipPrice = new(big.Int).Sub(tipPrice, baseFee) + } + + priority = math.MaxInt64 + priorityBig := new(big.Int).Quo(tipPrice, DefaultPriorityReduction.BigInt()) + + // safety check + if priorityBig.IsInt64() { + priority = priorityBig.Int64() + } + + return priority +} + // Failed returns if the contract execution failed in vm errors func (m *MsgEthereumTxResponse) Failed() bool { return len(m.VmError) > 0