imp(ante): refactor AnteHandler (#1479)

* imp(ante): refactor AnteHandler

* fix test

* test

* Adjust deprecated sdkerrors import (#1493)

* refactor test files

* Apply suggestions from code review

Co-authored-by: 4rgon4ut <59182467+4rgon4ut@users.noreply.github.com>

* lint

* prioritization comment

* fix test

Co-authored-by: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com>
Co-authored-by: 4rgon4ut <59182467+4rgon4ut@users.noreply.github.com>
This commit is contained in:
Federico Kunze Küllmer 2022-11-25 09:24:55 +01:00 committed by GitHub
parent f4c7be2efc
commit 16fb2e1110
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 597 additions and 508 deletions

View File

@ -6,9 +6,10 @@ import (
tmlog "github.com/tendermint/tendermint/libs/log" tmlog "github.com/tendermint/tendermint/libs/log"
errorsmod "cosmossdk.io/errors"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig" "github.com/cosmos/cosmos-sdk/crypto/types/multisig"
sdk "github.com/cosmos/cosmos-sdk/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" "github.com/cosmos/cosmos-sdk/types/tx/signing"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante" authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" 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 // cosmos-sdk tx with dynamic fee extension
anteHandler = newCosmosAnteHandler(options) anteHandler = newCosmosAnteHandler(options)
default: default:
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrUnknownExtensionOptions, errortypes.ErrUnknownExtensionOptions,
"rejecting tx with unsupported extension option: %s", typeURL, "rejecting tx with unsupported extension option: %s", typeURL,
) )
} }
@ -66,7 +67,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
case sdk.Tx: case sdk.Tx:
anteHandler = newCosmosAnteHandler(options) anteHandler = newCosmosAnteHandler(options)
default: 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) return anteHandler(ctx, tx, sim)
@ -75,7 +76,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
func Recover(logger tmlog.Logger, err *error) { func Recover(logger tmlog.Logger, err *error) {
if r := recover(); r != nil { 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 { if e, ok := r.(error); ok {
logger.Error( logger.Error(

View File

@ -3,11 +3,12 @@ package ante
import ( import (
"fmt" "fmt"
errorsmod "cosmossdk.io/errors"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/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" "github.com/cosmos/cosmos-sdk/types/tx/signing"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante" authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" "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) sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok { 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) authSignTx, ok := tx.(authsigning.Tx)
if !ok { 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. // 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 // EIP712 allows just one signature
if len(sigs) != 1 { 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 // check that signer length and signature length are the same
if len(sigs) != len(signerAddrs) { 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 // 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 // retrieve pubkey
pubKey := acc.GetPubKey() pubKey := acc.GetPubKey()
if !simulate && pubKey == nil { 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. // Check account sequence number.
if sig.Sequence != acc.GetSequence() { if sig.Sequence != acc.GetSequence() {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrWrongSequence, errortypes.ErrWrongSequence,
"account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence, "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 { 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) 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) return next(ctx, tx, simulate)
@ -152,12 +157,12 @@ func VerifySignature(
switch data := sigData.(type) { switch data := sigData.(type) {
case *signing.SingleSignatureData: case *signing.SingleSignatureData:
if data.SignMode != signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON { 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 { 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), // @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() msgs := tx.GetMsgs()
if len(msgs) == 0 { 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( txBytes := legacytx.StdSignBytes(
@ -182,33 +187,33 @@ func VerifySignature(
signerChainID, err := ethermint.ParseChainID(signerData.ChainID) signerChainID, err := ethermint.ParseChainID(signerData.ChainID)
if err != nil { 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) txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
if !ok { 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() opts := txWithExtensions.GetExtensionOptions()
if len(opts) != 1 { 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) extOpt, ok := opts[0].GetCachedValue().(*ethermint.ExtensionOptionsWeb3Tx)
if !ok { if !ok {
return sdkerrors.Wrap(sdkerrors.ErrInvalidChainID, "unknown extension option") return errorsmod.Wrap(errortypes.ErrUnknownExtensionOptions, "unknown extension option")
} }
if extOpt.TypedDataChainID != signerChainID.Uint64() { 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 { 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) feePayer, err := sdk.AccAddressFromBech32(extOpt.FeePayer)
if err != nil { 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{ feeDelegation := &eip712.FeeDelegationOptions{
@ -217,7 +222,7 @@ func VerifySignature(
typedData, err := eip712.WrapTxToTypedData(ethermintCodec, extOpt.TypedDataChainID, msgs[0], txBytes, feeDelegation) typedData, err := eip712.WrapTxToTypedData(ethermintCodec, extOpt.TypedDataChainID, msgs[0], txBytes, feeDelegation)
if err != nil { 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) sigHash, _, err := apitypes.TypedDataAndHash(typedData)
@ -227,7 +232,7 @@ func VerifySignature(
feePayerSig := extOpt.FeePayerSig feePayerSig := extOpt.FeePayerSig
if len(feePayerSig) != ethcrypto.SignatureLength { 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) // Remove the recovery offset if needed (ie. Metamask eip712 signature)
@ -237,12 +242,12 @@ func VerifySignature(
feePayerPubkey, err := secp256k1.RecoverPubkey(sigHash, feePayerSig) feePayerPubkey, err := secp256k1.RecoverPubkey(sigHash, feePayerSig)
if err != nil { 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) ecPubKey, err := ethcrypto.UnmarshalPubkey(feePayerPubkey)
if err != nil { 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 := &ethsecp256k1.PubKey{ pk := &ethsecp256k1.PubKey{
@ -250,23 +255,23 @@ func VerifySignature(
} }
if !pubKey.Equals(pk) { 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()) recoveredFeePayerAcc := sdk.AccAddress(pk.Address().Bytes())
if !recoveredFeePayerAcc.Equals(feePayer) { 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] // VerifySignature of ethsecp256k1 accepts 64 byte signature [R||S]
// WARNING! Under NO CIRCUMSTANCES try to use pubKey.VerifySignature there // WARNING! Under NO CIRCUMSTANCES try to use pubKey.VerifySignature there
if !secp256k1.VerifySignature(pubKey.Bytes(), sigHash, feePayerSig[:len(feePayerSig)-1]) { 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 return nil
default: default:
return sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures, "unexpected SignatureData %T", sigData) return errorsmod.Wrapf(errortypes.ErrTooManySignatures, "unexpected SignatureData %T", sigData)
} }
} }

View File

@ -1,17 +1,14 @@
package ante package ante
import ( import (
"errors"
"math" "math"
"math/big" "math/big"
"strconv"
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math" sdkmath "cosmossdk.io/math"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types" 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" ethermint "github.com/evmos/ethermint/types"
evmkeeper "github.com/evmos/ethermint/x/evm/keeper" evmkeeper "github.com/evmos/ethermint/x/evm/keeper"
@ -22,60 +19,6 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types" 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 // EthAccountVerificationDecorator validates an account balance checks
type EthAccountVerificationDecorator struct { type EthAccountVerificationDecorator struct {
ak evmtypes.AccountKeeper ak evmtypes.AccountKeeper
@ -109,18 +52,18 @@ func (avd EthAccountVerificationDecorator) AnteHandle(
for i, msg := range tx.GetMsgs() { for i, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { 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) txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
if err != nil { 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 // sender address should be in the tx cache from the previous AnteHandle call
from := msgEthTx.GetFrom() from := msgEthTx.GetFrom()
if from.Empty() { 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 // check whether the sender address is EOA
@ -132,12 +75,12 @@ func (avd EthAccountVerificationDecorator) AnteHandle(
avd.ak.SetAccount(ctx, acc) avd.ak.SetAccount(ctx, acc)
acct = statedb.NewEmptyAccount() acct = statedb.NewEmptyAccount()
} else if acct.IsContract() { } 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) "the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash)
} }
if err := evmkeeper.CheckSenderBalance(sdkmath.NewIntFromBigInt(acct.Balance), txData); err != nil { 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) 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. // Use the lowest priority of all the messages as the final one.
minPriority := int64(math.MaxInt64) minPriority := int64(math.MaxInt64)
baseFee := egcd.evmKeeper.GetBaseFee(ctx, ethCfg)
for _, msg := range tx.GetMsgs() { for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { 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) txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
if err != nil { 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 { 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) evmDenom := egcd.evmKeeper.GetEVMDenom(ctx)
fees, priority, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(
fees, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(
ctx, ctx,
*msgEthTx, *msgEthTx,
txData, txData,
evmDenom, evmDenom,
baseFee,
homestead, homestead,
istanbul, istanbul,
london, london,
) )
if err != nil { 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, 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 { if priority < minPriority {
minPriority = priority 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 // 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. // EthSetupContextDecorator so it will never exceed the block gas limit.
if gasWanted > blockGasLimit { if gasWanted > blockGasLimit {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrOutOfGas, errortypes.ErrOutOfGas,
"tx gas (%d) exceeds block gas limit (%d)", "tx gas (%d) exceeds block gas limit (%d)",
gasWanted, gasWanted,
blockGasLimit, blockGasLimit,
@ -299,14 +247,14 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
for _, msg := range tx.GetMsgs() { for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { 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) baseFee := ctd.evmKeeper.GetBaseFee(ctx, ethCfg)
coreMsg, err := msgEthTx.AsMessage(signer, baseFee) coreMsg, err := msgEthTx.AsMessage(signer, baseFee)
if err != nil { if err != nil {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
err, err,
"failed to create an ethereum core.Message from signer %T", signer, "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 evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) {
if baseFee == nil { if baseFee == nil {
return ctx, sdkerrors.Wrap( return ctx, errorsmod.Wrap(
evmtypes.ErrInvalidBaseFee, evmtypes.ErrInvalidBaseFee,
"base fee is supported but evm block context value is nil", "base fee is supported but evm block context value is nil",
) )
} }
if coreMsg.GasFeeCap().Cmp(baseFee) < 0 { if coreMsg.GasFeeCap().Cmp(baseFee) < 0 {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrInsufficientFee, errortypes.ErrInsufficientFee,
"max fee per gas less than block base fee (%s < %s)", "max fee per gas less than block base fee (%s < %s)",
coreMsg.GasFeeCap(), baseFee, 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 // 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 // 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()) { if coreMsg.Value().Sign() > 0 && !evm.Context().CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrInsufficientFunds, errortypes.ErrInsufficientFunds,
"failed to transfer %s from address %s using the EVM block context transfer function", "failed to transfer %s from address %s using the EVM block context transfer function",
coreMsg.Value(), coreMsg.Value(),
coreMsg.From(), coreMsg.From(),
@ -372,19 +320,19 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s
for _, msg := range tx.GetMsgs() { for _, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx) msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { 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) txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
if err != nil { 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 // increase sequence of sender
acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom()) acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom())
if acc == nil { if acc == nil {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrUnknownAddress, errortypes.ErrUnknownAddress,
"account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()), "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 // we merged the nonce verification to nonce increment, so when tx includes multiple messages
// with same sender, they'll be accepted. // with same sender, they'll be accepted.
if txData.GetNonce() != nonce { if txData.GetNonce() != nonce {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrInvalidSequence, errortypes.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", txData.GetNonce(), nonce, "invalid nonce; got %d, expected %d", txData.GetNonce(), nonce,
) )
} }
if err := acc.SetSequence(nonce + 1); err != nil { 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) issd.ak.SetAccount(ctx, acc)
@ -408,234 +356,3 @@ func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx s
return next(ctx, tx, simulate) 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)
}

View File

@ -6,7 +6,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" 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/app/ante"
"github.com/evmos/ethermint/server/config" "github.com/evmos/ethermint/server/config"
"github.com/evmos/ethermint/tests" "github.com/evmos/ethermint/tests"
@ -16,59 +15,6 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types" 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() { func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
dec := ante.NewEthAccountVerificationDecorator( dec := ante.NewEthAccountVerificationDecorator(
suite.app.AccountKeeper, suite.app.EvmKeeper, 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)
}
})
}
}

View File

@ -4,10 +4,11 @@ import (
"fmt" "fmt"
"math" "math"
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math" sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types" 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" authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
ethermint "github.com/evmos/ethermint/types" ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/types" "github.com/evmos/ethermint/x/evm/types"
@ -64,7 +65,7 @@ func NewDynamicFeeChecker(k DynamicFeeEVMKeeper) authante.TxFeeChecker {
baseFeeInt := sdkmath.NewIntFromBigInt(baseFee) baseFeeInt := sdkmath.NewIntFromBigInt(baseFee)
if feeCap.LT(baseFeeInt) { 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. // 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) { 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)
} }
} }

View File

@ -3,8 +3,8 @@ package ante
import ( import (
"math/big" "math/big"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types" 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 // 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 // Add total gasWanted to cumulative in block transientStore in FeeMarket module
if isBaseFeeEnabled { if isBaseFeeEnabled {
if _, err := gwd.feeMarketKeeper.AddTransientGasWanted(ctx, gasWanted); err != nil { 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")
} }
} }

View File

@ -3,8 +3,9 @@ package ante
import ( import (
"math/big" "math/big"
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types" 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" ethtypes "github.com/ethereum/go-ethereum/core/types"
evmtypes "github.com/evmos/ethermint/x/evm/types" evmtypes "github.com/evmos/ethermint/x/evm/types"
@ -20,14 +21,50 @@ type MinGasPriceDecorator struct {
evmKeeper EVMKeeper 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 { func NewMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) MinGasPriceDecorator {
return MinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} 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) { 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) feeTx, ok := tx.(sdk.FeeTx)
if !ok { 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 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) { 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.", "provided fee < minimum global fee (%s < %s). Please increase the gas price.",
feeCoins, feeCoins,
requiredFees) requiredFees)
@ -71,20 +108,8 @@ func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
return next(ctx, tx, simulate) return next(ctx, tx, simulate)
} }
// EthMinGasPriceDecorator will check if the transaction's fee is at least as large // AnteHandle ensures that the that the effective fee from the transaction is greater than the
// as the MinGasPrices param. If fee is too low, decorator returns error and tx // minimum global fee, which is defined by the MinGasPrice (parameter) * GasLimit (tx argument).
// 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}
}
func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { 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 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() { for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok { if !ok {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrUnknownRequest, errortypes.ErrUnknownRequest,
"invalid message type %T, expected %T", "invalid message type %T, expected %T",
msg, (*evmtypes.MsgEthereumTx)(nil), 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) txData, err := evmtypes.UnpackTxData(ethMsg.Data)
if err != nil { 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 { if txData.TxType() != ethtypes.LegacyTxType {
@ -133,8 +158,8 @@ func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
fee := sdk.NewDecFromBigInt(feeAmt) fee := sdk.NewDecFromBigInt(feeAmt)
if fee.LT(requiredFee) { if fee.LT(requiredFee) {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrInsufficientFee, 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 "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(), 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) 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)
}

View File

@ -42,7 +42,7 @@ func (s AnteTestSuite) TestMinGasPriceDecorator() {
return &invalidTx{} return &invalidTx{}
}, },
false, false,
"must be a FeeTx", "invalid transaction type",
false, false,
}, },
{ {
@ -345,3 +345,7 @@ func (s AnteTestSuite) TestEthMinGasPriceDecorator() {
} }
} }
} }
func (suite AnteTestSuite) TestEthMempoolFeeDecorator() {
// TODO: add test
}

View File

@ -1,8 +1,9 @@
package ante package ante
import ( import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/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" "github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/auth/ante"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
@ -32,19 +33,19 @@ type HandlerOptions struct {
func (options HandlerOptions) validate() error { func (options HandlerOptions) validate() error {
if options.AccountKeeper == nil { 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 { 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 { 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 { 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 { 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 return nil
} }

View File

@ -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 NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) evm.EVM
DeductTxCostsFromUserBalance( DeductTxCostsFromUserBalance(
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool, 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)
GetBalance(ctx sdk.Context, addr common.Address) *big.Int GetBalance(ctx sdk.Context, addr common.Address) *big.Int
ResetTransientGasUsed(ctx sdk.Context) ResetTransientGasUsed(ctx sdk.Context)
GetTxIndexTransient(ctx sdk.Context) uint64 GetTxIndexTransient(ctx sdk.Context) uint64

View File

@ -1,8 +1,9 @@
package ante package ante
import ( import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types" 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" 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) { 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() { for _, msg := range tx.GetMsgs() {
if _, ok := msg.(*evmtypes.MsgEthereumTx); ok { if _, ok := msg.(*evmtypes.MsgEthereumTx); ok {
return ctx, sdkerrors.Wrapf( return ctx, errorsmod.Wrapf(
sdkerrors.ErrInvalidType, errortypes.ErrInvalidType,
"MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option", "MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option",
) )
} }

189
app/ante/setup.go Normal file
View File

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

42
app/ante/setup_test.go Normal file
View File

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

View File

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

65
app/ante/sigverify.go Normal file
View File

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

View File

@ -594,9 +594,12 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() {
before := k.GetBalance(suite.ctx, suite.from) 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) txData, err := types.UnpackTxData(tx.Data)
suite.Require().NoError(err) 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) suite.Require().NoError(err)
res, err := k.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx) res, err := k.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx)

View File

@ -1,7 +1,6 @@
package keeper package keeper
import ( import (
"math"
"math/big" "math/big"
errorsmod "cosmossdk.io/errors" errorsmod "cosmossdk.io/errors"
@ -17,22 +16,16 @@ import (
) )
// DeductTxCostsFromUserBalance it calculates the tx costs and deducts the fees // DeductTxCostsFromUserBalance it calculates the tx costs and deducts the fees
// returns (effectiveFee, priority, error)
func (k Keeper) DeductTxCostsFromUserBalance( func (k Keeper) DeductTxCostsFromUserBalance(
ctx sdk.Context, ctx sdk.Context,
msgEthTx evmtypes.MsgEthereumTx, msgEthTx evmtypes.MsgEthereumTx,
txData evmtypes.TxData, txData evmtypes.TxData,
denom string, denom string,
baseFee *big.Int,
homestead, istanbul, london bool, homestead, istanbul, london bool,
) (fees sdk.Coins, priority int64, err error) { ) (fees sdk.Coins, err error) {
isContractCreation := txData.GetTo() == nil 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() gasLimit := txData.GetGas()
var accessList ethtypes.AccessList var accessList ethtypes.AccessList
@ -42,7 +35,7 @@ func (k Keeper) DeductTxCostsFromUserBalance(
intrinsicGas, err := core.IntrinsicGas(txData.GetData(), accessList, isContractCreation, homestead, istanbul) intrinsicGas, err := core.IntrinsicGas(txData.GetData(), accessList, isContractCreation, homestead, istanbul)
if err != nil { if err != nil {
return nil, 0, errorsmod.Wrapf( return nil, errorsmod.Wrapf(
err, err,
"failed to retrieve intrinsic gas, contract creation = %t; homestead = %t, istanbul = %t", "failed to retrieve intrinsic gas, contract creation = %t; homestead = %t, istanbul = %t",
isContractCreation, homestead, istanbul, isContractCreation, homestead, istanbul,
@ -51,53 +44,43 @@ func (k Keeper) DeductTxCostsFromUserBalance(
// intrinsic gas verification during CheckTx // intrinsic gas verification during CheckTx
if ctx.IsCheckTx() && gasLimit < intrinsicGas { if ctx.IsCheckTx() && gasLimit < intrinsicGas {
return nil, 0, errorsmod.Wrapf( return nil, errorsmod.Wrapf(
errortypes.ErrOutOfGas, errortypes.ErrOutOfGas,
"gas limit too low: %d (gas limit) < %d (intrinsic gas)", gasLimit, intrinsicGas, "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 { 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) ", "the tx gasfeecap is lower than the tx baseFee: %s (gasfeecap), %s (basefee) ",
txData.GetGasFeeCap(), txData.GetGasFeeCap(),
baseFee) baseFee)
} }
feeAmt = txData.EffectiveFee(baseFee) feeAmt := txData.EffectiveFee(baseFee)
if feeAmt.Sign() == 0 { if feeAmt.Sign() == 0 {
// zero fee, no need to deduct // 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 // deduct the full gas cost from the user balance
if err := authante.DeductFees(k.bankKeeper, ctx, signerAcc, fees); err != nil { if err := authante.DeductFees(k.bankKeeper, ctx, signerAcc, fees); err != nil {
return nil, 0, errorsmod.Wrapf( return nil, errorsmod.Wrapf(
err, err,
"failed to deduct full gas cost %s from the user %s balance", "failed to deduct full gas cost %s from the user %s balance",
fees, msgEthTx.From, fees, msgEthTx.From,
) )
} }
// calculate priority based on effective gas price return fees, nil
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
} }
// CheckSenderBalance validates that the tx cost value is positive and that the // CheckSenderBalance validates that the tx cost value is positive and that the

View File

@ -453,11 +453,16 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
txData, _ := evmtypes.UnpackTxData(tx.Data) 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, suite.ctx,
*tx, *tx,
txData, txData,
evmtypes.DefaultEVMDenom, evmtypes.DefaultEVMDenom,
baseFee,
false, false,
false, false,
suite.enableFeemarket, // london suite.enableFeemarket, // london

View File

@ -1,10 +1,37 @@
package types package types
import ( import (
"math"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "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 // Failed returns if the contract execution failed in vm errors
func (m *MsgEthereumTxResponse) Failed() bool { func (m *MsgEthereumTxResponse) Failed() bool {
return len(m.VmError) > 0 return len(m.VmError) > 0