Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 4 additions & 38 deletions app/ante/evm_checktx.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,45 +75,11 @@ func EvmCheckTxAnte(
}

func EvmStatelessChecks(ctx sdk.Context, tx sdk.Tx, chainID *big.Int) error {
txBody, ok := tx.(TxBody)
if ok {
body := txBody.GetBody()
if body.Memo != "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "memo must be empty for EVM txs")
}
if body.TimeoutHeight != 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "timeout_height must be zero for EVM txs")
}
if len(body.ExtensionOptions) > 0 || len(body.NonCriticalExtensionOptions) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "extension options must be empty for EVM txs")
}
}

txAuth, ok := tx.(TxAuthInfo)
if ok {
authInfo := txAuth.GetAuthInfo()
if len(authInfo.SignerInfos) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signer_infos must be empty for EVM txs")
}
if authInfo.Fee != nil {
if len(authInfo.Fee.Amount) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee amount must be empty for EVM txs")
}
if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee payer and granter must be empty for EVM txs")
}
}
if err := evmante.ValidateNoCosmosTxFields(tx); err != nil {
return err
}

txSig, ok := tx.(TxSignaturesV2)
if ok {
sigs, err := txSig.GetSignaturesV2()
if err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not get signatures")
}
if len(sigs) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs")
}
if err := evmante.ValidateNoOuterEVMMsgFields(tx); err != nil {
return err
}

if len(tx.GetMsgs()) != 1 {
Expand Down
32 changes: 32 additions & 0 deletions app/ante/evm_checktx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ante

import (
"testing"

"github.com/stretchr/testify/require"

codectypes "github.com/sei-protocol/sei-chain/sei-cosmos/codec/types"
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
txtypes "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx"
authtx "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/tx"
evmtypes "github.com/sei-protocol/sei-chain/x/evm/types"
"github.com/sei-protocol/sei-chain/x/evm/types/ethtx"
)

func TestEvmStatelessChecksRejectsRawSignatures(t *testing.T) {
msg, err := evmtypes.NewMsgEVMTransaction(&ethtx.AssociateTx{})
require.NoError(t, err)
msgAny, err := codectypes.NewAnyWithValue(msg)
require.NoError(t, err)

protoTx := &txtypes.Tx{
Body: &txtypes.TxBody{Messages: []*codectypes.Any{msgAny}},
AuthInfo: &txtypes.AuthInfo{
Fee: &txtypes.Fee{},
},
Signatures: [][]byte{{0x1}},
}

err = EvmStatelessChecks(sdk.Context{}, authtx.WrapTx(protoTx).GetTx(), nil)
require.ErrorContains(t, err, "signatures must be empty")
}
25 changes: 25 additions & 0 deletions giga/deps/xevm/types/message_evm_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,38 @@ func (msg *MsgEVMTransaction) GetSignBytes() []byte {
}

func (msg *MsgEVMTransaction) ValidateBasic() error {
if msg.Derived != nil && msg.Derived.PubKey == nil {
return sdkerrors.ErrInvalidPubKey
}
if err := validateCanonicalTxData(msg.Data); err != nil {
return err
}
amsg, isAssociate := msg.GetAssociateTx()
if isAssociate && len(amsg.CustomMessage) > MaxAssociateCustomMessageLength {
return sdkerrors.Wrapf(sdkerrors.ErrTxTooLarge, "custom message can have at most 64 characters")
}
return nil
}

func validateCanonicalTxData(any *codectypes.Any) error {
txData, err := UnpackTxData(any)
if err != nil {
return err
}
msg, ok := txData.(proto.Message)
if !ok {
return sdkerrors.Wrap(sdkerrors.ErrInvalidType, "invalid EVM tx data")
}
canonicalBz, err := proto.Marshal(msg)
if err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not marshal EVM tx data")
}
if len(any.Value) != len(canonicalBz) {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "EVM tx data contains unsupported fields")
}
return nil
}

func (msg *MsgEVMTransaction) AsTransaction() (*ethtypes.Transaction, ethtx.TxData) {
txData, err := UnpackTxData(msg.Data)
if err != nil {
Expand Down
18 changes: 18 additions & 0 deletions giga/deps/xevm/types/message_evm_transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import (
"github.com/sei-protocol/sei-chain/app"
testkeeper "github.com/sei-protocol/sei-chain/giga/deps/testutil/keeper"
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors"
wasmtypes "github.com/sei-protocol/sei-chain/sei-wasmd/x/wasm/types"

"github.com/ethereum/go-ethereum/common"
"github.com/sei-protocol/sei-chain/giga/deps/xevm/derived"
"github.com/sei-protocol/sei-chain/giga/deps/xevm/types"
"github.com/sei-protocol/sei-chain/giga/deps/xevm/types/ethtx"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -101,3 +103,19 @@ func TestMustGetEVMTransactionMessageMultipleMsgs(t *testing.T) {
types.MustGetEVMTransactionMessage(testTx)
t.Errorf("Should not be able to convert a non evm emssage")
}

func TestValidateBasicRejectsBloatInTxData(t *testing.T) {
msg, err := types.NewMsgEVMTransaction(&ethtx.AssociateTx{})
require.NoError(t, err)

msg.Data.Value = append(msg.Data.Value, 0x28, 0x01)

err = msg.ValidateBasic()
require.ErrorContains(t, err, "EVM tx data contains unsupported fields")
}

func TestValidateBasicRejectsExternalDerived(t *testing.T) {
msg := &types.MsgEVMTransaction{Derived: &derived.Derived{SenderEVMAddr: common.BytesToAddress([]byte("abc"))}}
err := msg.ValidateBasic()
require.ErrorIs(t, err, sdkerrors.ErrInvalidPubKey)
}
126 changes: 126 additions & 0 deletions x/evm/ante/field_bloat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package ante

import (
"github.com/gogo/protobuf/proto"
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors"
txtypes "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx"
signing "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx/signing"
evmtypes "github.com/sei-protocol/sei-chain/x/evm/types"
)

type protoTx interface {
GetProtoTx() *txtypes.Tx
}

// ValidateNoCosmosTxFields rejects Cosmos wrapper fields that EVM txs must not use.
func ValidateNoCosmosTxFields(tx sdk.Tx) error {
txBody, ok := tx.(interface {
GetBody() *txtypes.TxBody
})
if ok {
body := txBody.GetBody()
if body.Memo != "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "memo must be empty for EVM txs")
}
if body.TimeoutHeight != 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "timeout_height must be zero for EVM txs")
}
if len(body.ExtensionOptions) > 0 || len(body.NonCriticalExtensionOptions) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "extension options must be empty for EVM txs")
}
}

