Skip to content

Commit

Permalink
test(evm): ante handler setup ctx coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
onikonychev committed Jun 7, 2024
1 parent af87b51 commit a862309
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 155 deletions.
49 changes: 49 additions & 0 deletions app/evmante_emit_event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2023-2024 Nibi, Inc.
package app

import (
"strconv"

errorsmod "cosmossdk.io/errors"
"github.com/NibiruChain/nibiru/x/evm"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
)

// EthEmitEventDecorator emit events in ante handler in case of tx execution failed (out of block gas limit).
type EthEmitEventDecorator struct {
AppKeepers
}

// NewEthEmitEventDecorator creates a new EthEmitEventDecorator
func NewEthEmitEventDecorator(k AppKeepers) EthEmitEventDecorator {
return EthEmitEventDecorator{AppKeepers: k}

Check warning on line 20 in app/evmante_emit_event.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_emit_event.go#L19-L20

Added lines #L19 - L20 were not covered by tests
}

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

Check warning on line 26 in app/evmante_emit_event.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_emit_event.go#L26

Added line #L26 was not covered by tests
// 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 events at the
// very end of ante handler to be indexed by the consensus engine.
txIndex := eeed.EvmKeeper.EVMState().BlockTxIndex.GetOr(ctx, 0)

Check warning on line 30 in app/evmante_emit_event.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_emit_event.go#L30

Added line #L30 was not covered by tests

for i, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evm.MsgEthereumTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil))

Check warning on line 35 in app/evmante_emit_event.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_emit_event.go#L32-L35

Added lines #L32 - L35 were not covered by tests
}

// 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(
evm.EventTypeEthereumTx,
sdk.NewAttribute(evm.AttributeKeyEthereumTxHash, msgEthTx.Hash),
sdk.NewAttribute(evm.AttributeKeyTxIndex, strconv.FormatUint(txIndex+uint64(i), 10)), // #nosec G701
))

Check warning on line 45 in app/evmante_emit_event.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_emit_event.go#L41-L45

Added lines #L41 - L45 were not covered by tests
}

return next(ctx, tx, simulate)

Check warning on line 48 in app/evmante_emit_event.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_emit_event.go#L48

Added line #L48 was not covered by tests
}
159 changes: 4 additions & 155 deletions app/evmante_setup_ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@
package app

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"
gethcore "github.com/ethereum/go-ethereum/core/types"

"github.com/NibiruChain/nibiru/x/evm"
)

// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption
Expand All @@ -37,7 +30,10 @@ func (esc EthSetupContextDecorator) AnteHandle(
// all transactions must implement GasTx
_, ok := tx.(authante.GasTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected GasTx", tx)
return ctx, errorsmod.Wrapf(
errortypes.ErrInvalidType,
"invalid transaction type %T, expected GasTx", tx,
)

Check warning on line 36 in app/evmante_setup_ctx.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_setup_ctx.go#L33-L36

Added lines #L33 - L36 were not covered by tests
}

// We need to setup an empty gas config so that the gas is consistent with Ethereum.
Expand All @@ -50,150 +46,3 @@ func (esc EthSetupContextDecorator) AnteHandle(
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 {
AppKeepers
}

// NewEthEmitEventDecorator creates a new EthEmitEventDecorator
func NewEthEmitEventDecorator(k AppKeepers) EthEmitEventDecorator {
return EthEmitEventDecorator{AppKeepers: k}
}

// 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 events at the
// very end of ante handler to be indexed by the consensus engine.
txIndex := eeed.EvmKeeper.EVMState().BlockTxIndex.GetOr(ctx, 0)

for i, msg := range tx.GetMsgs() {
msgEthTx, ok := msg.(*evm.MsgEthereumTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.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(
evm.EventTypeEthereumTx,
sdk.NewAttribute(evm.AttributeKeyEthereumTxHash, msgEthTx.Hash),
sdk.NewAttribute(evm.AttributeKeyTxIndex, strconv.FormatUint(txIndex+uint64(i), 10)), // #nosec G701
))
}

return next(ctx, tx, simulate)
}

// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures
type EthValidateBasicDecorator struct {
AppKeepers
}

// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator
func NewEthValidateBasicDecorator(k AppKeepers) EthValidateBasicDecorator {
return EthValidateBasicDecorator{
AppKeepers: k,
}
}

// AnteHandle handles basic validation of tx
func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// no need to validate basic on recheck tx, call next antehandler
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}

