diff --git a/protocol/app/ante.go b/protocol/app/ante.go index b00c48e157..156d245138 100644 --- a/protocol/app/ante.go +++ b/protocol/app/ante.go @@ -124,15 +124,22 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { ), sigVerification: accountplusante.NewCircuitBreakerDecorator( options.Codec, - accountplusante.NewAuthenticatorDecorator( - options.Codec, - options.AccountplusKeeper, - options.AccountKeeper, - options.SignModeHandler, + sdk.ChainAnteDecorators( + customante.NewEmitPubKeyEventsDecorator(), + accountplusante.NewAuthenticatorDecorator( + options.Codec, + options.AccountplusKeeper, + options.AccountKeeper, + options.SignModeHandler, + ), ), - customante.NewSigVerificationDecorator( - options.AccountKeeper, - options.SignModeHandler, + sdk.ChainAnteDecorators( + ante.NewSetPubKeyDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + customante.NewSigVerificationDecorator( + options.AccountKeeper, + options.SignModeHandler, + ), ), ), consumeTxSizeGas: ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), @@ -142,8 +149,6 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { options.FeegrantKeeper, options.TxFeeChecker, ), - setPubKey: ante.NewSetPubKeyDecorator(options.AccountKeeper), - sigGasConsume: ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), clobRateLimit: clobante.NewRateLimitDecorator(options.ClobKeeper), clob: clobante.NewClobDecorator(options.ClobKeeper), marketUpdates: customante.NewValidateMarketUpdateDecorator( @@ -175,8 +180,6 @@ type lockingAnteHandler struct { sigVerification accountplusante.CircuitBreakerDecorator consumeTxSizeGas ante.ConsumeTxSizeGasDecorator deductFee ante.DeductFeeDecorator - setPubKey ante.SetPubKeyDecorator - sigGasConsume ante.SigGasConsumeDecorator clobRateLimit clobante.ClobRateLimitDecorator clob clobante.ClobDecorator marketUpdates customante.ValidateMarketUpdateDecorator @@ -252,15 +255,9 @@ func (h *lockingAnteHandler) clobAnteHandle(ctx sdk.Context, tx sdk.Tx, simulate if ctx, err = h.consumeTxSizeGas.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { return ctx, err } - if ctx, err = h.setPubKey.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { - return ctx, err - } if ctx, err = h.validateSigCount.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { return ctx, err } - if ctx, err = h.sigGasConsume.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { - return ctx, err - } if ctx, err = h.replayProtection.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { return ctx, err } @@ -422,15 +419,9 @@ func (h *lockingAnteHandler) otherMsgAnteHandle(ctx sdk.Context, tx sdk.Tx, simu if ctx, err = h.deductFee.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { return ctx, err } - if ctx, err = h.setPubKey.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { - return ctx, err - } if ctx, err = h.validateSigCount.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { return ctx, err } - if ctx, err = h.sigGasConsume.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { - return ctx, err - } if ctx, err = h.replayProtection.AnteHandle(ctx, tx, simulate, noOpAnteHandle); err != nil { return ctx, err } diff --git a/protocol/app/ante/pubkey.go b/protocol/app/ante/pubkey.go new file mode 100644 index 0000000000..7dbe315e3c --- /dev/null +++ b/protocol/app/ante/pubkey.go @@ -0,0 +1,110 @@ +package ante + +import ( + "encoding/base64" + "fmt" + + errorsmod "cosmossdk.io/errors" + + 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/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +// NewEmitPubKeyEventsDecorator emits events for each signer's public key. +// CONTRACT: Tx must implement SigVerifiableTx interface +type EmitPubKeyEventsDecorator struct{} + +func NewEmitPubKeyEventsDecorator() EmitPubKeyEventsDecorator { + return EmitPubKeyEventsDecorator{} +} + +func (eped EmitPubKeyEventsDecorator) AnteHandle( + ctx sdk.Context, + tx sdk.Tx, + simulate bool, + next sdk.AnteHandler, +) (sdk.Context, error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + + signers, err := sigTx.GetSigners() + if err != nil { + return ctx, err + } + + signerStrs := make([]string, len(signers)) + + // Also emit the following events, so that txs can be indexed by these + // indices: + // - signature (via `tx.signature=''`), + // - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`). + sigs, err := sigTx.GetSignaturesV2() + if err != nil { + return ctx, err + } + + var events sdk.Events + for i, sig := range sigs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signerStrs[i], sig.Sequence)), + )) + + sigBzs, err := signatureDataToBz(sig.Data) + if err != nil { + return ctx, err + } + for _, sigBz := range sigBzs { + events = append(events, sdk.NewEvent(sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)), + )) + } + } + + ctx.EventManager().EmitEvents(events) + + return next(ctx, tx, simulate) +} + +// signatureDataToBz converts a SignatureData into raw bytes signature. +// For SingleSignatureData, it returns the signature raw bytes. +// For MultiSignatureData, it returns an array of all individual signatures, +// as well as the aggregated signature. +func signatureDataToBz(data signing.SignatureData) ([][]byte, error) { + if data == nil { + return nil, fmt.Errorf("got empty SignatureData") + } + + switch data := data.(type) { + case *signing.SingleSignatureData: + return [][]byte{data.Signature}, nil + case *signing.MultiSignatureData: + sigs := [][]byte{} + var err error + + for _, d := range data.Signatures { + nestedSigs, err := signatureDataToBz(d) + if err != nil { + return nil, err + } + sigs = append(sigs, nestedSigs...) + } + + multiSignature := cryptotypes.MultiSignature{ + Signatures: sigs, + } + aggregatedSig, err := multiSignature.Marshal() + if err != nil { + return nil, err + } + sigs = append(sigs, aggregatedSig) + + return sigs, nil + default: + return nil, sdkerrors.ErrInvalidType.Wrapf("unexpected signature data type %T", data) + } +} diff --git a/protocol/app/app.go b/protocol/app/app.go index 4e974025f4..6b33dddc7a 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1112,6 +1112,28 @@ func New( app.SubaccountsKeeper, ) + // Initialize authenticators + app.AuthenticatorManager = authenticator.NewAuthenticatorManager() + 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], + app.AuthenticatorManager, + []string{ + lib.GovModuleAddress.String(), + }, + ) + accountplusModule := accountplusmodule.NewAppModule(appCodec, app.AccountPlusKeeper) + clobFlags := clobflags.GetClobFlagValuesFromOptions(appOpts) logger.Info("Parsed CLOB flags", "Flags", clobFlags) @@ -1231,28 +1253,6 @@ func New( app.VaultKeeper, ) - // Initialize authenticators - app.AuthenticatorManager = authenticator.NewAuthenticatorManager() - 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], - app.AuthenticatorManager, - []string{ - lib.GovModuleAddress.String(), - }, - ) - accountplusModule := accountplusmodule.NewAppModule(appCodec, app.AccountPlusKeeper) - /**** Module Options ****/ // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment diff --git a/protocol/testutil/constants/stateful_orders.go b/protocol/testutil/constants/stateful_orders.go index 3c9450ee2b..3e0eb8a80d 100644 --- a/protocol/testutil/constants/stateful_orders.go +++ b/protocol/testutil/constants/stateful_orders.go @@ -294,6 +294,18 @@ var ( Subticks: 30, GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, } + LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10 = clobtypes.Order{ + OrderId: clobtypes.OrderId{ + SubaccountId: Bob_Num0, + ClientId: 0, + OrderFlags: clobtypes.OrderIdFlags_LongTerm, + ClobPairId: 1, + }, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 25, + Subticks: 30, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, + } LongTermOrder_Bob_Num0_Id0_Clob0_Buy35_Price30_GTBT11 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Bob_Num0, diff --git a/protocol/x/accountplus/ante/circuit_breaker.go b/protocol/x/accountplus/ante/circuit_breaker.go index 95eebb429a..0d02c52612 100644 --- a/protocol/x/accountplus/ante/circuit_breaker.go +++ b/protocol/x/accountplus/ante/circuit_breaker.go @@ -11,20 +11,20 @@ import ( // the existence of `TxExtension`. type CircuitBreakerDecorator struct { cdc codec.BinaryCodec - authenticatorAnteHandlerFlow sdk.AnteDecorator - originalAnteHandlerFlow sdk.AnteDecorator + authenticatorAnteHandlerFlow sdk.AnteHandler + defaultAnteHandlerFlow sdk.AnteHandler } // NewCircuitBreakerDecorator creates a new instance of CircuitBreakerDecorator with the provided parameters. func NewCircuitBreakerDecorator( cdc codec.BinaryCodec, - auth sdk.AnteDecorator, - classic sdk.AnteDecorator, + authenticatorAnteHandlerFlow sdk.AnteHandler, + defaultAnteHandlerFlow sdk.AnteHandler, ) CircuitBreakerDecorator { return CircuitBreakerDecorator{ cdc: cdc, - authenticatorAnteHandlerFlow: auth, - originalAnteHandlerFlow: classic, + authenticatorAnteHandlerFlow: authenticatorAnteHandlerFlow, + defaultAnteHandlerFlow: defaultAnteHandlerFlow, } } @@ -44,9 +44,9 @@ func (ad CircuitBreakerDecorator) AnteHandle( // Check that the authenticator flow is active if specified, _ := lib.HasSelectedAuthenticatorTxExtensionSpecified(tx, ad.cdc); specified { // Return and call the AnteHandle function on all the authenticator decorators. - return ad.authenticatorAnteHandlerFlow.AnteHandle(ctx, tx, simulate, next) + return ad.authenticatorAnteHandlerFlow(ctx, tx, simulate) } // Return and call the AnteHandle function on all the original decorators. - return ad.originalAnteHandlerFlow.AnteHandle(ctx, tx, simulate, next) + return ad.defaultAnteHandlerFlow(ctx, tx, simulate) } diff --git a/protocol/x/accountplus/ante/circuit_breaker_test.go b/protocol/x/accountplus/ante/circuit_breaker_test.go index 3b554911c9..8b96f38951 100644 --- a/protocol/x/accountplus/ante/circuit_breaker_test.go +++ b/protocol/x/accountplus/ante/circuit_breaker_test.go @@ -151,8 +151,8 @@ func (s *AuthenticatorCircuitBreakerAnteSuite) TestCircuitBreakerAnte() { // Create a CircuitBreaker AnteDecorator cbd := ante.NewCircuitBreakerDecorator( s.tApp.App.AppCodec(), - mockTestAuthenticator, - mockTestClassic, + sdk.ChainAnteDecorators(mockTestAuthenticator), + sdk.ChainAnteDecorators(mockTestClassic), ) anteHandler := sdk.ChainAnteDecorators(cbd) diff --git a/protocol/x/accountplus/authenticator/signature_authenticator.go b/protocol/x/accountplus/authenticator/signature_authenticator.go index 9e7d759cd9..4abe84003a 100644 --- a/protocol/x/accountplus/authenticator/signature_authenticator.go +++ b/protocol/x/accountplus/authenticator/signature_authenticator.go @@ -56,6 +56,11 @@ func (sva SignatureVerification) Initialize(config []byte) (types.Authenticator, // Authenticate takes a SignaturesVerificationData struct and validates // each signer and signature using signature verification func (sva SignatureVerification) Authenticate(ctx sdk.Context, request types.AuthenticationRequest) error { + // First consume gas for verifying the signature + params := sva.ak.GetParams(ctx) + ctx.GasMeter().ConsumeGas(params.SigVerifyCostSecp256k1, "secp256k1 signature verification") + + // after gas consumption continue to verify signatures if request.Simulate || ctx.IsReCheckTx() { return nil } diff --git a/protocol/x/affiliates/e2e/register_affiliate_test.go b/protocol/x/affiliates/e2e/register_affiliate_test.go index 5408d56b02..56753ae1c9 100644 --- a/protocol/x/affiliates/e2e/register_affiliate_test.go +++ b/protocol/x/affiliates/e2e/register_affiliate_test.go @@ -10,9 +10,6 @@ import ( ) func TestRegisterAffiliateInvalidSigner(t *testing.T) { - tApp := testapp.NewTestAppBuilder(t).Build() - ctx := tApp.InitChain() - testCases := []struct { name string referee string @@ -45,6 +42,9 @@ func TestRegisterAffiliateInvalidSigner(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + msgRegisterAffiliate := types.MsgRegisterAffiliate{ Referee: tc.referee, Affiliate: tc.affiliate, diff --git a/protocol/x/clob/e2e/permissioned_keys_test.go b/protocol/x/clob/e2e/permissioned_keys_test.go index 18007beda5..934e43a5af 100644 --- a/protocol/x/clob/e2e/permissioned_keys_test.go +++ b/protocol/x/clob/e2e/permissioned_keys_test.go @@ -662,3 +662,758 @@ func TestPlaceOrder_PermissionedKeys_Failures(t *testing.T) { }) } } + +func TestPlaceOrder_PermissionedKeys_Success(t *testing.T) { + config := []aptypes.SubAuthenticatorInitData{ + { + Type: "SignatureVerification", + Config: constants.AlicePrivateKey.PubKey().Bytes(), + }, + { + Type: "MessageFilter", + Config: []byte("/dydxprotocol.clob.MsgPlaceOrder"), + }, + { + Type: "ClobPairIdFilter", + Config: []byte("0,1"), + }, + { + Type: "SubaccountFilter", + Config: []byte("0,1"), + }, + } + compositeAuthenticatorConfig, err := json.Marshal(config) + require.NoError(t, err) + + tests := map[string]struct { + smartAccountEnabled bool + blocks []TestBlockWithMsgs + + expectedOrderIdsInMemclob map[clobtypes.OrderId]bool + expectedOrderFillAmounts map[clobtypes.OrderId]uint64 + }{ + "Short term order placed via permissioned keys can be added to the orderbook": { + 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{0}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: true, + }, + }, + "Stateful order placed via permissioned keys can be added to the orderbook": { + 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.LongTermOrder_Bob_Num0_Id0_Clob0_Buy25_Price30_GTBT10, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{2}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.LongTermOrder_Bob_Num0_Id0_Clob0_Buy25_Price30_GTBT10.OrderId: true, + }, + }, + "Short term maker order placed via permissioned keys can be matched": { + 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{0}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 6, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC, + testapp.DefaultGenesis(), + ), + ), + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{0}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: false, + }, + expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: 5000, // full size of scaled orders + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: 5000, + }, + }, + "Stateful maker order placed via permissioned keys can be matched": { + 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.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{2}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 6, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC, + testapp.DefaultGenesis(), + ), + ), + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{0}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10.OrderId: true, + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: false, + }, + expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ + constants.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10.OrderId: 5000, + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: 5000, + }, + }, + "Short term taker order placed via permissioned keys can be matched": { + 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_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15, + testapp.DefaultGenesis(), + ), + ), + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{0}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + 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}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15.OrderId: false, + }, + expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: 5000, // full size of scaled orders + constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15.OrderId: 5000, + }, + }, + "Stateful taker order placed via permissioned keys can be matched": { + 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_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15, + testapp.DefaultGenesis(), + ), + ), + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{0}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 6, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{2}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10.OrderId: true, + constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15.OrderId: false, + }, + expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ + constants.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10.OrderId: 5000, + constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15.OrderId: 5000, + }, + }, + "Short term maker order is removed if permissioned key is removed": { + 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{0}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 6, + 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: 8, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC, + testapp.DefaultGenesis(), + ), + ), + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{0}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: false, + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: false, + }, + expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ + constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: 0, + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: 0, + }, + }, + "Stateful maker order can be matched even if permissioned key is removed": { + 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.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10, + testapp.DefaultGenesis(), + ), + ), + }, + Authenticators: []uint64{0}, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{1}, + SeqNum: []uint64{2}, + // Sign using Alice's private key. + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 6, + 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{3}, + Signers: []cryptotypes.PrivKey{constants.BobPrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + { + Block: 8, + Msgs: []TestSdkMsg{ + { + Msg: []sdk.Msg{ + clobtypes.NewMsgPlaceOrder( + testapp.MustScaleOrder( + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC, + testapp.DefaultGenesis(), + ), + ), + }, + + Fees: constants.TestFeeCoins_5Cents, + Gas: 0, + AccountNum: []uint64{0}, + SeqNum: []uint64{0}, + Signers: []cryptotypes.PrivKey{constants.AlicePrivateKey}, + + ExpectedRespCode: 0, + }, + }, + }, + }, + expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ + constants.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10.OrderId: true, + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: false, + }, + expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ + constants.LongTermOrder_Bob_Num0_Id0_Clob1_Buy25_Price30_GTBT10.OrderId: 5000, + constants.Order_Alice_Num0_Id1_Clob1_Sell5_Price15_GTB20_IOC.OrderId: 5000, + }, + }, + } + + 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() + + lastBlockHeight := uint32(0) + 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{}) + lastBlockHeight = block.Block + } + + ctx = tApp.AdvanceToBlock(lastBlockHeight+2, testapp.AdvanceToBlockOptions{}) + + for orderId, shouldHaveOrder := range tc.expectedOrderIdsInMemclob { + _, exists := tApp.App.ClobKeeper.MemClob.GetOrder(orderId) + require.Equal(t, shouldHaveOrder, exists) + } + + for orderId, expectedFillAmount := range tc.expectedOrderFillAmounts { + _, fillAmount, _ := tApp.App.ClobKeeper.GetOrderFillAmount(ctx, orderId) + require.Equal(t, expectedFillAmount, fillAmount.ToUint64()) + } + }) + } +}