txAuth, ok := tx.(interface {
GetAuthInfo() *txtypes.AuthInfo
})
if ok {
authInfo := txAuth.GetAuthInfo()
if len(authInfo.SignerInfos) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signer_infos must be empty for EVM txs")
}
if authInfo.Fee != nil {
if len(authInfo.Fee.Amount) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee amount must be empty for EVM txs")
}
if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee payer and granter must be empty for EVM txs")
}
}
}

if txProto, ok := tx.(protoTx); ok {
if len(txProto.GetProtoTx().Signatures) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs")
}
return nil
}

txSig, ok := tx.(interface {
GetSignaturesV2() ([]signing.SignatureV2, error)
})
if ok {
sigs, err := txSig.GetSignaturesV2()
if err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not get signatures")
}
if len(sigs) > 0 {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs")
}
}

return nil
}

// ValidateNoOuterEVMMsgFields rejects extra bytes on the serialized outer MsgEVMTransaction.
func ValidateNoOuterEVMMsgFields(tx sdk.Tx) error {
txProto, ok := tx.(protoTx)
if !ok {
return nil
}

protoTx := txProto.GetProtoTx()
if protoTx == nil || protoTx.Body == nil || len(protoTx.Body.Messages) != 1 || len(tx.GetMsgs()) != 1 {
return nil
}

msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEVMTransaction)
if !ok || msg.Data == nil || protoTx.Body.Messages[0] == nil {
return nil
}

canonicalMsgBz, err := canonicalEVMMsgBytes(msg)
if err != nil {
return err
}
if len(protoTx.Body.Messages[0].Value) != len(canonicalMsgBz) {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "EVM message contains unsupported fields")
}
return nil
}

func canonicalEVMMsgBytes(msg *evmtypes.MsgEVMTransaction) ([]byte, error) {
txData, err := evmtypes.UnpackTxData(msg.Data)
if err != nil {
return nil, err
}
txDataMsg, ok := txData.(proto.Message)
if !ok {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidType, "invalid EVM tx data")
}
canonicalDataBz, err := proto.Marshal(txDataMsg)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not marshal EVM tx data")
}

normalizedMsg := *msg
normalizedData := *msg.Data
normalizedData.Value = canonicalDataBz
normalizedMsg.Data = &normalizedData

canonicalMsgBz, err := normalizedMsg.Marshal()
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not marshal EVM message")
}
return canonicalMsgBz, nil
}
51 changes: 4 additions & 47 deletions x/evm/ante/no_cosmos_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package ante

import (
sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types"
sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors"
txtypes "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx"
signing "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx/signing"
)

// EVMNoCosmosFieldsDecorator ensures all Cosmos tx fields are empty for EVM txs.
Expand All @@ -15,51 +12,11 @@ func NewEVMNoCosmosFieldsDecorator() EVMNoCosmosFieldsDecorator {
}

func (d EVMNoCosmosFieldsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
txBody, ok := tx.(interface {
GetBody() *txtypes.TxBody
})
if ok {
body := txBody.GetBody()
if body.Memo != "" {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "memo must be empty for EVM txs")
}
if body.TimeoutHeight != 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "timeout_height must be zero for EVM txs")
}
if len(body.ExtensionOptions) > 0 || len(body.NonCriticalExtensionOptions) > 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "extension options must be empty for EVM txs")
}
if err := ValidateNoCosmosTxFields(tx); err != nil {
return ctx, err
}

txAuth, ok := tx.(interface {
GetAuthInfo() *txtypes.AuthInfo
})
if ok {
authInfo := txAuth.GetAuthInfo()
if len(authInfo.SignerInfos) > 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signer_infos must be empty for EVM txs")
}
if authInfo.Fee != nil {
if len(authInfo.Fee.Amount) > 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee amount must be empty for EVM txs")
}
if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee payer and granter must be empty for EVM txs")
}
}
}

txSig, ok := tx.(interface {
GetSignaturesV2() ([]signing.SignatureV2, error)
})
if ok {
sigs, err := txSig.GetSignaturesV2()
if err != nil {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "could not get signatures")
}
if len(sigs) > 0 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "signatures must be empty for EVM txs")
}
if err := ValidateNoOuterEVMMsgFields(tx); err != nil {
return ctx, err
}

return next(ctx, tx, simulate)
Expand Down
Loading
Loading