err := tx.ValidateBasic()
// ErrNoSignatures is fine with eth tx
if err != nil && !errors.Is(err, errortypes.ErrNoSignatures) {
return ctx, errorsmod.Wrap(err, "tx basic validation failed")
}

// For eth type cosmos tx, some fields should be verified as zero values,
// since we will only verify the signature against the hash of the MsgEthereumTx.Data
wrapperTx, ok := tx.(protoTxProvider)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid tx type %T, didn't implement interface protoTxProvider", tx)
}

protoTx := wrapperTx.GetProtoTx()
body := protoTx.Body
if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest,
"for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty")
}

if len(body.ExtensionOptions) != 1 {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1")
}

authInfo := protoTx.AuthInfo
if len(authInfo.SignerInfos) > 0 {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty")
}

if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty")
}

sigs := protoTx.Signatures
if len(sigs) > 0 {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx Signatures should be empty")
}

txFee := sdk.Coins{}
txGasLimit := uint64(0)

evmParams := vbd.EvmKeeper.GetParams(ctx)
baseFee := vbd.EvmKeeper.GetBaseFee(ctx)
enableCreate := evmParams.GetEnableCreate()
enableCall := evmParams.GetEnableCall()
evmDenom := evmParams.GetEvmDenom()

for _, msg := range protoTx.GetMsgs() {
msgEthTx, ok := msg.(*evm.MsgEthereumTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.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 := evm.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(evm.ErrCreateDisabled, "failed to create new contract")
} else if !enableCall && txData.GetTo() != nil {
return ctx, errorsmod.Wrap(evm.ErrCallDisabled, "failed to call contract")
}

if baseFee == nil && txData.TxType() == gethcore.DynamicFeeTxType {
return ctx, errorsmod.Wrap(gethcore.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 changes: 42 additions & 0 deletions app/evmante_setup_ctx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package app_test

import (
"math"

"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/x/evm/evmtest"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

func (s *TestSuite) TestEthSetupContextDecorator() {
deps := evmtest.NewTestDeps()
stateDB := deps.StateDB()
anteDec := app.NewEthSetUpContextDecorator(deps.Chain.AppKeepers)

s.Require().NoError(stateDB.Commit())
tx := happyCreateContractTx(&deps)

// Set block gas used to non 0 to check that handler resets it
anteDec.EvmKeeper.EvmState.BlockGasUsed.Set(deps.Ctx, 1000)

// Ante handler returns new context
newCtx, err := anteDec.AnteHandle(
deps.Ctx, tx, false, NextNoOpAnteHandler,
)
s.Require().NoError(err)

// Check that ctx gas meter is set up to infinite
ctxGasMeter := newCtx.GasMeter()
s.Require().Equal(sdk.Gas(math.MaxUint64), ctxGasMeter.GasRemaining())

// Check that gas configs are reset to default
defaultGasConfig := storetypes.GasConfig{}
s.Require().Equal(defaultGasConfig, newCtx.KVGasConfig())
s.Require().Equal(defaultGasConfig, newCtx.TransientKVGasConfig())

// Check that block gas used is reset to 0
gas, err := anteDec.EvmKeeper.EvmState.BlockGasUsed.Get(newCtx)
s.Require().NoError(err)
s.Require().Equal(gas, uint64(0))
}
123 changes: 123 additions & 0 deletions app/evmante_validate_basic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) 2023-2024 Nibi, Inc.
package app

import (
"errors"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
gethcore "github.com/ethereum/go-ethereum/core/types"

"github.com/NibiruChain/nibiru/x/evm"
)

// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures
type EthValidateBasicDecorator struct {
AppKeepers
}

// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator
func NewEthValidateBasicDecorator(k AppKeepers) EthValidateBasicDecorator {
return EthValidateBasicDecorator{
AppKeepers: k,

Check warning on line 24 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L22-L24

Added lines #L22 - L24 were not covered by tests
}
}

// AnteHandle handles basic validation of tx
func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {

Check warning on line 29 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L29

Added line #L29 was not covered by tests
// no need to validate basic on recheck tx, call next antehandler
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)

Check warning on line 32 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L31-L32

Added lines #L31 - L32 were not covered by tests
}

err := tx.ValidateBasic()

Check warning on line 35 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L35

Added line #L35 was not covered by tests
// ErrNoSignatures is fine with eth tx
if err != nil && !errors.Is(err, errortypes.ErrNoSignatures) {
return ctx, errorsmod.Wrap(err, "tx basic validation failed")

Check warning on line 38 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L37-L38

Added lines #L37 - L38 were not covered by tests
}

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

Check warning on line 45 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L43-L45

Added lines #L43 - L45 were not covered by tests
}

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")

Check warning on line 52 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L48-L52

Added lines #L48 - L52 were not covered by tests
}

