diff --git a/protocol/app/app.go b/protocol/app/app.go index 5b4e38955a..4e974025f4 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1233,9 +1233,16 @@ func New( // Initialize authenticators app.AuthenticatorManager = authenticator.NewAuthenticatorManager() - app.AuthenticatorManager.InitializeAuthenticators([]accountplusmoduletypes.Authenticator{ - authenticator.NewSignatureVerification(app.AccountKeeper), - }) + app.AuthenticatorManager.InitializeAuthenticators( + []accountplusmoduletypes.Authenticator{ + authenticator.NewAllOf(app.AuthenticatorManager), + authenticator.NewAnyOf(app.AuthenticatorManager), + authenticator.NewSignatureVerification(app.AccountKeeper), + authenticator.NewMessageFilter(), + authenticator.NewClobPairIdFilter(), + authenticator.NewSubaccountFilter(), + }, + ) app.AccountPlusKeeper = *accountplusmodulekeeper.NewKeeper( appCodec, keys[accountplusmoduletypes.StoreKey], diff --git a/protocol/testutil/app/app.go b/protocol/testutil/app/app.go index 40de56f9e2..a4dbac0bc2 100644 --- a/protocol/testutil/app/app.go +++ b/protocol/testutil/app/app.go @@ -49,6 +49,7 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/testutil/appoptions" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" testlog "github.com/dydxprotocol/v4-chain/protocol/testutil/logger" + aptypes "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" bridgetypes "github.com/dydxprotocol/v4-chain/protocol/x/bridge/types" @@ -207,7 +208,8 @@ type GenesisStates interface { govplus.GenesisState | vaulttypes.GenesisState | revsharetypes.GenesisState | - marketmapmoduletypes.GenesisState + marketmapmoduletypes.GenesisState | + aptypes.GenesisState } // UpdateGenesisDocWithAppStateForModule updates the supplied genesis doc using the provided function. The function @@ -269,6 +271,8 @@ func UpdateGenesisDocWithAppStateForModule[T GenesisStates](genesisDoc *types.Ge moduleName = marketmapmoduletypes.ModuleName case listingtypes.GenesisState: moduleName = listingtypes.ModuleName + case aptypes.GenesisState: + moduleName = aptypes.ModuleName default: panic(fmt.Errorf("Unsupported type %T", t)) } diff --git a/protocol/testutil/constants/genesis.go b/protocol/testutil/constants/genesis.go index 2c679ec3ed..ba7c2f1402 100644 --- a/protocol/testutil/constants/genesis.go +++ b/protocol/testutil/constants/genesis.go @@ -442,6 +442,14 @@ const GenesisState = `{ "validator_historical_rewards": [], "validator_slash_events": [] }, + "dydxaccountplus": { + "accounts": [], + "params": { + "is_smart_account_active": false + }, + "next_authenticator_id": "0", + "authenticator_data": [] + }, "epochs": { "epoch_info_list": [ { diff --git a/protocol/x/accountplus/authenticator/all_of.go b/protocol/x/accountplus/authenticator/all_of.go index 34460098cc..8f8b13bebc 100644 --- a/protocol/x/accountplus/authenticator/all_of.go +++ b/protocol/x/accountplus/authenticator/all_of.go @@ -49,7 +49,7 @@ func (aoa AllOf) StaticGas() uint64 { } func (aoa AllOf) Initialize(config []byte) (types.Authenticator, error) { - var initDatas []SubAuthenticatorInitData + var initDatas []types.SubAuthenticatorInitData if err := json.Unmarshal(config, &initDatas); err != nil { return nil, errorsmod.Wrap(err, "failed to parse sub-authenticators initialization data") } @@ -99,7 +99,10 @@ func (aoa AllOf) Authenticate(ctx sdk.Context, request types.AuthenticationReque request.Signature = signatures[i] } if err := auth.Authenticate(ctx, request); err != nil { - return err + return errorsmod.Wrap( + types.ErrAllOfVerification, + err.Error(), + ) } } return nil diff --git a/protocol/x/accountplus/authenticator/any_of.go b/protocol/x/accountplus/authenticator/any_of.go index 325834268a..c51dfa80f0 100644 --- a/protocol/x/accountplus/authenticator/any_of.go +++ b/protocol/x/accountplus/authenticator/any_of.go @@ -60,7 +60,7 @@ func (aoa AnyOf) StaticGas() uint64 { func (aoa AnyOf) Initialize(config []byte) (types.Authenticator, error) { // Decode the initialization data for each sub-authenticator - var initDatas []SubAuthenticatorInitData + var initDatas []types.SubAuthenticatorInitData if err := json.Unmarshal(config, &initDatas); err != nil { return nil, errorsmod.Wrap(err, "failed to parse sub-authenticators initialization data") } @@ -126,7 +126,7 @@ func (aoa AnyOf) Authenticate(ctx sdk.Context, request types.AuthenticationReque if err != nil { // return all errors return errorsmod.Wrapf( - sdkerrors.ErrUnauthorized, + types.ErrAnyOfVerification, "all sub-authenticators failed to authenticate: %s", strings.Join(subAuthErrors, "; "), ) diff --git a/protocol/x/accountplus/authenticator/clob_pair_id_filter.go b/protocol/x/accountplus/authenticator/clob_pair_id_filter.go index 2a20762952..bdf3841384 100644 --- a/protocol/x/accountplus/authenticator/clob_pair_id_filter.go +++ b/protocol/x/accountplus/authenticator/clob_pair_id_filter.go @@ -6,7 +6,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" ) @@ -77,7 +76,7 @@ func (m ClobPairIdFilter) Authenticate(ctx sdk.Context, request types.Authentica for _, clobPairId := range requestOrderIds { if _, ok := m.whitelist[clobPairId]; !ok { return errorsmod.Wrapf( - sdkerrors.ErrUnauthorized, + types.ErrClobPairIdVerification, "order id %d not in whitelist %v", clobPairId, m.whitelist, diff --git a/protocol/x/accountplus/authenticator/clob_pair_id_filter_test.go b/protocol/x/accountplus/authenticator/clob_pair_id_filter_test.go index 948f588b5a..86802677c5 100644 --- a/protocol/x/accountplus/authenticator/clob_pair_id_filter_test.go +++ b/protocol/x/accountplus/authenticator/clob_pair_id_filter_test.go @@ -6,11 +6,11 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/authenticator" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" "github.com/stretchr/testify/suite" ) @@ -105,7 +105,7 @@ func (s *ClobPairIdFilterTest) TestFilter() { if tt.match { s.Require().NoError(err) } else { - s.Require().ErrorIs(err, sdkerrors.ErrUnauthorized) + s.Require().ErrorIs(err, types.ErrClobPairIdVerification) } }) } diff --git a/protocol/x/accountplus/authenticator/composite.go b/protocol/x/accountplus/authenticator/composite.go index b49000fb42..babbfbffae 100644 --- a/protocol/x/accountplus/authenticator/composite.go +++ b/protocol/x/accountplus/authenticator/composite.go @@ -10,11 +10,6 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" ) -type SubAuthenticatorInitData struct { - Type string `json:"type"` - Config []byte `json:"config"` -} - func subTrack( ctx sdk.Context, request types.AuthenticationRequest, @@ -50,7 +45,7 @@ func onSubAuthenticatorsAdded( authenticatorId string, am *AuthenticatorManager, ) error { - var initDatas []SubAuthenticatorInitData + var initDatas []types.SubAuthenticatorInitData if err := json.Unmarshal(data, &initDatas); err != nil { return errorsmod.Wrapf(err, "failed to unmarshal sub-authenticator init data") } @@ -97,7 +92,7 @@ func onSubAuthenticatorsRemoved( authenticatorId string, am *AuthenticatorManager, ) error { - var initDatas []SubAuthenticatorInitData + var initDatas []types.SubAuthenticatorInitData if err := json.Unmarshal(data, &initDatas); err != nil { return err } diff --git a/protocol/x/accountplus/authenticator/composition_test.go b/protocol/x/accountplus/authenticator/composition_test.go index 01b3f1a315..b05335b564 100644 --- a/protocol/x/accountplus/authenticator/composition_test.go +++ b/protocol/x/accountplus/authenticator/composition_test.go @@ -187,9 +187,9 @@ func (s *AggregatedAuthenticatorsTest) TestAnyOf() { for _, tc := range testCases { s.T().Run(tc.name, func(t *testing.T) { // Convert the authenticators to InitializationData - initData := []authenticator.SubAuthenticatorInitData{} + initData := []types.SubAuthenticatorInitData{} for _, auth := range tc.authenticators { - initData = append(initData, authenticator.SubAuthenticatorInitData{ + initData = append(initData, types.SubAuthenticatorInitData{ Type: auth.Type(), Config: testData, }) @@ -344,9 +344,9 @@ func (s *AggregatedAuthenticatorsTest) TestAllOf() { for _, tc := range testCases { s.T().Run(tc.name, func(t *testing.T) { // Convert the authenticators to InitializationData - initData := []authenticator.SubAuthenticatorInitData{} + initData := []types.SubAuthenticatorInitData{} for _, auth := range tc.authenticators { - initData = append(initData, authenticator.SubAuthenticatorInitData{ + initData = append(initData, types.SubAuthenticatorInitData{ Type: auth.Type(), Config: testData, }) @@ -562,14 +562,14 @@ func (csa *CompositeSpyAuth) buildInitData() ([]byte, error) { } return json.Marshal(spyData) } else if len(csa.anyOf) > 0 { - var initData []authenticator.SubAuthenticatorInitData + var initData []types.SubAuthenticatorInitData for _, subAuth := range csa.anyOf { data, err := subAuth.buildInitData() if err != nil { return nil, err } - initData = append(initData, authenticator.SubAuthenticatorInitData{ + initData = append(initData, types.SubAuthenticatorInitData{ Type: subAuth.Type(), Config: data, }) @@ -577,14 +577,14 @@ func (csa *CompositeSpyAuth) buildInitData() ([]byte, error) { return json.Marshal(initData) } else if len(csa.allOf) > 0 { - var initData []authenticator.SubAuthenticatorInitData + var initData []types.SubAuthenticatorInitData for _, subAuth := range csa.allOf { data, err := subAuth.buildInitData() if err != nil { return nil, err } - initData = append(initData, authenticator.SubAuthenticatorInitData{ + initData = append(initData, types.SubAuthenticatorInitData{ Type: subAuth.Type(), Config: data, }) @@ -901,14 +901,14 @@ func (s *AggregatedAuthenticatorsTest) TestAnyOfNotWritingFailedSubAuthState() { } func marshalAuth(ta testAuth, testData []byte) ([]byte, error) { - initData := []authenticator.SubAuthenticatorInitData{} + initData := []types.SubAuthenticatorInitData{} for _, sub := range ta.subAuths { subData, err := marshalAuth(sub, testData) if err != nil { return nil, err } - initData = append(initData, authenticator.SubAuthenticatorInitData{ + initData = append(initData, types.SubAuthenticatorInitData{ Type: sub.authenticator.Type(), Config: subData, }) diff --git a/protocol/x/accountplus/authenticator/message_filter.go b/protocol/x/accountplus/authenticator/message_filter.go index a8bc467aad..96ae8c533a 100644 --- a/protocol/x/accountplus/authenticator/message_filter.go +++ b/protocol/x/accountplus/authenticator/message_filter.go @@ -6,7 +6,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" ) @@ -54,7 +53,7 @@ func (m MessageFilter) Track(ctx sdk.Context, request types.AuthenticationReques func (m MessageFilter) Authenticate(ctx sdk.Context, request types.AuthenticationRequest) error { if _, ok := m.whitelist[sdk.MsgTypeURL(request.Msg)]; !ok { return errorsmod.Wrapf( - sdkerrors.ErrUnauthorized, + types.ErrMessageTypeVerification, "message types do not match. Got %s, Expected %v", sdk.MsgTypeURL(request.Msg), m.whitelist, diff --git a/protocol/x/accountplus/authenticator/message_filter_test.go b/protocol/x/accountplus/authenticator/message_filter_test.go index 42c5edc925..b39abf57cc 100644 --- a/protocol/x/accountplus/authenticator/message_filter_test.go +++ b/protocol/x/accountplus/authenticator/message_filter_test.go @@ -6,13 +6,13 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" bank "github.com/cosmos/cosmos-sdk/x/bank/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/authenticator" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" "github.com/stretchr/testify/suite" ) @@ -121,7 +121,7 @@ func (s *MessageFilterTest) TestBankSend() { if tt.match { s.Require().NoError(err) } else { - s.Require().ErrorIs(err, sdkerrors.ErrUnauthorized) + s.Require().ErrorIs(err, types.ErrMessageTypeVerification) } }) } diff --git a/protocol/x/accountplus/authenticator/signature_authenticator.go b/protocol/x/accountplus/authenticator/signature_authenticator.go index e3ecce10de..9e7d759cd9 100644 --- a/protocol/x/accountplus/authenticator/signature_authenticator.go +++ b/protocol/x/accountplus/authenticator/signature_authenticator.go @@ -65,7 +65,7 @@ func (sva SignatureVerification) Authenticate(ctx sdk.Context, request types.Aut if !sva.PubKey.VerifySignature(request.SignModeTxData.Direct, request.Signature) { return errorsmod.Wrapf( - sdkerrors.ErrUnauthorized, + types.ErrSignatureVerification, "signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", request.TxData.AccountNumber, request.TxData.AccountSequence, diff --git a/protocol/x/accountplus/authenticator/subaccount_filter.go b/protocol/x/accountplus/authenticator/subaccount_filter.go index c36290bc31..7e9ea984d7 100644 --- a/protocol/x/accountplus/authenticator/subaccount_filter.go +++ b/protocol/x/accountplus/authenticator/subaccount_filter.go @@ -6,7 +6,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" ) @@ -75,7 +74,7 @@ func (m SubaccountFilter) Authenticate(ctx sdk.Context, request types.Authentica for _, subaccountNum := range requestSubaccountNums { if _, ok := m.whitelist[subaccountNum]; !ok { return errorsmod.Wrapf( - sdkerrors.ErrUnauthorized, + types.ErrSubaccountVerification, "subaccount number %d not in whitelist %v", subaccountNum, m.whitelist, diff --git a/protocol/x/accountplus/authenticator/subaccount_filter_test.go b/protocol/x/accountplus/authenticator/subaccount_filter_test.go index 09f6e85eb8..2221d2adaa 100644 --- a/protocol/x/accountplus/authenticator/subaccount_filter_test.go +++ b/protocol/x/accountplus/authenticator/subaccount_filter_test.go @@ -6,11 +6,11 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/authenticator" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" "github.com/stretchr/testify/suite" ) @@ -105,7 +105,7 @@ func (s *SubaccountFilterTest) TestFilter() { if tt.match { s.Require().NoError(err) } else { - s.Require().ErrorIs(err, sdkerrors.ErrUnauthorized) + s.Require().ErrorIs(err, types.ErrSubaccountVerification) } }) } diff --git a/protocol/x/accountplus/types/errors.go b/protocol/x/accountplus/types/errors.go index 9307876c66..347334b2e2 100644 --- a/protocol/x/accountplus/types/errors.go +++ b/protocol/x/accountplus/types/errors.go @@ -33,4 +33,36 @@ var ( 6, "The transaction has multiple signers", ) + + // Errors for failing authenticator validation + ErrSignatureVerification = errorsmod.Register( + ModuleName, + 100, + "Signature verification failed", + ) + ErrMessageTypeVerification = errorsmod.Register( + ModuleName, + 101, + "Message type verification failed", + ) + ErrClobPairIdVerification = errorsmod.Register( + ModuleName, + 102, + "Clob pair id verification failed", + ) + ErrSubaccountVerification = errorsmod.Register( + ModuleName, + 103, + "Subaccount verification failed", + ) + ErrAllOfVerification = errorsmod.Register( + ModuleName, + 104, + "AllOf verification failed", + ) + ErrAnyOfVerification = errorsmod.Register( + ModuleName, + 105, + "AnyOf verification failed", + ) ) diff --git a/protocol/x/accountplus/types/iface.go b/protocol/x/accountplus/types/iface.go index b1a048cfdc..53d21a6ed4 100644 --- a/protocol/x/accountplus/types/iface.go +++ b/protocol/x/accountplus/types/iface.go @@ -10,6 +10,11 @@ type InitializedAuthenticator struct { Authenticator Authenticator } +type SubAuthenticatorInitData struct { + Type string `json:"type"` + Config []byte `json:"config"` +} + // Authenticator is an interface that encapsulates all authentication functionalities essential for // verifying transactions, paying transaction fees, and managing gas consumption during verification. type Authenticator interface { diff --git a/protocol/x/accountplus/types/message_add_authenticator.go b/protocol/x/accountplus/types/message_add_authenticator.go index 557d3796a8..f70b178d26 100644 --- a/protocol/x/accountplus/types/message_add_authenticator.go +++ b/protocol/x/accountplus/types/message_add_authenticator.go @@ -4,7 +4,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -const AUTHENTICATOR_DATA_MAX_LENGTH = 256 +// AUTHENTICATOR_DATA_MAX_LENGTH is the maximum length of the data field in an authenticator. +// +// This is used as a light-weight spam mitigation measure to prevent users from adding +// arbitrarily complex authenticators that are too resource intensive. +const AUTHENTICATOR_DATA_MAX_LENGTH = 1024 // ValidateBasic performs stateless validation for the `MsgAddAuthenticator` msg. func (msg *MsgAddAuthenticator) ValidateBasic() (err error) { diff --git a/protocol/x/accountplus/types/message_add_authenticator_test.go b/protocol/x/accountplus/types/message_add_authenticator_test.go index cc818ffad2..9a0c056486 100644 --- a/protocol/x/accountplus/types/message_add_authenticator_test.go +++ b/protocol/x/accountplus/types/message_add_authenticator_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestMsgCompleteBridge_ValidateBasic(t *testing.T) { +func TestMsgAddAuthenticator_ValidateBasic(t *testing.T) { const messageFilterData = "/cosmos.bank.v1beta1.MsgMultiSend,/cosmos.bank.v1beta1.MsgSend" tests := map[string]struct { @@ -45,6 +45,14 @@ func TestMsgCompleteBridge_ValidateBasic(t *testing.T) { {"Type":"SignatureVerification","Config":"%s"}, {"Type":"SignatureVerification","Config":"%s"}, {"Type":"SignatureVerification","Config":"%s"} + {"Type":"SignatureVerification","Config":"%s"}, + {"Type":"SignatureVerification","Config":"%s"}, + {"Type":"SignatureVerification","Config":"%s"}, + {"Type":"SignatureVerification","Config":"%s"} + {"Type":"SignatureVerification","Config":"%s"}, + {"Type":"SignatureVerification","Config":"%s"}, + {"Type":"SignatureVerification","Config":"%s"}, + {"Type":"SignatureVerification","Config":"%s"} ] " }, @@ -54,6 +62,14 @@ func TestMsgCompleteBridge_ValidateBasic(t *testing.T) { constants.BobPrivateKey.PubKey().String(), constants.CarlPrivateKey.PubKey().String(), constants.DavePrivateKey.PubKey().String(), + constants.AlicePrivateKey.PubKey().String(), + constants.BobPrivateKey.PubKey().String(), + constants.CarlPrivateKey.PubKey().String(), + constants.DavePrivateKey.PubKey().String(), + constants.AlicePrivateKey.PubKey().String(), + constants.BobPrivateKey.PubKey().String(), + constants.CarlPrivateKey.PubKey().String(), + constants.DavePrivateKey.PubKey().String(), ), ), }, diff --git a/protocol/x/clob/e2e/permissioned_keys_test.go b/protocol/x/clob/e2e/permissioned_keys_test.go new file mode 100644 index 0000000000..18007beda5 --- /dev/null +++ b/protocol/x/clob/e2e/permissioned_keys_test.go @@ -0,0 +1,664 @@ +package clob_test + +import ( + "encoding/json" + "testing" + + sdkmath "cosmossdk.io/math" + abcitypes "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + testtx "github.com/dydxprotocol/v4-chain/protocol/testutil/tx" + aptypes "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + sendingtypes "github.com/dydxprotocol/v4-chain/protocol/x/sending/types" + "github.com/stretchr/testify/require" +) + +type TestBlockWithMsgs struct { + Block uint32 + Msgs []TestSdkMsg +} + +type TestSdkMsg struct { + Msg []sdk.Msg + Authenticators []uint64 + Fees sdk.Coins + Gas uint64 + AccountNum []uint64 + SeqNum []uint64 + Signers []cryptotypes.PrivKey + + ExpectedRespCode uint32 + ExpectedLog string +} + +func TestPlaceOrder_PermissionedKeys_Failures(t *testing.T) { + config := []aptypes.SubAuthenticatorInitData{ + { + Type: "SignatureVerification", + Config: constants.AlicePrivateKey.PubKey().Bytes(), + }, + { + Type: "MessageFilter", + Config: []byte("/cosmos.bank.v1beta1.MsgSend"), + }, + } + compositeAuthenticatorConfig, err := json.Marshal(config) + require.NoError(t, err) + + tests := map[string]struct { + smartAccountEnabled bool + blocks []TestBlockWithMsgs + + expectedOrderIdsInMemclob map[clobtypes.OrderId]bool + }{ + "Txn has authenticators specified, but smart account is not enabled": { + smartAccountEnabled: false, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrSmartAccountNotActive.ABCICode(), + ExpectedLog: aptypes.ErrSmartAccountNotActive.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn has authenticators specified, but authenticator is not found": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrAuthenticatorNotFound.ABCICode(), + ExpectedLog: aptypes.ErrAuthenticatorNotFound.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn has authenticators specified, but authenticator was removed": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "MessageFilter", + Data: []byte("/cosmos.bank.v1beta1.MsgSend"), + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgRemoveAuthenticator{ + Sender: constants.BobAccAddress.String(), + Id: 0, + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{2}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 6, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrAuthenticatorNotFound.ABCICode(), + ExpectedLog: aptypes.ErrAuthenticatorNotFound.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn rejected by signature verification authenticator": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "SignatureVerification", + // Allow signature verification using Alice's public key. + Data: constants.AlicePrivateKey.PubKey().Bytes(), + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrSignatureVerification.ABCICode(), + ExpectedLog: aptypes.ErrSignatureVerification.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn rejected by message filter authenticator": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "MessageFilter", + Data: []byte("/cosmos.bank.v1beta1.MsgSend"), + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrMessageTypeVerification.ABCICode(), + ExpectedLog: aptypes.ErrMessageTypeVerification.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn rejected by clob pair id filter authenticator": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "ClobPairIdFilter", + Data: []byte("0"), + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrClobPairIdVerification.ABCICode(), + ExpectedLog: aptypes.ErrClobPairIdVerification.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn rejected by subaccount number filter authenticator": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "SubaccountFilter", + Data: []byte("1"), + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrSubaccountVerification.ABCICode(), + ExpectedLog: aptypes.ErrSubaccountVerification.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn rejected by all of authenticator": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "AllOf", + Data: compositeAuthenticatorConfig, + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrAllOfVerification.ABCICode(), + ExpectedLog: aptypes.ErrAllOfVerification.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "Txn rejected by any of authenticator": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "AnyOf", + Data: compositeAuthenticatorConfig, + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrAnyOfVerification.ABCICode(), + ExpectedLog: aptypes.ErrAnyOfVerification.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + "One of the messages in the transaction is rejected": { + smartAccountEnabled: true, + blocks: []TestBlockWithMsgs{ + { + Block: 2, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &aptypes.MsgAddAuthenticator{ + Sender: constants.BobAccAddress.String(), + AuthenticatorType: "MessageFilter", + Data: []byte("/cosmos.bank.v1beta1.MsgSend"), + }, + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{1}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 4, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + &banktypes.MsgSend{ + FromAddress: constants.BobAccAddress.String(), + ToAddress: constants.AliceAccAddress.String(), + Amount: sdk.Coins{sdk.Coin{ + Denom: "foo", + Amount: sdkmath.OneInt(), + }}, + }, + &sendingtypes.MsgCreateTransfer{ + Transfer: &sendingtypes.Transfer{ + Sender: constants.Bob_Num0, + Recipient: constants.Alice_Num0, + AssetId: assettypes.AssetUsdc.Id, + Amount: 500_000_000, // $500 + }, + }, + }, + Authenticators: []uint64{0, 0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 300_000, + AccountNum: []uint64{1}, + SeqNum: []uint64{2}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: aptypes.ErrMessageTypeVerification.ABCICode(), + ExpectedLog: aptypes.ErrMessageTypeVerification.Error(), + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + genesis = testapp.DefaultGenesis() + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *aptypes.GenesisState) { + genesisState.Params.IsSmartAccountActive = tc.smartAccountEnabled + }, + ) + return genesis + }).Build() + ctx := tApp.InitChain() + + for _, block := range tc.blocks { + for _, msg := range block.Msgs { + tx, err := testtx.GenTx( + ctx, + tApp.App.TxConfig(), + msg.Msg, + msg.Fees, + msg.Gas, + tApp.App.ChainID(), + msg.AccountNum, + msg.SeqNum, + msg.Signers, + msg.Signers, + msg.Authenticators, + ) + require.NoError(t, err) + + bytes, err := tApp.App.TxConfig().TxEncoder()(tx) + if err != nil { + panic(err) + } + checkTxReq := abcitypes.RequestCheckTx{ + Tx: bytes, + Type: abcitypes.CheckTxType_New, + } + + resp := tApp.CheckTx(checkTxReq) + require.Equal( + t, + msg.ExpectedRespCode, + resp.Code, + "Response code was not as expected", + ) + require.Contains( + t, + resp.Log, + msg.ExpectedLog, + "Response log was not as expected", + ) + } + ctx = tApp.AdvanceToBlock(block.Block, testapp.AdvanceToBlockOptions{}) + } + + for orderId, shouldHaveOrder := range tc.expectedOrderIdsInMemclob { + _, exists := tApp.App.ClobKeeper.MemClob.GetOrder(orderId) + require.Equal(t, shouldHaveOrder, exists) + } + }) + } +}