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:
parent
f4c7be2efc
commit
16fb2e1110
@ -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(
|
||||||
|
@ -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 := ðsecp256k1.PubKey{
|
pk := ðsecp256k1.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
347
app/ante/eth.go
347
app/ante/eth.go
@ -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)
|
|
||||||
}
|
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
110
app/ante/fees.go
110
app/ante/fees.go
@ -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)
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
189
app/ante/setup.go
Normal 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
42
app/ante/setup_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
65
app/ante/signverify_test.go
Normal file
65
app/ante/signverify_test.go
Normal 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
65
app/ante/sigverify.go
Normal 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)
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user