if len(body.ExtensionOptions) != 1 {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1")

Check warning on line 56 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L55-L56

Added lines #L55 - L56 were not covered by tests
}

authInfo := protoTx.AuthInfo
if len(authInfo.SignerInfos) > 0 {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty")

Check warning on line 61 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L59-L61

Added lines #L59 - L61 were not covered by tests
}

if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty")

Check warning on line 65 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L64-L65

Added lines #L64 - L65 were not covered by tests
}

sigs := protoTx.Signatures
if len(sigs) > 0 {
return ctx, errorsmod.Wrap(errortypes.ErrInvalidRequest, "for eth tx Signatures should be empty")

Check warning on line 70 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L68-L70

Added lines #L68 - L70 were not covered by tests
}

txFee := sdk.Coins{}
txGasLimit := uint64(0)

Check warning on line 74 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L73-L74

Added lines #L73 - L74 were not covered by tests

evmParams := vbd.EvmKeeper.GetParams(ctx)
baseFee := vbd.EvmKeeper.GetBaseFee(ctx)
enableCreate := evmParams.GetEnableCreate()
enableCall := evmParams.GetEnableCall()
evmDenom := evmParams.GetEvmDenom()

Check warning on line 80 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L76-L80

Added lines #L76 - L80 were not covered by tests

for _, msg := range protoTx.GetMsgs() {
msgEthTx, ok := msg.(*evm.MsgEthereumTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evm.MsgEthereumTx)(nil))

Check warning on line 85 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L82-L85

Added lines #L82 - L85 were not covered by tests
}

// Validate `From` field
if msgEthTx.From != "" {
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid From %s, expect empty string", msgEthTx.From)

Check warning on line 90 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L89-L90

Added lines #L89 - L90 were not covered by tests
}

txGasLimit += msgEthTx.GetGas()

Check warning on line 93 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L93

Added line #L93 was not covered by tests

txData, err := evm.UnpackTxData(msgEthTx.Data)
if err != nil {
return ctx, errorsmod.Wrap(err, "failed to unpack MsgEthereumTx Data")

Check warning on line 97 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L95-L97

Added lines #L95 - L97 were not covered by tests
}

// return error if contract creation or call are disabled through governance
if !enableCreate && txData.GetTo() == nil {
return ctx, errorsmod.Wrap(evm.ErrCreateDisabled, "failed to create new contract")
} else if !enableCall && txData.GetTo() != nil {
return ctx, errorsmod.Wrap(evm.ErrCallDisabled, "failed to call contract")

Check warning on line 104 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L101-L104

Added lines #L101 - L104 were not covered by tests
}

if baseFee == nil && txData.TxType() == gethcore.DynamicFeeTxType {
return ctx, errorsmod.Wrap(gethcore.ErrTxTypeNotSupported, "dynamic fee tx not supported")

Check warning on line 108 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L107-L108

Added lines #L107 - L108 were not covered by tests
}

txFee = txFee.Add(sdk.Coin{Denom: evmDenom, Amount: sdkmath.NewIntFromBigInt(txData.Fee())})

Check warning on line 111 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L111

Added line #L111 was not covered by tests
}

if !authInfo.Fee.Amount.IsEqual(txFee) {
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee)

Check warning on line 115 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L114-L115

Added lines #L114 - L115 were not covered by tests
}

if authInfo.Fee.GasLimit != txGasLimit {
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit)

Check warning on line 119 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L118-L119

Added lines #L118 - L119 were not covered by tests
}

return next(ctx, tx, simulate)

Check warning on line 122 in app/evmante_validate_basic.go

View check run for this annotation

Codecov / codecov/patch

app/evmante_validate_basic.go#L122

Added line #L122 was not covered by tests
}

0 comments on commit a862309

Please sign in to comment.