diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts index 93d034a144..045342f671 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts @@ -42,9 +42,17 @@ export class LCDQueryClient { if so which block they are re-enabled on. */ - async getWithdrawalAndTransfersBlockedInfo(_params: QueryGetWithdrawalAndTransfersBlockedInfoRequest = {}): Promise { + async getWithdrawalAndTransfersBlockedInfo(params: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise { + const options: any = { + params: {} + }; + + if (typeof params?.perpetualId !== "undefined") { + options.params.perpetual_id = params.perpetualId; + } + const endpoint = `dydxprotocol/subaccounts/withdrawals_and_transfers_blocked_info`; - return await this.req.get(endpoint); + return await this.req.get(endpoint, options); } } \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.rpc.Query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.rpc.Query.ts index 41f65d7652..1e76701fcf 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.rpc.Query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.rpc.Query.ts @@ -15,7 +15,7 @@ export interface Query { * if so which block they are re-enabled on. */ - getWithdrawalAndTransfersBlockedInfo(request?: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise; + getWithdrawalAndTransfersBlockedInfo(request: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise; } export class QueryClientImpl implements Query { private readonly rpc: Rpc; @@ -41,7 +41,7 @@ export class QueryClientImpl implements Query { return promise.then(data => QuerySubaccountAllResponse.decode(new _m0.Reader(data))); } - getWithdrawalAndTransfersBlockedInfo(request: QueryGetWithdrawalAndTransfersBlockedInfoRequest = {}): Promise { + getWithdrawalAndTransfersBlockedInfo(request: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise { const data = QueryGetWithdrawalAndTransfersBlockedInfoRequest.encode(request).finish(); const promise = this.rpc.request("dydxprotocol.subaccounts.Query", "GetWithdrawalAndTransfersBlockedInfo", data); return promise.then(data => QueryGetWithdrawalAndTransfersBlockedInfoResponse.decode(new _m0.Reader(data))); @@ -60,7 +60,7 @@ export const createRpcQueryExtension = (base: QueryClient) => { return queryService.subaccountAll(request); }, - getWithdrawalAndTransfersBlockedInfo(request?: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise { + getWithdrawalAndTransfersBlockedInfo(request: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise { return queryService.getWithdrawalAndTransfersBlockedInfo(request); } diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts index 0b1d182ed9..0464bd1359 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts @@ -48,16 +48,22 @@ export interface QuerySubaccountAllResponseSDKType { } /** * QueryGetWithdrawalAndTransfersBlockedInfoRequest is a request type for - * fetching information about whether withdrawals and transfers are blocked. + * fetching information about whether withdrawals and transfers are blocked for + * a collateral pool associated with the passed in perpetual id. */ -export interface QueryGetWithdrawalAndTransfersBlockedInfoRequest {} +export interface QueryGetWithdrawalAndTransfersBlockedInfoRequest { + perpetualId: number; +} /** * QueryGetWithdrawalAndTransfersBlockedInfoRequest is a request type for - * fetching information about whether withdrawals and transfers are blocked. + * fetching information about whether withdrawals and transfers are blocked for + * a collateral pool associated with the passed in perpetual id. */ -export interface QueryGetWithdrawalAndTransfersBlockedInfoRequestSDKType {} +export interface QueryGetWithdrawalAndTransfersBlockedInfoRequestSDKType { + perpetual_id: number; +} /** * QueryGetWithdrawalAndTransfersBlockedInfoRequest is a response type for * fetching information about whether withdrawals and transfers are blocked. @@ -280,11 +286,17 @@ export const QuerySubaccountAllResponse = { }; function createBaseQueryGetWithdrawalAndTransfersBlockedInfoRequest(): QueryGetWithdrawalAndTransfersBlockedInfoRequest { - return {}; + return { + perpetualId: 0 + }; } export const QueryGetWithdrawalAndTransfersBlockedInfoRequest = { - encode(_: QueryGetWithdrawalAndTransfersBlockedInfoRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + encode(message: QueryGetWithdrawalAndTransfersBlockedInfoRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.perpetualId !== 0) { + writer.uint32(8).uint32(message.perpetualId); + } + return writer; }, @@ -297,6 +309,10 @@ export const QueryGetWithdrawalAndTransfersBlockedInfoRequest = { const tag = reader.uint32(); switch (tag >>> 3) { + case 1: + message.perpetualId = reader.uint32(); + break; + default: reader.skipType(tag & 7); break; @@ -306,8 +322,9 @@ export const QueryGetWithdrawalAndTransfersBlockedInfoRequest = { return message; }, - fromPartial(_: DeepPartial): QueryGetWithdrawalAndTransfersBlockedInfoRequest { + fromPartial(object: DeepPartial): QueryGetWithdrawalAndTransfersBlockedInfoRequest { const message = createBaseQueryGetWithdrawalAndTransfersBlockedInfoRequest(); + message.perpetualId = object.perpetualId ?? 0; return message; } diff --git a/proto/dydxprotocol/subaccounts/query.proto b/proto/dydxprotocol/subaccounts/query.proto index 740d50977a..7533366d0f 100644 --- a/proto/dydxprotocol/subaccounts/query.proto +++ b/proto/dydxprotocol/subaccounts/query.proto @@ -56,8 +56,11 @@ message QuerySubaccountAllResponse { } // QueryGetWithdrawalAndTransfersBlockedInfoRequest is a request type for -// fetching information about whether withdrawals and transfers are blocked. -message QueryGetWithdrawalAndTransfersBlockedInfoRequest {} +// fetching information about whether withdrawals and transfers are blocked for +// a collateral pool associated with the passed in perpetual id. +message QueryGetWithdrawalAndTransfersBlockedInfoRequest { + uint32 perpetual_id = 1; +} // QueryGetWithdrawalAndTransfersBlockedInfoRequest is a response type for // fetching information about whether withdrawals and transfers are blocked. diff --git a/protocol/testutil/constants/addresses.go b/protocol/testutil/constants/addresses.go index d4987bad8a..259a548a3e 100644 --- a/protocol/testutil/constants/addresses.go +++ b/protocol/testutil/constants/addresses.go @@ -2,6 +2,8 @@ package constants import ( sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) var ( @@ -17,4 +19,8 @@ var ( BobConsAddress = sdk.ConsAddress(BobPrivateKey.PubKey().Address()) CarlConsAddress = sdk.ConsAddress(CarlPrivateKey.PubKey().Address()) DaveConsAddress = sdk.ConsAddress(DavePrivateKey.PubKey().Address()) + + // Collateral pool addresses for isolated perpetuals. + IsoCollateralPoolAddress = authtypes.NewModuleAddress(satypes.ModuleName + ":3") + Iso2CollateralPoolAddress = authtypes.NewModuleAddress(satypes.ModuleName + ":4") ) diff --git a/protocol/testutil/constants/clob_pair.go b/protocol/testutil/constants/clob_pair.go index 10b42e79bf..15c4c5b669 100644 --- a/protocol/testutil/constants/clob_pair.go +++ b/protocol/testutil/constants/clob_pair.go @@ -127,4 +127,40 @@ var ( QuantumConversionExponent: -8, Status: clobtypes.ClobPair_STATUS_PAUSED, } + ClobPair_3_Iso = clobtypes.ClobPair{ + Id: 3, + Metadata: &clobtypes.ClobPair_PerpetualClobMetadata{ + PerpetualClobMetadata: &clobtypes.PerpetualClobMetadata{ + PerpetualId: 3, + }, + }, + StepBaseQuantums: 5, + SubticksPerTick: 5, + QuantumConversionExponent: -8, + Status: clobtypes.ClobPair_STATUS_ACTIVE, + } + ClobPair_3_Iso_Final_Settlement = clobtypes.ClobPair{ + Id: 3, + Metadata: &clobtypes.ClobPair_PerpetualClobMetadata{ + PerpetualClobMetadata: &clobtypes.PerpetualClobMetadata{ + PerpetualId: 3, + }, + }, + StepBaseQuantums: 5, + SubticksPerTick: 5, + QuantumConversionExponent: -8, + Status: clobtypes.ClobPair_STATUS_FINAL_SETTLEMENT, + } + ClobPair_4_Iso2 = clobtypes.ClobPair{ + Id: 4, + Metadata: &clobtypes.ClobPair_PerpetualClobMetadata{ + PerpetualClobMetadata: &clobtypes.PerpetualClobMetadata{ + PerpetualId: 4, + }, + }, + StepBaseQuantums: 5, + SubticksPerTick: 5, + QuantumConversionExponent: -8, + Status: clobtypes.ClobPair_STATUS_ACTIVE, + } ) diff --git a/protocol/testutil/constants/subaccounts.go b/protocol/testutil/constants/subaccounts.go index e1613b3fe3..46532aeede 100644 --- a/protocol/testutil/constants/subaccounts.go +++ b/protocol/testutil/constants/subaccounts.go @@ -286,6 +286,22 @@ var ( }, }, } + Carl_Num0_1ISO_Short_49USD = satypes.Subaccount{ + Id: &Carl_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(49_000_000), // $49 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 3, + Quantums: dtypes.NewInt(-1_000_000_000), // -1 ISO + FundingIndex: dtypes.NewInt(0), + }, + }, + } Carl_Num0_599USD = satypes.Subaccount{ Id: &Carl_Num0, AssetPositions: []*satypes.AssetPosition{ @@ -518,6 +534,22 @@ var ( }, }, } + Dave_Num0_1ISO2_Short_499USD = satypes.Subaccount{ + Id: &Dave_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(499_000_000), // $499 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 4, + Quantums: dtypes.NewInt(-10_000_000), // -1 ISO2 + FundingIndex: dtypes.NewInt(0), + }, + }, + } Dave_Num0_599USD = satypes.Subaccount{ Id: &Dave_Num0, AssetPositions: []*satypes.AssetPosition{ diff --git a/protocol/x/clob/e2e/withdrawal_gating_test.go b/protocol/x/clob/e2e/withdrawal_gating_test.go index a69101ff45..762624807d 100644 --- a/protocol/x/clob/e2e/withdrawal_gating_test.go +++ b/protocol/x/clob/e2e/withdrawal_gating_test.go @@ -252,7 +252,11 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) tApp.App.SubaccountsKeeper.GetSubaccount(ctx, *expectedSubaccount.Id), ) } - negativeTncSubaccountSeenAtBlock, exists := tApp.App.SubaccountsKeeper.GetNegativeTncSubaccountSeenAtBlock(ctx) + negativeTncSubaccountSeenAtBlock, exists, err := tApp.App.SubaccountsKeeper.GetNegativeTncSubaccountSeenAtBlock( + ctx, + constants.BtcUsd_NoMarginRequirement.Params.Id, + ) + require.NoError(t, err) require.Equal(t, tc.expectedWithdrawalsGated, exists) require.Equal(t, tc.expectedNegativeTncSubaccountSeenAtBlock, negativeTncSubaccountSeenAtBlock) diff --git a/protocol/x/clob/keeper/process_operations.go b/protocol/x/clob/keeper/process_operations.go index 1d0445a7c9..0b67620210 100644 --- a/protocol/x/clob/keeper/process_operations.go +++ b/protocol/x/clob/keeper/process_operations.go @@ -694,7 +694,13 @@ func (k Keeper) PersistMatchDeleveragingToState( metrics.GetLabelForBoolValue(metrics.IsLong, position.GetIsLong()), metrics.GetLabelForBoolValue(metrics.DeliverTx, true), ) - k.subaccountsKeeper.SetNegativeTncSubaccountSeenAtBlock(ctx, lib.MustConvertIntegerToUint32(ctx.BlockHeight())) + if err = k.subaccountsKeeper.SetNegativeTncSubaccountSeenAtBlock( + ctx, + perpetualId, + lib.MustConvertIntegerToUint32(ctx.BlockHeight()), + ); err != nil { + return err + } return nil } diff --git a/protocol/x/clob/keeper/process_operations_test.go b/protocol/x/clob/keeper/process_operations_test.go index 69d6a1d42d..6d1e0072fb 100644 --- a/protocol/x/clob/keeper/process_operations_test.go +++ b/protocol/x/clob/keeper/process_operations_test.go @@ -67,7 +67,7 @@ type processProposerOperationsTestCase struct { expectedQuoteBalances map[satypes.SubaccountId]int64 expectedPerpetualPositions map[satypes.SubaccountId][]*satypes.PerpetualPosition expectedSubaccountLiquidationInfo map[satypes.SubaccountId]types.SubaccountLiquidationInfo - expectedNegativeTncSubaccountSeen bool + expectedNegativeTncSubaccountSeen map[uint32]bool expectedError error expectedPanics string } @@ -958,7 +958,9 @@ func TestProcessProposerOperations(t *testing.T) { expectedPerpetualPositions: map[satypes.SubaccountId][]*satypes.PerpetualPosition{ constants.Carl_Num0: constants.Carl_Num0_1BTC_Short_50499USD.GetPerpetualPositions(), }, - expectedNegativeTncSubaccountSeen: true, + expectedNegativeTncSubaccountSeen: map[uint32]bool{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance.Params.Id: true, + }, }, "Zero-fill deleveraging succeeds when the account is negative TNC and has a position in final settlement" + " market. It updates the last negative TNC subaccount seen block number in state": { @@ -997,8 +999,9 @@ func TestProcessProposerOperations(t *testing.T) { constants.Carl_Num0: constants.Carl_Num0_1BTC_Short_50499USD.GetPerpetualPositions(), constants.Dave_Num0: constants.Dave_Num0_1BTC_Long_50000USD.GetPerpetualPositions(), }, - - expectedNegativeTncSubaccountSeen: true, + expectedNegativeTncSubaccountSeen: map[uint32]bool{ + constants.BtcUsd_100PercentMarginRequirement.Params.Id: true, + }, }, "Zero-fill deleveraging succeeds when there's multiple zero-fill deleveraging events for the same subaccount " + "and perpetual ID": { @@ -1042,7 +1045,9 @@ func TestProcessProposerOperations(t *testing.T) { expectedPerpetualPositions: map[satypes.SubaccountId][]*satypes.PerpetualPosition{ constants.Carl_Num0: constants.Carl_Num0_1BTC_Short_50499USD.GetPerpetualPositions(), }, - expectedNegativeTncSubaccountSeen: true, + expectedNegativeTncSubaccountSeen: map[uint32]bool{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance.Params.Id: true, + }, }, "Zero-fill deleverage succeeds after the same subaccount is partially deleveraged": { perpetuals: []perptypes.Perpetual{ @@ -1104,7 +1109,153 @@ func TestProcessProposerOperations(t *testing.T) { }, }, }, - expectedNegativeTncSubaccountSeen: true, + expectedNegativeTncSubaccountSeen: map[uint32]bool{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance.Params.Id: true, + }, + }, + "Zero-fill deleveraging succeeds when the account is negative TNC and updates the last negative TNC subaccount " + + "seen block number in state for an isolated perpetual collateral pool if the subaccount is isolated to the " + + "isolated perpetual": { + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_NoMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualFeeParams: &constants.PerpetualFeeParams, + clobPairs: []types.ClobPair{ + constants.ClobPair_3_Iso, + }, + subaccounts: []satypes.Subaccount{ + // deleverageable since TNC = -$1 + constants.Carl_Num0_1ISO_Short_49USD, + }, + marketIdToOraclePriceOverride: map[uint32]uint64{ + constants.IsoUsd_IsolatedMarket.Params.MarketId: 5_000_000_000, // $50 / ISO + }, + rawOperations: []types.OperationRaw{ + clobtest.NewMatchOperationRawFromPerpetualDeleveragingLiquidation( + types.MatchPerpetualDeleveraging{ + Liquidated: constants.Carl_Num0, + PerpetualId: 3, + Fills: []types.MatchPerpetualDeleveraging_Fill{}, + }, + ), + }, + + expectedProcessProposerMatchesEvents: types.ProcessProposerMatchesEvents{ + BlockHeight: blockHeight, + }, + expectedQuoteBalances: map[satypes.SubaccountId]int64{ + constants.Carl_Num0: constants.Carl_Num0_1ISO_Short_49USD.GetUsdcPosition().Int64(), + }, + expectedPerpetualPositions: map[satypes.SubaccountId][]*satypes.PerpetualPosition{ + constants.Carl_Num0: constants.Carl_Num0_1ISO_Short_49USD.GetPerpetualPositions(), + }, + expectedNegativeTncSubaccountSeen: map[uint32]bool{ + constants.BtcUsd_NoMarginRequirement.Params.Id: false, + constants.IsoUsd_IsolatedMarket.Params.Id: true, + }, + }, + "Zero-fill deleveraging succeeds when the account is negative TNC and has a position in final settlement" + + " market. It updates the last negative TNC subaccount seen block number in state for an isolated perpetual" + + " collateral pool if the subaccount is isolated to the isolated perpetual": { + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_100PercentMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualFeeParams: &constants.PerpetualFeeParams, + clobPairs: []types.ClobPair{ + constants.ClobPair_Btc, + constants.ClobPair_3_Iso_Final_Settlement, + }, + subaccounts: []satypes.Subaccount{ + // deleveragable: TNC = -$1. + constants.Carl_Num0_1ISO_Short_49USD, + constants.Dave_Num0_1BTC_Long_50000USD, + }, + marketIdToOraclePriceOverride: map[uint32]uint64{ + constants.IsoUsd_IsolatedMarket.Params.MarketId: 5_000_000_000, // $50 / ISO + }, + rawOperations: []types.OperationRaw{ + clobtest.NewMatchOperationRawFromPerpetualDeleveragingLiquidation( + types.MatchPerpetualDeleveraging{ + Liquidated: constants.Carl_Num0, + PerpetualId: 3, + Fills: []types.MatchPerpetualDeleveraging_Fill{}, + }, + ), + }, + expectedProcessProposerMatchesEvents: types.ProcessProposerMatchesEvents{ + BlockHeight: blockHeight, + }, + expectedQuoteBalances: map[satypes.SubaccountId]int64{ + constants.Carl_Num0: constants.Carl_Num0_1ISO_Short_49USD.GetUsdcPosition().Int64(), + constants.Dave_Num0: constants.Dave_Num0_1BTC_Long_50000USD.GetUsdcPosition().Int64(), + }, + expectedPerpetualPositions: map[satypes.SubaccountId][]*satypes.PerpetualPosition{ + constants.Carl_Num0: constants.Carl_Num0_1ISO_Short_49USD.GetPerpetualPositions(), + constants.Dave_Num0: constants.Dave_Num0_1BTC_Long_50000USD.GetPerpetualPositions(), + }, + expectedNegativeTncSubaccountSeen: map[uint32]bool{ + constants.BtcUsd_NoMarginRequirement.Params.Id: false, + constants.IsoUsd_IsolatedMarket.Params.Id: true, + }, + }, + "Zero-fill deleveraging succeeds when there's multiple zero-fill deleveraging events for the different subaccount " + + "and perpetual ID. It updates the last negative TNC subaccount seen block number in state for both isolated " + + "perpetual collateral pools if the subaccounts are isolated to different isolated perpetuals": { + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_NoMarginRequirement, + constants.IsoUsd_IsolatedMarket, + constants.Iso2Usd_IsolatedMarket, + }, + perpetualFeeParams: &constants.PerpetualFeeParams, + clobPairs: []types.ClobPair{ + constants.ClobPair_3_Iso, + constants.ClobPair_4_Iso2, + }, + subaccounts: []satypes.Subaccount{ + // deleverageable since TNC = -$1 + constants.Carl_Num0_1ISO_Short_49USD, + // deleverageable since TNC = -$1 + constants.Dave_Num0_1ISO2_Short_499USD, + }, + marketIdToOraclePriceOverride: map[uint32]uint64{ + constants.IsoUsd_IsolatedMarket.Params.MarketId: 5_000_000_000, // $50 / ISO + constants.Iso2Usd_IsolatedMarket.Params.MarketId: 5_000_000_000, // $500 / ISO2 + }, + rawOperations: []types.OperationRaw{ + clobtest.NewMatchOperationRawFromPerpetualDeleveragingLiquidation( + types.MatchPerpetualDeleveraging{ + Liquidated: constants.Carl_Num0, + PerpetualId: 3, + Fills: []types.MatchPerpetualDeleveraging_Fill{}, + }, + ), + clobtest.NewMatchOperationRawFromPerpetualDeleveragingLiquidation( + types.MatchPerpetualDeleveraging{ + Liquidated: constants.Dave_Num0, + PerpetualId: 4, + Fills: []types.MatchPerpetualDeleveraging_Fill{}, + }, + ), + }, + + expectedProcessProposerMatchesEvents: types.ProcessProposerMatchesEvents{ + BlockHeight: blockHeight, + }, + expectedQuoteBalances: map[satypes.SubaccountId]int64{ + constants.Carl_Num0: constants.Carl_Num0_1ISO_Short_49USD.GetUsdcPosition().Int64(), + constants.Dave_Num0: constants.Dave_Num0_1ISO2_Short_499USD.GetUsdcPosition().Int64(), + }, + expectedPerpetualPositions: map[satypes.SubaccountId][]*satypes.PerpetualPosition{ + constants.Carl_Num0: constants.Carl_Num0_1ISO_Short_49USD.GetPerpetualPositions(), + constants.Dave_Num0: constants.Dave_Num0_1ISO2_Short_499USD.GetPerpetualPositions(), + }, + expectedNegativeTncSubaccountSeen: map[uint32]bool{ + constants.BtcUsd_NoMarginRequirement.Params.Id: false, + constants.IsoUsd_IsolatedMarket.Params.Id: true, + constants.Iso2Usd_IsolatedMarket.Params.Id: true, + }, }, "Succeeds order removal operations with previous stateful orders": { perpetuals: []perptypes.Perpetual{ @@ -2404,14 +2555,20 @@ func runProcessProposerOperationsTestCase( require.Equal(t, fillAmount, actualFillAmount) } - // Verify the negative TNC subaccount seen block. - seenNegativeTncSubaccountBlock, exists := ks.SubaccountsKeeper.GetNegativeTncSubaccountSeenAtBlock(ctx) - if tc.expectedNegativeTncSubaccountSeen { - require.True(t, exists) - require.Equal(t, uint32(ctx.BlockHeight()), seenNegativeTncSubaccountBlock) - } else { - require.False(t, exists) - require.Equal(t, uint32(0), seenNegativeTncSubaccountBlock) + for perpetualId, expectedNegativeTncSubaccountSeen := range tc.expectedNegativeTncSubaccountSeen { + // Verify the negative TNC subaccount seen block. + seenNegativeTncSubaccountBlock, exists, err := ks.SubaccountsKeeper.GetNegativeTncSubaccountSeenAtBlock( + ctx, + perpetualId, + ) + require.NoError(t, err) + if expectedNegativeTncSubaccountSeen { + require.True(t, exists) + require.Equal(t, uint32(ctx.BlockHeight()), seenNegativeTncSubaccountBlock) + } else { + require.False(t, exists) + require.Equal(t, uint32(0), seenNegativeTncSubaccountBlock) + } } mockIndexerEventManager.AssertExpectations(t) diff --git a/protocol/x/clob/types/expected_keepers.go b/protocol/x/clob/types/expected_keepers.go index 3c4c8b86a2..a79e87f4a4 100644 --- a/protocol/x/clob/types/expected_keepers.go +++ b/protocol/x/clob/types/expected_keepers.go @@ -61,14 +61,19 @@ type SubaccountsKeeper interface { ) SetNegativeTncSubaccountSeenAtBlock( ctx sdk.Context, + perpetualId uint32, blockHeight uint32, - ) + ) error TransferFeesToFeeCollectorModule(ctx sdk.Context, assetId uint32, amount *big.Int, perpetualId uint32) error TransferInsuranceFundPayments( ctx sdk.Context, amount *big.Int, perpetualId uint32, ) error + GetCollateralPoolFromPerpetualId( + ctx sdk.Context, + perpetualId uint32, + ) (sdk.AccAddress, error) } type AssetsKeeper interface { diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index bcaad7caff..651111ea5c 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -31,6 +31,11 @@ import ( gometrics "github.com/hashicorp/go-metrics" ) +func (k Keeper) IsIsolatedPerpetual(ctx sdk.Context, perpetualId uint32) (bool, error) { + insuranceFundName, err := k.GetInsuranceFundName(ctx, perpetualId) + return insuranceFundName == types.InsuranceFundName, err +} + // GetInsuranceFundName returns the name of the insurance fund account for a given perpetual. // For isolated markets, the name is "insurance-fund:". // For cross markets, the name is "insurance-fund". diff --git a/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info.go b/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info.go index eb0931e75b..a017d8b954 100644 --- a/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info.go +++ b/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info.go @@ -25,7 +25,13 @@ func (k Keeper) GetWithdrawalAndTransfersBlockedInfo( ) chainOutageSeenAtBlock, chainOutageExists := downtimeInfo.BlockInfo.Height, downtimeInfo.BlockInfo.Height > 0 && downtimeInfo.Duration > 0 - negativeTncSubaccountSeenAtBlock, negativeTncSubaccountSeenAtBlockExists := k.GetNegativeTncSubaccountSeenAtBlock(ctx) + negativeTncSubaccountSeenAtBlock, negativeTncSubaccountSeenAtBlockExists, err := k.GetNegativeTncSubaccountSeenAtBlock( + ctx, + req.PerpetualId, + ) + if err != nil { + return nil, err + } // Withdrawals and transfers are blocked at non-zero block iff a chain outage or negative TNC subaccount exists. withdrawalsAndTransfersBlockedUntilBlock := uint32(0) diff --git a/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info_test.go b/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info_test.go index 735eefcaa0..d04e94e939 100644 --- a/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info_test.go +++ b/protocol/x/subaccounts/keeper/grpc_query_withdrawal_and_transfers_blocked_info_test.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc/status" sdktypes "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" btkeeper "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/keeper" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" @@ -19,7 +20,7 @@ import ( func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { for testName, tc := range map[string]struct { // Setup. - setup func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) + setup func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error // Parameters. request *types.QueryGetWithdrawalAndTransfersBlockedInfoRequest @@ -29,15 +30,21 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { err error }{ "Nil request returns an error": { - setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) {}, + setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error { + return nil + }, err: status.Error(codes.InvalidArgument, "invalid request"), }, `No negative TNC subaccount or chain outage in state returns withdrawals and transfers unblocked at block 0`: { - setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) {}, + setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error { + return nil + }, - request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{}, + request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{ + PerpetualId: uint32(constants.BtcUsd_NoMarginRequirement.Params.Id), + }, response: &types.QueryGetWithdrawalAndTransfersBlockedInfoResponse{ NegativeTncSubaccountSeenAtBlock: 0, @@ -47,11 +54,17 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, `Negative TNC subaccount seen in state returns withdrawals and transfers unblocked after the delay`: { - setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) { - sk.SetNegativeTncSubaccountSeenAtBlock(ctx, 7) + setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error { + return sk.SetNegativeTncSubaccountSeenAtBlock( + ctx, + constants.BtcUsd_NoMarginRequirement.Params.Id, + 7, + ) }, - request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{}, + request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{ + PerpetualId: uint32(constants.BtcUsd_NoMarginRequirement.Params.Id), + }, response: &types.QueryGetWithdrawalAndTransfersBlockedInfoResponse{ NegativeTncSubaccountSeenAtBlock: 7, @@ -60,8 +73,29 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, }, }, + `Negative TNC subaccount seen in state returns withdrawals and transfers unblocked + after the delay (for isolated perpetual)`: { + setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error { + return sk.SetNegativeTncSubaccountSeenAtBlock( + ctx, + constants.IsoUsd_IsolatedMarket.Params.Id, + 5, + ) + }, + + request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{ + PerpetualId: uint32(constants.IsoUsd_IsolatedMarket.Params.Id), + }, + + response: &types.QueryGetWithdrawalAndTransfersBlockedInfoResponse{ + NegativeTncSubaccountSeenAtBlock: 5, + ChainOutageSeenAtBlock: 0, + WithdrawalsAndTransfersUnblockedAtBlock: 5 + + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + }, + }, `Chain outage seen in state returns withdrawals and transfers unblocked after the delay`: { - setup: func(ctx sdktypes.Context, k sakeeper.Keeper, bk btkeeper.Keeper) { + setup: func(ctx sdktypes.Context, k sakeeper.Keeper, bk btkeeper.Keeper) error { bk.SetAllDowntimeInfo( ctx, &blocktimetypes.AllDowntimeInfo{ @@ -82,9 +116,12 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, }, }) + return nil }, - request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{}, + request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{ + PerpetualId: constants.BtcUsd_NoMarginRequirement.Params.Id, + }, response: &types.QueryGetWithdrawalAndTransfersBlockedInfoResponse{ NegativeTncSubaccountSeenAtBlock: 0, @@ -95,8 +132,15 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, `Negative TNC subaccount and chain outage seen in state returns withdrawals and transfers unblocked after the max block number + delay (negative TNC subaccount block greater)`: { - setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) { - sk.SetNegativeTncSubaccountSeenAtBlock(ctx, 27) + setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error { + err := sk.SetNegativeTncSubaccountSeenAtBlock( + ctx, + constants.BtcUsd_NoMarginRequirement.Params.Id, + 27, + ) + if err != nil { + return nil + } bk.SetAllDowntimeInfo( ctx, &blocktimetypes.AllDowntimeInfo{ @@ -117,9 +161,12 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, }, }) + return nil }, - request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{}, + request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{ + PerpetualId: constants.BtcUsd_NoMarginRequirement.Params.Id, + }, response: &types.QueryGetWithdrawalAndTransfersBlockedInfoResponse{ NegativeTncSubaccountSeenAtBlock: 27, @@ -130,8 +177,15 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, `Negative TNC subaccount and chain outage seen in state returns withdrawals and transfers unblocked after the max block number + delay (chain outage block greater)`: { - setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) { - sk.SetNegativeTncSubaccountSeenAtBlock(ctx, 37) + setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error { + err := sk.SetNegativeTncSubaccountSeenAtBlock( + ctx, + constants.BtcUsd_NoMarginRequirement.Params.Id, + 37, + ) + if err != nil { + return err + } bk.SetAllDowntimeInfo( ctx, &blocktimetypes.AllDowntimeInfo{ @@ -152,9 +206,12 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, }, }) + return nil }, - request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{}, + request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{ + PerpetualId: constants.BtcUsd_NoMarginRequirement.Params.Id, + }, response: &types.QueryGetWithdrawalAndTransfersBlockedInfoResponse{ NegativeTncSubaccountSeenAtBlock: 37, @@ -165,8 +222,15 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, `Negative TNC subaccount and chain outage seen in state returns withdrawals and transfers unblocked after the max block number + delay (both blocks equal)`: { - setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) { - sk.SetNegativeTncSubaccountSeenAtBlock(ctx, 3) + setup: func(ctx sdktypes.Context, sk sakeeper.Keeper, bk btkeeper.Keeper) error { + err := sk.SetNegativeTncSubaccountSeenAtBlock( + ctx, + constants.BtcUsd_NoMarginRequirement.Params.Id, + 3, + ) + if err != nil { + return err + } bk.SetAllDowntimeInfo( ctx, &blocktimetypes.AllDowntimeInfo{ @@ -187,9 +251,12 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, }, }) + return nil }, - request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{}, + request: &types.QueryGetWithdrawalAndTransfersBlockedInfoRequest{ + PerpetualId: constants.BtcUsd_NoMarginRequirement.Params.Id, + }, response: &types.QueryGetWithdrawalAndTransfersBlockedInfoResponse{ NegativeTncSubaccountSeenAtBlock: 3, @@ -200,8 +267,12 @@ func TestQueryWithdrawalAndTransfersBlockedInfo(t *testing.T) { }, } { t.Run(testName, func(t *testing.T) { - ctx, keeper, _, _, _, _, _, blocktimeKeeper, _ := keepertest.SubaccountsKeepers(t, true) - tc.setup(ctx, *keeper, *blocktimeKeeper) + ctx, keeper, pricesKeeper, perpetualsKeeper, _, _, _, blocktimeKeeper, _ := keepertest.SubaccountsKeepers(t, true) + keepertest.CreateTestMarkets(t, ctx, pricesKeeper) + keepertest.CreateTestLiquidityTiers(t, ctx, perpetualsKeeper) + keepertest.CreateTestPerpetuals(t, ctx, perpetualsKeeper) + err := tc.setup(ctx, *keeper, *blocktimeKeeper) + require.NoError(t, err) response, err := keeper.GetWithdrawalAndTransfersBlockedInfo(ctx, tc.request) if tc.err != nil { require.ErrorIs(t, err, tc.err) diff --git a/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go b/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go index 7f52c54f91..04bc0867b3 100644 --- a/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go +++ b/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go @@ -3,28 +3,56 @@ package keeper import ( "fmt" + "cosmossdk.io/store/prefix" storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" gogotypes "github.com/cosmos/gogoproto/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) // GetNegativeTncSubaccountSeenAtBlock gets the last block height a negative TNC subaccount was -// seen in state and a boolean for whether it exists in state. +// seen in state for the given collateral pool address and a boolean for whether it exists in state. func (k Keeper) GetNegativeTncSubaccountSeenAtBlock( ctx sdk.Context, + perpetualId uint32, +) (uint32, bool, error) { + store := prefix.NewStore( + ctx.KVStore(k.storeKey), + []byte(types.NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix), + ) + + suffix, err := k.getNegativeTncSubaccountStoreSuffix(ctx, perpetualId) + if err != nil { + return 0, false, err + } + + blockHeight, exists := k.getNegativeTncSubaccountSeenAtBlock(store, suffix) + return blockHeight, exists, nil +} + +// Internal helper method to read the store using a store suffix. +func (k Keeper) getNegativeTncSubaccountSeenAtBlockWithSuffix( + ctx sdk.Context, + storeSuffix string, ) (uint32, bool) { - store := ctx.KVStore(k.storeKey) - return k.getNegativeTncSubaccountSeenAtBlock(store) + store := prefix.NewStore( + ctx.KVStore(k.storeKey), + []byte(types.NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix), + ) + + return k.getNegativeTncSubaccountSeenAtBlock(store, storeSuffix) } // getNegativeTncSubaccountSeenAtBlock is a helper function that takes a store and returns the last -// block height a negative TNC subaccount was seen in state and a boolean for whether it exists in state. +// block height a negative TNC subaccount was seen in state for the given collateral pool address +// and a boolean for whether it exists in state. func (k Keeper) getNegativeTncSubaccountSeenAtBlock( store storetypes.KVStore, + storeSuffix string, ) (uint32, bool) { b := store.Get( - []byte(types.NegativeTncSubaccountSeenAtBlockKey), + []byte(storeSuffix), ) blockHeight := gogotypes.UInt32Value{Value: 0} exists := false @@ -37,16 +65,25 @@ func (k Keeper) getNegativeTncSubaccountSeenAtBlock( } // SetNegativeTncSubaccountSeenAtBlock sets a block number in state where a negative TNC subaccount -// was seen. This function will overwrite previous values at this key. +// was seen for a specific collateral pool. This function will overwrite previous values at this key. // This function will panic if the old block height is greater than the new block height. func (k Keeper) SetNegativeTncSubaccountSeenAtBlock( ctx sdk.Context, + perpetualId uint32, blockHeight uint32, -) { - store := ctx.KVStore(k.storeKey) +) error { + store := prefix.NewStore( + ctx.KVStore(k.storeKey), + []byte(types.NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix), + ) + + storeSuffix, err := k.getNegativeTncSubaccountStoreSuffix(ctx, perpetualId) + if err != nil { + return err + } // Panic if the stored block height value exists and is greater than the new block height value. - currentValue, exists := k.getNegativeTncSubaccountSeenAtBlock(store) + currentValue, exists := k.getNegativeTncSubaccountSeenAtBlock(store, storeSuffix) if exists && blockHeight < currentValue { panic( fmt.Sprintf( @@ -59,7 +96,83 @@ func (k Keeper) SetNegativeTncSubaccountSeenAtBlock( blockHeightValue := gogotypes.UInt32Value{Value: blockHeight} store.Set( - []byte(types.NegativeTncSubaccountSeenAtBlockKey), + []byte(storeSuffix), k.cdc.MustMarshal(&blockHeightValue), ) + + return nil +} + +func (k Keeper) getNegativeTncSubaccountStoreSuffix( + ctx sdk.Context, + perpetualId uint32, +) (string, error) { + isIsolated, err := k.perpetualsKeeper.IsIsolatedPerpetual(ctx, perpetualId) + if err != nil { + return "", err + } + if isIsolated { + return types.CrossCollateralSuffix, nil + } else { + return lib.UintToString(perpetualId), nil + } +} + +// getNegativeTncSubaccountStoresuffixes gets a slice of negative tnc subaccount store suffixes for +// the subaccounts in the slice of `settledUpdate`s passed in. +// The slice will be de-duplicated and will contain unique store suffixes. +func (k Keeper) getNegativeTncSubaccountStoresuffixes( + ctx sdk.Context, + settledUpdates []SettledUpdate, +) ( + suffixes []string, + err error, +) { + suffixesMap := make(map[string]bool) + suffixes = make([]string, 0) + for _, u := range settledUpdates { + var suffix string + if len(u.SettledSubaccount.PerpetualPositions) == 0 { + suffix = types.CrossCollateralSuffix + } else { + suffix, err = k.getNegativeTncSubaccountStoreSuffix(ctx, u.SettledSubaccount.PerpetualPositions[0].PerpetualId) + if err != nil { + return nil, err + } + } + if _, exists := suffixesMap[suffix]; !exists { + suffixes = append(suffixes, suffix) + suffixesMap[suffix] = true + } + } + return suffixes, nil +} + +// getLastBlockNegativeSubaccountSeen gets the last block where a subaccount with negative total net +// collateral was seen for subaccounts in a slice of settled updates. +func (k Keeper) getLastBlockNegativeSubaccountSeen( + ctx sdk.Context, + settledUpdates []SettledUpdate, +) ( + lastBlockNegativeSubaccountSeen uint32, + negativeSubaccountExists bool, + err error, +) { + negativeTncSubaccountStoresuffixes, err := k.getNegativeTncSubaccountStoresuffixes( + ctx, + settledUpdates, + ) + if err != nil { + return uint32(0), false, err + } + lastBlockNegativeSubaccountSeen = uint32(0) + negativeSubaccountExists = false + for _, storeSuffix := range negativeTncSubaccountStoresuffixes { + blockHeight, exists := k.getNegativeTncSubaccountSeenAtBlockWithSuffix(ctx, storeSuffix) + if exists && blockHeight > lastBlockNegativeSubaccountSeen { + lastBlockNegativeSubaccountSeen = blockHeight + negativeSubaccountExists = true + } + } + return lastBlockNegativeSubaccountSeen, negativeSubaccountExists, nil } diff --git a/protocol/x/subaccounts/keeper/negative_tnc_subaccount_test.go b/protocol/x/subaccounts/keeper/negative_tnc_subaccount_test.go index 39ffcbb440..2f0230c69a 100644 --- a/protocol/x/subaccounts/keeper/negative_tnc_subaccount_test.go +++ b/protocol/x/subaccounts/keeper/negative_tnc_subaccount_test.go @@ -4,6 +4,8 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" "github.com/dydxprotocol/v4-chain/protocol/testutil/tracer" "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/keeper" @@ -12,129 +14,230 @@ import ( ) func TestGetSetNegativeTncSubaccountSeenAtBlock(t *testing.T) { + testPerpetualIds := []uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id, + constants.Iso2Usd_IsolatedMarket.Params.Id, + constants.BtcUsd_NoMarginRequirement.Params.Id, + } + testsuffixes := []string{ + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + lib.UintToString(constants.Iso2Usd_IsolatedMarket.Params.Id), + types.CrossCollateralSuffix, + } tests := map[string]struct { // Setup. - setupTestAndPerformAssertions func(ctx sdk.Context, s keeper.Keeper) + setupTestAndPerformAssertions func(ctx sdk.Context, s keeper.Keeper) error // Expectations. expectedMultiStoreWrites []string }{ "Block height defaults to zero if not set and doesn't exist": { - setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) { - block, exists := k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.False(t, exists) - require.Equal( - t, - uint32(0), - block, - ) + setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) error { + for _, perpetualId := range testPerpetualIds { + block, exists, err := k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.False(t, exists) + require.Equal( + t, + uint32(0), + block, + ) + } + return nil }, expectedMultiStoreWrites: []string{}, }, "Block height can be updated": { - setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) { - k.SetNegativeTncSubaccountSeenAtBlock(ctx, 1) - block, exists := k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.True(t, exists) - require.Equal( - t, - uint32(1), - block, - ) + setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) error { + for _, perpetualId := range testPerpetualIds { + err := k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, 1) + if err != nil { + return err + } + block, exists, err := k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(1), + block, + ) + } + return nil }, expectedMultiStoreWrites: []string{ - types.NegativeTncSubaccountSeenAtBlockKey, + types.NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix + testsuffixes[0], + types.NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix + testsuffixes[1], + types.NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix + testsuffixes[2], }, }, "Block height can be updated more than once": { - setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) { - k.SetNegativeTncSubaccountSeenAtBlock(ctx, 1) - block, exists := k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.True(t, exists) - require.Equal( - t, - uint32(1), - block, - ) - - k.SetNegativeTncSubaccountSeenAtBlock(ctx, 2) - block, exists = k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.True(t, exists) - require.Equal( - t, - uint32(2), - block, - ) - - k.SetNegativeTncSubaccountSeenAtBlock(ctx, 3) - block, exists = k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.True(t, exists) - require.Equal( - t, - uint32(3), - block, - ) - - k.SetNegativeTncSubaccountSeenAtBlock(ctx, 10) - block, exists = k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.True(t, exists) - require.Equal( - t, - uint32(10), - block, - ) - }, + setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) error { + for _, perpetualId := range testPerpetualIds { + err := k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, 1) + if err != nil { + return nil + } + block, exists, err := k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(1), + block, + ) - expectedMultiStoreWrites: []string{ - types.NegativeTncSubaccountSeenAtBlockKey, - types.NegativeTncSubaccountSeenAtBlockKey, - types.NegativeTncSubaccountSeenAtBlockKey, - types.NegativeTncSubaccountSeenAtBlockKey, + err = k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, 2) + if err != nil { + return nil + } + block, exists, err = k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(2), + block, + ) + + err = k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, 3) + if err != nil { + return nil + } + block, exists, err = k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(3), + block, + ) + + err = k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, 10) + if err != nil { + return nil + } + block, exists, err = k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(10), + block, + ) + } + return nil }, + + expectedMultiStoreWrites: append( + getWriteKeys(testsuffixes[0], 4), + append( + getWriteKeys(testsuffixes[1], 4), + getWriteKeys(testsuffixes[2], 4)..., + )..., + ), }, "Block height can be updated to same block height": { - setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) { - k.SetNegativeTncSubaccountSeenAtBlock(ctx, 0) - block, exists := k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.True(t, exists) - require.Equal( - t, - uint32(0), - block, - ) - - k.SetNegativeTncSubaccountSeenAtBlock(ctx, 0) - block, exists = k.GetNegativeTncSubaccountSeenAtBlock(ctx) - require.True(t, exists) - require.Equal( - t, - uint32(0), - block, - ) + setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) error { + for _, perpetualId := range testPerpetualIds { + err := k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, 0) + if err != nil { + return err + } + block, exists, err := k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(0), + block, + ) + + err = k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, 0) + if err != nil { + return err + } + block, exists, err = k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(0), + block, + ) + } + return nil }, - expectedMultiStoreWrites: []string{ - types.NegativeTncSubaccountSeenAtBlockKey, - types.NegativeTncSubaccountSeenAtBlockKey, + expectedMultiStoreWrites: append( + getWriteKeys(testsuffixes[0], 2), + append( + getWriteKeys(testsuffixes[1], 2), + getWriteKeys(testsuffixes[2], 2)..., + )..., + ), + }, + "Block height can be updated to different block heights for each collateral pool address": { + setupTestAndPerformAssertions: func(ctx sdk.Context, k keeper.Keeper) error { + for i, perpetualId := range testPerpetualIds { + err := k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, uint32(i)) + if err != nil { + return err + } + block, exists, err := k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(i), + block, + ) + + err = k.SetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId, uint32(2*i+1)) + if err != nil { + return err + } + block, exists, err = k.GetNegativeTncSubaccountSeenAtBlock(ctx, perpetualId) + require.NoError(t, err) + require.True(t, exists) + require.Equal( + t, + uint32(2*i+1), + block, + ) + } + return nil }, + + expectedMultiStoreWrites: append( + getWriteKeys(testsuffixes[0], 2), + append( + getWriteKeys(testsuffixes[1], 2), + getWriteKeys(testsuffixes[2], 2)..., + )..., + ), }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { // Setup keeper state and test parameters. - ctx, subaccountsKeeper, _, _, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, true) + ctx, subaccountsKeeper, pricesKeeper, perpetualsKeeper, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, true) + keepertest.CreateTestMarkets(t, ctx, pricesKeeper) + keepertest.CreateTestLiquidityTiers(t, ctx, perpetualsKeeper) + keepertest.CreateTestPerpetuals(t, ctx, perpetualsKeeper) // Set the tracer on the multistore to verify the performed writes are correct. traceDecoder := &tracer.TraceDecoder{} ctx.MultiStore().SetTracer(traceDecoder) - tc.setupTestAndPerformAssertions( + err := tc.setupTestAndPerformAssertions( ctx, *subaccountsKeeper, ) + require.NoError(t, err) // Verify the writes were done in the expected order. traceDecoder.RequireKeyPrefixWrittenInSequence(t, tc.expectedMultiStoreWrites) @@ -144,12 +247,23 @@ func TestGetSetNegativeTncSubaccountSeenAtBlock(t *testing.T) { func TestGetSetNegativeTncSubaccountSeenAtBlock_PanicsOnDecreasingBlock(t *testing.T) { // Setup keeper state and test parameters. - ctx, subaccountsKeeper, _, _, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, true) - - subaccountsKeeper.SetNegativeTncSubaccountSeenAtBlock(ctx, 2) + ctx, subaccountsKeeper, pricesKeeper, perpetualsKeeper, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, true) + keepertest.CreateTestMarkets(t, ctx, pricesKeeper) + keepertest.CreateTestLiquidityTiers(t, ctx, perpetualsKeeper) + keepertest.CreateTestPerpetuals(t, ctx, perpetualsKeeper) + err := subaccountsKeeper.SetNegativeTncSubaccountSeenAtBlock(ctx, uint32(0), 2) + require.NoError(t, err) require.PanicsWithValue( t, "SetNegativeTncSubaccountSeenAtBlock: new block height (1) is less than the current block height (2)", - func() { subaccountsKeeper.SetNegativeTncSubaccountSeenAtBlock(ctx, 1) }, + func() { _ = subaccountsKeeper.SetNegativeTncSubaccountSeenAtBlock(ctx, uint32(0), 1) }, ) } + +func getWriteKeys(suffix string, times int) []string { + writeKeys := make([]string, times) + for i := 0; i < times; i++ { + writeKeys[i] = types.NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix + suffix + } + return writeKeys +} diff --git a/protocol/x/subaccounts/keeper/oimf_test.go b/protocol/x/subaccounts/keeper/oimf_test.go index 16bcce5eb7..6282188f05 100644 --- a/protocol/x/subaccounts/keeper/oimf_test.go +++ b/protocol/x/subaccounts/keeper/oimf_test.go @@ -343,7 +343,6 @@ func TestGetDeltaOpenInterestFromPerpMatchUpdates(t *testing.T) { tc.expectedUpdatedPerpId, updatedPerpId, ) - fmt.Printf("deltaOpenInterest: %+v, tc.expectedDelta: %+v\n", deltaOpenInterest == nil, tc.expectedDelta == nil) require.Zerof( t, tc.expectedDelta.Cmp(deltaOpenInterest), diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index 2de2db773a..6cd9e58c8f 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -62,8 +62,15 @@ func (k Keeper) GetCollateralPoolForSubaccount(ctx sdk.Context, subaccountId typ sdk.AccAddress, error, ) { - // Use the default collateral pool if the subaccount has no perpetual positions. subaccount := k.GetSubaccount(ctx, subaccountId) + return k.getCollateralPoolForSubaccount(ctx, subaccount) +} + +func (k Keeper) getCollateralPoolForSubaccount(ctx sdk.Context, subaccount types.Subaccount) ( + sdk.AccAddress, + error, +) { + // Use the default collateral pool if the subaccount has no perpetual positions. if len(subaccount.PerpetualPositions) == 0 { return types.ModuleAddress, nil } @@ -573,10 +580,16 @@ func (k Keeper) internalCanUpdateSubaccounts( // Block all withdrawals and transfers if either of the following is true within the last // `WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS`: - // - There was a negative TNC subaccount seen. + // - There was a negative TNC subaccount seen for any of the collateral pools of subaccounts being updated // - There was a chain outage that lasted at least five minutes. if updateType == types.Withdrawal || updateType == types.Transfer { - lastBlockNegativeTncSubaccountSeen, negativeTncSubaccountExists := k.GetNegativeTncSubaccountSeenAtBlock(ctx) + lastBlockNegativeTncSubaccountSeen, negativeTncSubaccountExists, err := k.getLastBlockNegativeSubaccountSeen( + ctx, + settledUpdates, + ) + if err != nil { + return false, nil, err + } currentBlock := uint32(ctx.BlockHeight()) // Panic if the current block is less than the last block a negative TNC subaccount was seen. diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index 3cdf6f6535..1999d6b6f0 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -2609,7 +2609,7 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { Number: 0, } secondSubaccountId := types.SubaccountId{ - Owner: "0", + Owner: "1", Number: 1, } @@ -2621,16 +2621,16 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { marketParamPrices []pricestypes.MarketParamPrice // subaccount state - perpetualPositions []*types.PerpetualPosition - assetPositions []*types.AssetPosition + perpetualPositions map[types.SubaccountId][]*types.PerpetualPosition + assetPositions map[types.SubaccountId][]*types.AssetPosition // updates updates []types.Update // expectations expectedQuoteBalance *big.Int - expectedPerpetualPositions []*types.PerpetualPosition - expectedAssetPositions []*types.AssetPosition + expectedPerpetualPositions map[types.SubaccountId][]*types.PerpetualPosition + expectedAssetPositions map[types.SubaccountId][]*types.AssetPosition expectedSuccess bool expectedSuccessPerUpdate []types.UpdateResult expectedErr error @@ -2643,7 +2643,7 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { // Negative TNC subaccount state currentBlock uint32 - negativeTncSubaccountSeenAtBlock uint32 + negativeTncSubaccountSeenAtBlock map[uint32]uint32 // Update type updateType types.UpdateType @@ -2652,15 +2652,20 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { expectedQuoteBalance: big.NewInt(100), expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + }, updates: []types.Update{ { AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), }, }, - expectedAssetPositions: []*types.AssetPosition{ - { - AssetId: uint32(0), - Quantums: dtypes.NewInt(100), // 100 USDC + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(100), // 100 USDC + }, }, }, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ @@ -2673,8 +2678,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100, + }, updateType: types.Deposit, }, @@ -2683,15 +2690,20 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { expectedQuoteBalance: big.NewInt(100), expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + }, updates: []types.Update{ { AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), }, }, - expectedAssetPositions: []*types.AssetPosition{ - { - AssetId: uint32(0), - Quantums: dtypes.NewInt(100), // 100 USDC + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(100), // 100 USDC + }, }, }, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ @@ -2705,8 +2717,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { msgSenderEnabled: true, currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100 - - types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, updateType: types.Deposit, }, @@ -2714,15 +2728,20 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { expectedQuoteBalance: big.NewInt(100), expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + }, updates: []types.Update{ { AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), }, }, - expectedAssetPositions: []*types.AssetPosition{ - { - AssetId: uint32(0), - Quantums: dtypes.NewInt(100), // 100 USDC + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(100), // 100 USDC + }, }, }, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ @@ -2735,8 +2754,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 0, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 0, + }, updateType: types.Deposit, }, @@ -2747,13 +2768,13 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedAssetPositions: []*types.AssetPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: {}, }, @@ -2764,8 +2785,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100, + }, updateType: types.Withdrawal, }, @@ -2777,13 +2800,13 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedAssetPositions: []*types.AssetPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: {}, }, @@ -2795,8 +2818,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { msgSenderEnabled: true, currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100 - - types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, updateType: types.Withdrawal, }, @@ -2808,16 +2833,18 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedAssetPositions: []*types.AssetPosition{ - { - AssetId: uint32(0), - Quantums: dtypes.NewInt(-100), // 100 USDC + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(-100), // 100 USDC + }, }, }, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ @@ -2836,8 +2863,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { msgSenderEnabled: true, currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100 - - types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + }, updateType: types.Withdrawal, }, @@ -2848,16 +2877,64 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedAssetPositions: []*types.AssetPosition{ + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(-100), // 100 USDC + }, + }, + }, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(-100), // 100 USDC + }, + }, + }, + updates: []types.Update{ { - AssetId: uint32(0), - Quantums: dtypes.NewInt(-100), // 100 USDC + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 0, + }, + + updateType: types.Withdrawal, + }, + `withdrawals are not blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for a different + collateral pool`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: true, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(-100), // 100 USDC + }, }, }, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ @@ -2875,31 +2952,261 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 0, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, + + updateType: types.Withdrawal, + }, + `withdrawals are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for an isolated + perpetual collateral pool`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{types.WithdrawalsAndTransfersBlocked}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + }, + updates: []types.Update{ + { + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, + + updateType: types.Withdrawal, + }, + `withdrawals are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was never seen for the cross-perpetual + collateral pool, both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + constants.BtcUsd_NoMarginRequirement.Params.Id: 0, + }, + + updateType: types.Withdrawal, + }, + `withdrawals are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was seen for the cross-perpetual + collateral pool after WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + }, + + updateType: types.Withdrawal, + }, + `withdrawals are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was never seen for another isolated + collateral pool, both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + constants.Iso2Usd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 0, + constants.Iso2Usd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, + + updateType: types.Withdrawal, + }, + `withdrawals are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was seen for another isolated perpetual + collateral pool after WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + constants.Iso2Usd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + constants.Iso2Usd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, updateType: types.Withdrawal, }, "well-collateralized matches are not blocked if negative TNC subaccount was seen at current block": { - assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 + assetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 + }, expectedQuoteBalance: big.NewInt(0), expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_NoMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneAndHalfBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneAndHalfBTCLong}, }, expectedUpdatedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ firstSubaccountId: { &constants.PerpetualPosition_OneAndHalfBTCLong, }, }, - expectedAssetPositions: []*types.AssetPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: { { @@ -2931,32 +3238,36 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: false, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100, + }, updateType: types.Match, }, `well-collateralized matches are not blocked if current block is within WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS`: { - assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 + assetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 + }, expectedQuoteBalance: big.NewInt(0), expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_NoMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneAndHalfBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneAndHalfBTCLong}, }, expectedUpdatedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ firstSubaccountId: { &constants.PerpetualPosition_OneAndHalfBTCLong, }, }, - expectedAssetPositions: []*types.AssetPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: { { @@ -2989,31 +3300,35 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { msgSenderEnabled: false, currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100 - - types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, updateType: types.Match, }, "well-collateralized matches are not blocked if negative TNC subaccount was never seen": { - assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 + assetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: testutil.CreateUsdcAssetPosition(big.NewInt(25_000_000_000)), // $25,000 + }, expectedQuoteBalance: big.NewInt(0), expectedSuccess: true, expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, perpetuals: []perptypes.Perpetual{ constants.BtcUsd_NoMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneAndHalfBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneAndHalfBTCLong}, }, expectedUpdatedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ firstSubaccountId: { &constants.PerpetualPosition_OneAndHalfBTCLong, }, }, - expectedAssetPositions: []*types.AssetPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: { { @@ -3045,8 +3360,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: false, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 0, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 0, + }, updateType: types.Match, }, @@ -3060,11 +3377,11 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneHundredthBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneHundredthBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneHundredthBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneHundredthBTCLong}, }, updates: []types.Update{ { @@ -3089,9 +3406,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100, - + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100, + }, updateType: types.Match, }, `undercollateralized matches are not blocked if current block is within @@ -3105,11 +3423,11 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneHundredthBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneHundredthBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneHundredthBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneHundredthBTCLong}, }, updates: []types.Update{ { @@ -3135,8 +3453,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { msgSenderEnabled: true, currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100 - - types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, updateType: types.Match, }, @@ -3150,11 +3470,11 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_SmallMarginRequirement, }, - perpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneHundredthBTCLong, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneHundredthBTCLong}, }, - expectedPerpetualPositions: []*types.PerpetualPosition{ - &constants.PerpetualPosition_OneHundredthBTCLong, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneHundredthBTCLong}, }, updates: []types.Update{ { @@ -3179,8 +3499,10 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 0, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 0, + }, updateType: types.Match, }, @@ -3191,10 +3513,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { types.WithdrawalsAndTransfersBlocked, types.WithdrawalsAndTransfersBlocked, }, - perpetuals: []perptypes.Perpetual{}, - perpetualPositions: []*types.PerpetualPosition{}, - expectedPerpetualPositions: []*types.PerpetualPosition{}, - expectedAssetPositions: []*types.AssetPosition{}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: {}, secondSubaccountId: {}, @@ -3211,10 +3535,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100, + }, - updateType: types.Withdrawal, + updateType: types.Transfer, }, `transfers are blocked if negative TNC subaccount was seen within WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS`: { @@ -3224,10 +3550,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { types.WithdrawalsAndTransfersBlocked, types.WithdrawalsAndTransfersBlocked, }, - perpetuals: []perptypes.Perpetual{}, - perpetualPositions: []*types.PerpetualPosition{}, - expectedPerpetualPositions: []*types.PerpetualPosition{}, - expectedAssetPositions: []*types.AssetPosition{}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: {}, secondSubaccountId: {}, @@ -3245,10 +3573,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { msgSenderEnabled: true, currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100 - - types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, - updateType: types.Withdrawal, + updateType: types.Transfer, }, `transfers are not blocked if negative TNC subaccount was seen after WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS`: { @@ -3258,10 +3588,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { types.NewlyUndercollateralized, types.Success, }, - perpetuals: []perptypes.Perpetual{}, - perpetualPositions: []*types.PerpetualPosition{}, - expectedPerpetualPositions: []*types.PerpetualPosition{}, - expectedAssetPositions: []*types.AssetPosition{}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: {}, secondSubaccountId: {}, @@ -3279,10 +3611,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { msgSenderEnabled: true, currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 100 - - types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + }, - updateType: types.Withdrawal, + updateType: types.Transfer, }, "transfers are not blocked if negative TNC subaccount was never seen": { expectedQuoteBalance: big.NewInt(-100), @@ -3291,10 +3625,12 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { types.NewlyUndercollateralized, types.Success, }, - perpetuals: []perptypes.Perpetual{}, - perpetualPositions: []*types.PerpetualPosition{}, - expectedPerpetualPositions: []*types.PerpetualPosition{}, - expectedAssetPositions: []*types.AssetPosition{}, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{}, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ firstSubaccountId: {}, secondSubaccountId: {}, @@ -3311,10 +3647,248 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { }, msgSenderEnabled: true, - currentBlock: 100, - negativeTncSubaccountSeenAtBlock: 0, + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_NoMarginRequirement.Params.Id: 0, + }, - updateType: types.Withdrawal, + updateType: types.Transfer, + }, + `transfers are not blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for a different + collateral pool from the ones associated with the subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.NewlyUndercollateralized, + types.Success, + }, + perpetuals: []perptypes.Perpetual{ + constants.IsoUsd_IsolatedMarket, + constants.Iso2Usd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + secondSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + secondSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + SubaccountId: firstSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.Iso2Usd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, + + updateType: types.Transfer, + }, + `transfers are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was never seen for the cross-perpetual + collateral pool, both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + secondSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + secondSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + SubaccountId: firstSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + constants.BtcUsd_NoMarginRequirement.Params.Id: 0, + }, + + updateType: types.Transfer, + }, + `transferss are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was seen for the cross-perpetual + collateral pool after WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_SmallMarginRequirement, + constants.IsoUsd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + secondSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + secondSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + SubaccountId: firstSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + constants.BtcUsd_NoMarginRequirement.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + }, + + updateType: types.Transfer, + }, + `transfers are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was never seen for another isolated perpetual + collateral pool, both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.IsoUsd_IsolatedMarket, + constants.Iso2Usd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + SubaccountId: firstSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 0, + constants.Iso2Usd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, + + updateType: types.Transfer, + }, + `transferss are blocked if negative TNC subaccount was seen within + WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS for one isolated + perpetual collateral pool and negative TNC subaccount was seen for another the cross-perpetual + collateral pool after WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + both of which are associated with subaccounts being updated`: { + expectedQuoteBalance: big.NewInt(-100), + expectedSuccess: false, + expectedSuccessPerUpdate: []types.UpdateResult{ + types.WithdrawalsAndTransfersBlocked, + types.WithdrawalsAndTransfersBlocked, + }, + perpetuals: []perptypes.Perpetual{ + constants.IsoUsd_IsolatedMarket, + constants.Iso2Usd_IsolatedMarket, + }, + perpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + firstSubaccountId: {&constants.PerpetualPosition_OneISOLong}, + secondSubaccountId: {&constants.PerpetualPosition_OneISO2Long}, + }, + expectedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{}, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + firstSubaccountId: {}, + secondSubaccountId: {}, + }, + updates: []types.Update{ + { + SubaccountId: firstSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(-100)), + }, + { + SubaccountId: secondSubaccountId, + AssetUpdates: testutil.CreateUsdcAssetUpdate(big.NewInt(100)), + }, + }, + msgSenderEnabled: true, + + currentBlock: 100, + negativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS, + constants.Iso2Usd_IsolatedMarket.Params.Id: 100 - + types.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS + 1, + }, + + updateType: types.Transfer, }, } @@ -3378,15 +3952,31 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { } subaccounts := createNSubaccount(keeper, ctx, 2, big.NewInt(1_000)) - subaccounts[0].PerpetualPositions = tc.perpetualPositions - subaccounts[0].AssetPositions = tc.assetPositions - keeper.SetSubaccount(ctx, subaccounts[0]) - keeper.SetSubaccount(ctx, subaccounts[1]) + for _, subaccount := range subaccounts { + if perpetualPositions, exists := tc.perpetualPositions[*subaccount.Id]; exists { + subaccount.PerpetualPositions = perpetualPositions + } else { + subaccount.PerpetualPositions = []*types.PerpetualPosition{} + } + if assetPositions, exists := tc.assetPositions[*subaccount.Id]; exists { + subaccount.AssetPositions = assetPositions + } else { + subaccount.AssetPositions = []*types.AssetPosition{} + } + keeper.SetSubaccount(ctx, subaccount) + } subaccountId := *subaccounts[0].Id // Set the negative TNC subaccount seen at block in state if it's greater than 0. - if tc.negativeTncSubaccountSeenAtBlock != 0 { - keeper.SetNegativeTncSubaccountSeenAtBlock(ctx, tc.negativeTncSubaccountSeenAtBlock) + for perpetualId, negativeTncSubaccountSeenAtBlock := range tc.negativeTncSubaccountSeenAtBlock { + if negativeTncSubaccountSeenAtBlock != 0 { + err := keeper.SetNegativeTncSubaccountSeenAtBlock( + ctx, + perpetualId, + negativeTncSubaccountSeenAtBlock, + ) + require.NoError(t, err) + } } // Set the current block number on the context. @@ -3429,14 +4019,19 @@ func TestUpdateSubaccounts_WithdrawalsBlocked(t *testing.T) { ) } - newSubaccount := keeper.GetSubaccount(ctx, subaccountId) - require.Equal(t, len(tc.expectedPerpetualPositions), len(newSubaccount.PerpetualPositions)) - for i, ep := range tc.expectedPerpetualPositions { - require.Equal(t, *ep, *newSubaccount.PerpetualPositions[i]) + for subaccountIdToCheck, expectedPerpetualPositions := range tc.expectedPerpetualPositions { + newSubaccount := keeper.GetSubaccount(ctx, subaccountIdToCheck) + require.Equal(t, len(expectedPerpetualPositions), len(newSubaccount.PerpetualPositions)) + for i, ep := range expectedPerpetualPositions { + require.Equal(t, *ep, *newSubaccount.PerpetualPositions[i]) + } } - require.Equal(t, len(tc.expectedAssetPositions), len(newSubaccount.AssetPositions)) - for i, ep := range tc.expectedAssetPositions { - require.Equal(t, *ep, *newSubaccount.AssetPositions[i]) + for subaccountIdToCheck, expectedAssetPositions := range tc.expectedAssetPositions { + newSubaccount := keeper.GetSubaccount(ctx, subaccountIdToCheck) + require.Equal(t, len(expectedAssetPositions), len(newSubaccount.AssetPositions)) + for i, ap := range expectedAssetPositions { + require.Equal(t, *ap, *newSubaccount.AssetPositions[i]) + } } }) } diff --git a/protocol/x/subaccounts/types/expected_keepers.go b/protocol/x/subaccounts/types/expected_keepers.go index 8f0c9dd217..201f8352fc 100644 --- a/protocol/x/subaccounts/types/expected_keepers.go +++ b/protocol/x/subaccounts/types/expected_keepers.go @@ -74,6 +74,7 @@ type PerpetualsKeeper interface { GetAllPerpetuals(ctx sdk.Context) []perptypes.Perpetual GetInsuranceFundName(ctx sdk.Context, perpetualId uint32) (string, error) GetInsuranceFundModuleAddress(ctx sdk.Context, perpetualId uint32) (sdk.AccAddress, error) + IsIsolatedPerpetual(ctx sdk.Context, perpetualId uint32) (bool, error) ModifyOpenInterest(ctx sdk.Context, perpetualId uint32, bigQuantums *big.Int) error } diff --git a/protocol/x/subaccounts/types/keys.go b/protocol/x/subaccounts/types/keys.go index 6843297789..0d6089d20a 100644 --- a/protocol/x/subaccounts/types/keys.go +++ b/protocol/x/subaccounts/types/keys.go @@ -13,7 +13,10 @@ const ( const ( // SubaccountKeyPrefix is the prefix to retrieve all Subaccounts SubaccountKeyPrefix = "SA:" - // NegativeTncSubaccountSeenAtBlockKey is the store key that stores the last - // block a negative TNC subaccount was seen in state. - NegativeTncSubaccountSeenAtBlockKey = "NegSA:" + // NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix is the prefix for the store key that + // stores the last block a negative TNC subaccount was seen in state for a specific collateral pool. + NegativeTncSubaccountForCollateralPoolSeenAtBlockKeyPrefix = "NegSA:" + // Suffix for the store key to the last block a negative TNC subaccount was seen in state for the + // cross collateral pool. + CrossCollateralSuffix = "cross" ) diff --git a/protocol/x/subaccounts/types/query.pb.go b/protocol/x/subaccounts/types/query.pb.go index 1c0893dc8a..9c0bc41dc2 100644 --- a/protocol/x/subaccounts/types/query.pb.go +++ b/protocol/x/subaccounts/types/query.pb.go @@ -228,8 +228,10 @@ func (m *QuerySubaccountAllResponse) GetPagination() *query.PageResponse { } // QueryGetWithdrawalAndTransfersBlockedInfoRequest is a request type for -// fetching information about whether withdrawals and transfers are blocked. +// fetching information about whether withdrawals and transfers are blocked for +// a collateral pool associated with the passed in perpetual id. type QueryGetWithdrawalAndTransfersBlockedInfoRequest struct { + PerpetualId uint32 `protobuf:"varint,1,opt,name=perpetual_id,json=perpetualId,proto3" json:"perpetual_id,omitempty"` } func (m *QueryGetWithdrawalAndTransfersBlockedInfoRequest) Reset() { @@ -269,6 +271,13 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoRequest) XXX_DiscardUnknown() var xxx_messageInfo_QueryGetWithdrawalAndTransfersBlockedInfoRequest proto.InternalMessageInfo +func (m *QueryGetWithdrawalAndTransfersBlockedInfoRequest) GetPerpetualId() uint32 { + if m != nil { + return m.PerpetualId + } + return 0 +} + // QueryGetWithdrawalAndTransfersBlockedInfoRequest is a response type for // fetching information about whether withdrawals and transfers are blocked. type QueryGetWithdrawalAndTransfersBlockedInfoResponse struct { @@ -349,49 +358,50 @@ func init() { } var fileDescriptor_adc19ff1d5b72954 = []byte{ - // 666 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0x4d, 0x6b, 0xd4, 0x40, - 0x18, 0xde, 0x6c, 0x3f, 0xc0, 0x91, 0x5e, 0x86, 0x52, 0xb7, 0x41, 0xd6, 0xb2, 0xac, 0x6d, 0x95, - 0x36, 0xe3, 0xb6, 0x15, 0x41, 0x28, 0xb8, 0x7b, 0xb0, 0x5a, 0x0f, 0xd5, 0xb4, 0x52, 0x10, 0x24, - 0x4c, 0x92, 0x69, 0x1a, 0x4c, 0x67, 0xb6, 0x99, 0x49, 0x3f, 0x28, 0xbd, 0x78, 0xf3, 0x26, 0xf8, - 0x03, 0xbc, 0x7a, 0x15, 0xfd, 0x11, 0x3d, 0x16, 0xbd, 0x78, 0x12, 0x69, 0xfd, 0x09, 0xfe, 0x00, - 0xd9, 0x99, 0xc9, 0x26, 0xdb, 0x1a, 0x76, 0x15, 0x6f, 0xc9, 0xcc, 0xfb, 0x3c, 0xef, 0xf3, 0xbc, - 0xf3, 0x3e, 0xa0, 0xee, 0x1f, 0xfa, 0x07, 0xed, 0x98, 0x09, 0xe6, 0xb1, 0x08, 0xf1, 0xc4, 0xc5, - 0x9e, 0xc7, 0x12, 0x2a, 0x38, 0xda, 0x4d, 0x48, 0x7c, 0x68, 0xc9, 0x2b, 0x58, 0xc9, 0x57, 0x59, - 0xb9, 0x2a, 0x73, 0xd2, 0x63, 0x7c, 0x87, 0x71, 0x47, 0x5e, 0x22, 0xf5, 0xa3, 0x40, 0xe6, 0x78, - 0xc0, 0x02, 0xa6, 0xce, 0x3b, 0x5f, 0xfa, 0xf4, 0x7a, 0xc0, 0x58, 0x10, 0x11, 0x84, 0xdb, 0x21, - 0xc2, 0x94, 0x32, 0x81, 0x45, 0xc8, 0x68, 0x8a, 0xb9, 0xad, 0x18, 0x90, 0x8b, 0x39, 0x51, 0x0a, - 0xd0, 0x5e, 0xc3, 0x25, 0x02, 0x37, 0x50, 0x1b, 0x07, 0x21, 0x95, 0xc5, 0xba, 0xf6, 0x56, 0xa1, - 0xf4, 0xec, 0x5b, 0x95, 0xd6, 0x3c, 0x30, 0xf9, 0xac, 0x43, 0xb6, 0x42, 0xc4, 0x7a, 0xf7, 0xce, - 0x26, 0xbb, 0x09, 0xe1, 0x02, 0x5a, 0x60, 0x84, 0xed, 0x53, 0x12, 0x57, 0x8c, 0x29, 0x63, 0xf6, - 0x4a, 0xab, 0xf2, 0xe5, 0xf3, 0xfc, 0xb8, 0x36, 0xd2, 0xf4, 0xfd, 0x98, 0x70, 0xbe, 0x2e, 0xe2, - 0x90, 0x06, 0xb6, 0x2a, 0x83, 0x13, 0x60, 0x94, 0x26, 0x3b, 0x2e, 0x89, 0x2b, 0xe5, 0x29, 0x63, - 0x76, 0xcc, 0xd6, 0x7f, 0x35, 0x02, 0xae, 0xc9, 0x26, 0xf9, 0x0e, 0xbc, 0xcd, 0x28, 0x27, 0x70, - 0x15, 0x80, 0x4c, 0x93, 0xec, 0x73, 0x75, 0xa1, 0x6e, 0x15, 0x0d, 0xd5, 0xca, 0x18, 0x5a, 0xc3, - 0x27, 0xdf, 0x6f, 0x94, 0xec, 0x1c, 0xba, 0xeb, 0xa5, 0x19, 0x45, 0x97, 0xbd, 0x3c, 0x04, 0x20, - 0x9b, 0x93, 0x6e, 0x34, 0x6d, 0x69, 0x37, 0x9d, 0xa1, 0x5a, 0xea, 0x59, 0xf5, 0x50, 0xad, 0xa7, - 0x38, 0x20, 0x1a, 0x6b, 0xe7, 0x90, 0xb5, 0x8f, 0x06, 0x30, 0x2f, 0x98, 0x69, 0x46, 0x51, 0xa1, - 0x9f, 0xa1, 0x7f, 0xf7, 0x03, 0x57, 0x7a, 0x24, 0x97, 0xa5, 0xe4, 0x99, 0xbe, 0x92, 0x95, 0x90, - 0x1e, 0xcd, 0x0b, 0xe0, 0x4e, 0xfa, 0xc8, 0x9b, 0xa1, 0xd8, 0xf6, 0x63, 0xbc, 0x8f, 0xa3, 0x26, - 0xf5, 0x37, 0x62, 0x4c, 0xf9, 0x16, 0x89, 0x79, 0x2b, 0x62, 0xde, 0x2b, 0xe2, 0x3f, 0xa6, 0x5b, - 0x4c, 0x7b, 0xae, 0xbd, 0x2f, 0x83, 0xc6, 0x5f, 0x80, 0xb4, 0xfd, 0x35, 0x70, 0x93, 0x92, 0x00, - 0x8b, 0x70, 0x8f, 0x38, 0x82, 0x7a, 0x4e, 0xe6, 0xc6, 0xe1, 0x84, 0x50, 0x07, 0x0b, 0xc7, 0xed, - 0xc0, 0xe4, 0x03, 0x8c, 0xd9, 0x53, 0x69, 0xf1, 0x06, 0xf5, 0xb2, 0x51, 0xac, 0x13, 0x42, 0x9b, - 0x42, 0xd2, 0xc3, 0xfb, 0xc0, 0xf4, 0xb6, 0x71, 0x48, 0x1d, 0x96, 0x08, 0x1c, 0x90, 0x0b, 0x2c, - 0x6a, 0xcd, 0x26, 0x64, 0xc5, 0x9a, 0x2c, 0xc8, 0x63, 0x5f, 0x82, 0xb9, 0xfd, 0xae, 0x72, 0xee, - 0x60, 0xea, 0x3b, 0x22, 0x15, 0xef, 0x24, 0xd4, 0x55, 0xfa, 0x33, 0xb6, 0x21, 0xc9, 0x36, 0x93, - 0xc3, 0xe4, 0xed, 0x3e, 0x4f, 0x01, 0x9a, 0x7e, 0xe1, 0xd7, 0x30, 0x18, 0x91, 0x13, 0x82, 0x9f, - 0x0c, 0x00, 0x32, 0xf9, 0x70, 0xb1, 0xf8, 0xbd, 0x0b, 0xb3, 0x66, 0x36, 0xfa, 0x80, 0x2e, 0x67, - 0xa7, 0xb6, 0xfc, 0xfa, 0xeb, 0xcf, 0x77, 0xe5, 0x7b, 0xf0, 0x2e, 0x1a, 0x20, 0xef, 0xe8, 0x48, - 0x66, 0xf4, 0x18, 0x1d, 0xa9, 0x50, 0x1e, 0xc3, 0x0f, 0x06, 0x18, 0xeb, 0x59, 0xe2, 0xbe, 0xc2, - 0xff, 0x14, 0x2c, 0x73, 0x69, 0x60, 0xe1, 0xb9, 0x9c, 0xd4, 0xe6, 0xa4, 0xf6, 0x69, 0x58, 0x1f, - 0x44, 0x3b, 0x7c, 0x53, 0x06, 0xf5, 0x41, 0xf6, 0x10, 0xae, 0xf6, 0x1f, 0xfd, 0xa0, 0x09, 0x30, - 0x9f, 0xfc, 0x17, 0x2e, 0xed, 0xf7, 0x91, 0xf4, 0xdb, 0x82, 0x0f, 0x8a, 0xfd, 0x16, 0xef, 0x6a, - 0xba, 0xa9, 0x21, 0xdd, 0x62, 0xad, 0xcd, 0x93, 0xb3, 0xaa, 0x71, 0x7a, 0x56, 0x35, 0x7e, 0x9c, - 0x55, 0x8d, 0xb7, 0xe7, 0xd5, 0xd2, 0xe9, 0x79, 0xb5, 0xf4, 0xed, 0xbc, 0x5a, 0x7a, 0xb1, 0x1c, - 0x84, 0x62, 0x3b, 0x71, 0x2d, 0x8f, 0xed, 0xf4, 0x76, 0xd9, 0x5b, 0x9a, 0x97, 0x11, 0x41, 0xdd, - 0x93, 0x83, 0x9e, 0xce, 0xe2, 0xb0, 0x4d, 0xb8, 0x3b, 0x2a, 0x6f, 0x17, 0x7f, 0x07, 0x00, 0x00, - 0xff, 0xff, 0x06, 0xb5, 0xab, 0xe1, 0xf9, 0x06, 0x00, 0x00, + // 687 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x4f, 0x4f, 0xd4, 0x4e, + 0x18, 0xde, 0x2e, 0x7f, 0x92, 0xdf, 0xf0, 0xe3, 0x32, 0x21, 0xb8, 0x34, 0x66, 0xc5, 0xcd, 0x0a, + 0x68, 0xa0, 0x75, 0x01, 0x63, 0x62, 0x42, 0xe2, 0xee, 0x41, 0x04, 0x0f, 0x68, 0x81, 0x90, 0x98, + 0x98, 0x66, 0xda, 0xbe, 0x94, 0xc6, 0x32, 0x53, 0x3a, 0x53, 0xfe, 0x84, 0x70, 0xf1, 0xe6, 0xcd, + 0xc4, 0x0f, 0xe0, 0xd5, 0xab, 0xd1, 0x0f, 0xc1, 0x91, 0xe8, 0xc5, 0x93, 0x31, 0xe0, 0x47, 0xf0, + 0x03, 0x98, 0x9d, 0xb6, 0xdb, 0x2e, 0xd8, 0xec, 0x6a, 0xbc, 0xb5, 0x33, 0xef, 0xf3, 0xbc, 0xcf, + 0xf3, 0xf4, 0x7d, 0x8b, 0xea, 0xce, 0x91, 0x73, 0x18, 0x84, 0x4c, 0x30, 0x9b, 0xf9, 0x3a, 0x8f, + 0x2c, 0x62, 0xdb, 0x2c, 0xa2, 0x82, 0xeb, 0x7b, 0x11, 0x84, 0x47, 0x9a, 0xbc, 0xc2, 0x95, 0x7c, + 0x95, 0x96, 0xab, 0x52, 0x27, 0x6c, 0xc6, 0x77, 0x19, 0x37, 0xe5, 0xa5, 0x1e, 0xbf, 0xc4, 0x20, + 0x75, 0xcc, 0x65, 0x2e, 0x8b, 0xcf, 0xdb, 0x4f, 0xc9, 0xe9, 0x75, 0x97, 0x31, 0xd7, 0x07, 0x9d, + 0x04, 0x9e, 0x4e, 0x28, 0x65, 0x82, 0x08, 0x8f, 0xd1, 0x14, 0x73, 0x27, 0x66, 0xd0, 0x2d, 0xc2, + 0x21, 0x56, 0xa0, 0xef, 0x37, 0x2c, 0x10, 0xa4, 0xa1, 0x07, 0xc4, 0xf5, 0xa8, 0x2c, 0x4e, 0x6a, + 0x6f, 0x17, 0x4a, 0xcf, 0x9e, 0xe3, 0xd2, 0x9a, 0x8d, 0x26, 0x9e, 0xb5, 0xc9, 0x96, 0x41, 0xac, + 0x77, 0xee, 0x0c, 0xd8, 0x8b, 0x80, 0x0b, 0xac, 0xa1, 0x21, 0x76, 0x40, 0x21, 0xac, 0x28, 0x93, + 0xca, 0xcc, 0x7f, 0xad, 0xca, 0xe7, 0x4f, 0x73, 0x63, 0x89, 0x91, 0xa6, 0xe3, 0x84, 0xc0, 0xf9, + 0xba, 0x08, 0x3d, 0xea, 0x1a, 0x71, 0x19, 0x1e, 0x47, 0xc3, 0x34, 0xda, 0xb5, 0x20, 0xac, 0x94, + 0x27, 0x95, 0x99, 0x51, 0x23, 0x79, 0xab, 0x01, 0xba, 0x26, 0x9b, 0xe4, 0x3b, 0xf0, 0x80, 0x51, + 0x0e, 0x78, 0x15, 0xa1, 0x4c, 0x93, 0xec, 0x33, 0x32, 0x5f, 0xd7, 0x8a, 0x42, 0xd5, 0x32, 0x86, + 0xd6, 0xe0, 0xe9, 0xb7, 0x1b, 0x25, 0x23, 0x87, 0xee, 0x78, 0x69, 0xfa, 0xfe, 0x55, 0x2f, 0x8f, + 0x10, 0xca, 0x72, 0x4a, 0x1a, 0x4d, 0x69, 0x89, 0x9b, 0x76, 0xa8, 0x5a, 0xfc, 0x59, 0x93, 0x50, + 0xb5, 0xa7, 0xc4, 0x85, 0x04, 0x6b, 0xe4, 0x90, 0xb5, 0x0f, 0x0a, 0x52, 0x2f, 0x99, 0x69, 0xfa, + 0x7e, 0xa1, 0x9f, 0x81, 0xbf, 0xf7, 0x83, 0x97, 0xbb, 0x24, 0x97, 0xa5, 0xe4, 0xe9, 0x9e, 0x92, + 0x63, 0x21, 0x5d, 0x9a, 0x37, 0xd1, 0xdd, 0xf4, 0x23, 0x6f, 0x79, 0x62, 0xc7, 0x09, 0xc9, 0x01, + 0xf1, 0x9b, 0xd4, 0xd9, 0x08, 0x09, 0xe5, 0xdb, 0x10, 0xf2, 0x96, 0xcf, 0xec, 0x97, 0xe0, 0xac, + 0xd0, 0x6d, 0x96, 0xe6, 0x75, 0x13, 0xfd, 0x1f, 0x40, 0x18, 0x80, 0x88, 0x88, 0x6f, 0x7a, 0x8e, + 0x4c, 0x6c, 0xd4, 0x18, 0xe9, 0x9c, 0xad, 0x38, 0xb5, 0x77, 0x65, 0xd4, 0xf8, 0x03, 0xde, 0x24, + 0xa1, 0x35, 0x74, 0x8b, 0x82, 0x4b, 0x84, 0xb7, 0x0f, 0xa6, 0xa0, 0xb6, 0x99, 0x19, 0x36, 0x39, + 0x00, 0x35, 0x89, 0x30, 0xad, 0x36, 0x2c, 0xe9, 0x38, 0x99, 0x16, 0x6f, 0x50, 0x3b, 0x4b, 0x6b, + 0x1d, 0x80, 0x36, 0x85, 0xa4, 0xc7, 0x0f, 0x90, 0x6a, 0xef, 0x10, 0x8f, 0x9a, 0x2c, 0x12, 0xc4, + 0x85, 0x4b, 0x2c, 0xf1, 0x24, 0x8e, 0xcb, 0x8a, 0x35, 0x59, 0x90, 0xc7, 0xbe, 0x40, 0xb3, 0x07, + 0x1d, 0xe5, 0xdc, 0x24, 0xd4, 0x31, 0x45, 0x2a, 0xde, 0x8c, 0xa8, 0x15, 0xeb, 0xcf, 0xd8, 0x06, + 0x24, 0xdb, 0x74, 0x0e, 0x93, 0xb7, 0xbb, 0x99, 0x02, 0x12, 0xfa, 0xf9, 0x9f, 0x83, 0x68, 0x48, + 0x26, 0x84, 0x3f, 0x2a, 0x08, 0x65, 0xf2, 0xf1, 0x42, 0xf1, 0x48, 0x14, 0xae, 0xa3, 0xda, 0xe8, + 0x01, 0xba, 0xba, 0x5e, 0xb5, 0xa5, 0x57, 0x5f, 0x7e, 0xbc, 0x2d, 0xdf, 0xc7, 0xf7, 0xf4, 0x3e, + 0x7e, 0x09, 0xfa, 0xb1, 0x5c, 0xe3, 0x13, 0xfd, 0x38, 0xde, 0xdb, 0x13, 0xfc, 0x5e, 0x41, 0xa3, + 0x5d, 0x73, 0xde, 0x53, 0xf8, 0xef, 0x76, 0x4f, 0x5d, 0xec, 0x5b, 0x78, 0x6e, 0x95, 0x6a, 0xb3, + 0x52, 0xfb, 0x14, 0xae, 0xf7, 0xa3, 0x1d, 0xbf, 0x2e, 0xa3, 0x7a, 0x3f, 0x73, 0x88, 0x57, 0x7b, + 0x47, 0xdf, 0xef, 0x92, 0xa8, 0x4f, 0xfe, 0x09, 0x57, 0xe2, 0xf7, 0xb1, 0xf4, 0xdb, 0xc2, 0x0f, + 0x8b, 0xfd, 0x16, 0xcf, 0x6a, 0x3a, 0xa9, 0x1e, 0xdd, 0x66, 0xad, 0xad, 0xd3, 0xf3, 0xaa, 0x72, + 0x76, 0x5e, 0x55, 0xbe, 0x9f, 0x57, 0x95, 0x37, 0x17, 0xd5, 0xd2, 0xd9, 0x45, 0xb5, 0xf4, 0xf5, + 0xa2, 0x5a, 0x7a, 0xbe, 0xe4, 0x7a, 0x62, 0x27, 0xb2, 0x34, 0x9b, 0xed, 0x76, 0x77, 0xd9, 0x5f, + 0x9c, 0x93, 0x2b, 0xa2, 0x77, 0x4e, 0x0e, 0xbb, 0x3a, 0x8b, 0xa3, 0x00, 0xb8, 0x35, 0x2c, 0x6f, + 0x17, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0xed, 0x0b, 0x2a, 0xec, 0x1c, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -726,6 +736,11 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoRequest) MarshalToSizedBuffer( _ = i var l int _ = l + if m.PerpetualId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PerpetualId)) + i-- + dAtA[i] = 0x8 + } return len(dAtA) - i, nil } @@ -843,6 +858,9 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoRequest) Size() (n int) { } var l int _ = l + if m.PerpetualId != 0 { + n += 1 + sovQuery(uint64(m.PerpetualId)) + } return n } @@ -1289,6 +1307,25 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoRequest) Unmarshal(dAtA []byte return fmt.Errorf("proto: QueryGetWithdrawalAndTransfersBlockedInfoRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PerpetualId", wireType) + } + m.PerpetualId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PerpetualId |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:]) diff --git a/protocol/x/subaccounts/types/query.pb.gw.go b/protocol/x/subaccounts/types/query.pb.gw.go index 00ebde2bbe..9aebee906d 100644 --- a/protocol/x/subaccounts/types/query.pb.gw.go +++ b/protocol/x/subaccounts/types/query.pb.gw.go @@ -145,10 +145,21 @@ func local_request_Query_SubaccountAll_0(ctx context.Context, marshaler runtime. } +var ( + filter_Query_GetWithdrawalAndTransfersBlockedInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + func request_Query_GetWithdrawalAndTransfersBlockedInfo_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QueryGetWithdrawalAndTransfersBlockedInfoRequest var metadata runtime.ServerMetadata + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_GetWithdrawalAndTransfersBlockedInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.GetWithdrawalAndTransfersBlockedInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err @@ -158,6 +169,13 @@ func local_request_Query_GetWithdrawalAndTransfersBlockedInfo_0(ctx context.Cont var protoReq QueryGetWithdrawalAndTransfersBlockedInfoRequest var metadata runtime.ServerMetadata + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_GetWithdrawalAndTransfersBlockedInfo_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetWithdrawalAndTransfersBlockedInfo(ctx, &protoReq) return msg, metadata, err