diff --git a/protocol/app/module_accounts.go b/protocol/app/module_accounts.go index 0271faf694..5ba8510899 100644 --- a/protocol/app/module_accounts.go +++ b/protocol/app/module_accounts.go @@ -39,7 +39,7 @@ var ( bridgemoduletypes.ModuleName: {authtypes.Minter}, // subaccounts module account holds tokens for all subaccounts. satypes.ModuleName: nil, - // clob insurance fund account manages insurance fund for liquidations. + // insurance fund account manages insurance fund for liquidations. perpetualsmoduletypes.InsuranceFundName: nil, // rewards treasury account distribute funds trading accounts. rewardsmoduletypes.TreasuryAccountName: nil, diff --git a/protocol/daemons/liquidation/client/grpc_helper_test.go b/protocol/daemons/liquidation/client/grpc_helper_test.go index 181d932bfd..2d164072ad 100644 --- a/protocol/daemons/liquidation/client/grpc_helper_test.go +++ b/protocol/daemons/liquidation/client/grpc_helper_test.go @@ -2,10 +2,11 @@ package client_test import ( "context" - "cosmossdk.io/log" "errors" "testing" + "cosmossdk.io/log" + "github.com/cosmos/cosmos-sdk/types/query" "github.com/dydxprotocol/v4-chain/protocol/daemons/flags" "github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/api" @@ -409,6 +410,7 @@ func TestGetAllMarketPrices(t *testing.T) { response2 := &pricestypes.QueryAllMarketPricesResponse{ MarketPrices: []pricestypes.MarketPrice{ constants.TestMarketPrices[2], + constants.TestMarketPrices[3], }, } mck.On("AllMarketPrices", mock.Anything, req2).Return(response2, nil) diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index e29a53517c..4799fd2d86 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -265,7 +265,7 @@ func (_m *ClobKeeper) GetIndexerEventManager() indexer_manager.IndexerEventManag } // GetInsuranceFundBalance provides a mock function with given fields: ctx -func (_m *ClobKeeper) GetInsuranceFundBalance(ctx types.Context) *big.Int { +func (_m *ClobKeeper) GetInsuranceFundBalance(ctx types.Context, perpetualId uint32) *big.Int { ret := _m.Called(ctx) var r0 *big.Int diff --git a/protocol/testing/e2e/gov/perpetuals_test.go b/protocol/testing/e2e/gov/perpetuals_test.go index 2c8a8fbbce..e2ddeefcff 100644 --- a/protocol/testing/e2e/gov/perpetuals_test.go +++ b/protocol/testing/e2e/gov/perpetuals_test.go @@ -19,7 +19,7 @@ import ( var ( TEST_PERPETUAL_PARAMS = perptypes.PerpetualParams{ - Id: 765, + Id: 0, Ticker: "BTC-ADV4TNT", MarketId: 123, AtomicResolution: -8, diff --git a/protocol/testutil/constants/perpetuals.go b/protocol/testutil/constants/perpetuals.go index 2fc10cbb3c..3e55df8e80 100644 --- a/protocol/testutil/constants/perpetuals.go +++ b/protocol/testutil/constants/perpetuals.go @@ -299,8 +299,60 @@ var ( }, FundingIndex: dtypes.ZeroInt(), } + IsoUsd_IsolatedMarket = perptypes.Perpetual{ + Params: perptypes.PerpetualParams{ + Id: 3, + Ticker: "ISO-USD", + MarketId: uint32(3), + AtomicResolution: int32(-9), + DefaultFundingPpm: int32(0), + LiquidityTier: uint32(3), + MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, + }, + FundingIndex: dtypes.ZeroInt(), + } ) +var TestMarketPerpetuals = []perptypes.Perpetual{ + { + Params: perptypes.PerpetualParams{ + Id: 0, + Ticker: "BTC-USD", + MarketId: uint32(0), + AtomicResolution: int32(-10), + DefaultFundingPpm: int32(0), + LiquidityTier: uint32(0), + MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + }, + FundingIndex: dtypes.ZeroInt(), + }, + { + Params: perptypes.PerpetualParams{ + Id: 1, + Ticker: "ETH-USD", + MarketId: uint32(1), + AtomicResolution: int32(-9), + DefaultFundingPpm: int32(0), + LiquidityTier: uint32(0), + MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + }, + FundingIndex: dtypes.ZeroInt(), + }, + { + Params: perptypes.PerpetualParams{ + Id: 2, + Ticker: "SOL-USD", + MarketId: uint32(2), + AtomicResolution: int32(-9), + DefaultFundingPpm: int32(0), + LiquidityTier: uint32(3), + MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + }, + FundingIndex: dtypes.ZeroInt(), + }, + IsoUsd_IsolatedMarket, +} + // AddPremiumVotes messages. var ( TestAddPremiumVotesMsg = &perptypes.MsgAddPremiumVotes{ diff --git a/protocol/testutil/constants/pricefeed.go b/protocol/testutil/constants/pricefeed.go index 6198676491..47df2ae63a 100644 --- a/protocol/testutil/constants/pricefeed.go +++ b/protocol/testutil/constants/pricefeed.go @@ -13,6 +13,7 @@ var ( MarketId0 = uint32(0) MarketId1 = uint32(1) MarketId2 = uint32(2) + MarketId3 = uint32(3) MarketId7 = uint32(7) MarketId8 = uint32(8) @@ -269,23 +270,32 @@ var ( Exchange2_Price2_TimeT, }, }, + { + MarketId: MarketId3, + ExchangePrices: []*api.ExchangePrice{ + Exchange3_Price3_TimeT, + }, + }, } AtTimeTSingleExchangeSmoothedPrices = map[uint32]uint64{ MarketId0: Exchange0_Price4_TimeT.Price, MarketId1: Exchange1_Price1_TimeT.Price, MarketId2: Exchange2_Price2_TimeT.Price, + MarketId3: Exchange3_Price3_TimeT.Price, } AtTimeTSingleExchangeSmoothedPricesPlus10 = map[uint32]uint64{ MarketId0: Exchange0_Price4_TimeT.Price + 10, MarketId1: Exchange1_Price1_TimeT.Price + 10, MarketId2: Exchange2_Price2_TimeT.Price + 10, + MarketId3: Exchange3_Price3_TimeT.Price + 10, } AtTimeTSingleExchangeSmoothedPricesPlus7 = map[uint32]uint64{ MarketId0: Exchange0_Price4_TimeT.Price + 7, MarketId1: Exchange1_Price1_TimeT.Price + 7, MarketId2: Exchange2_Price2_TimeT.Price + 7, + MarketId3: Exchange3_Price3_TimeT.Price + 7, } MixedTimePriceUpdate = []*api.MarketPriceUpdate{ diff --git a/protocol/testutil/constants/prices.go b/protocol/testutil/constants/prices.go index 61a2702def..022d1793c5 100644 --- a/protocol/testutil/constants/prices.go +++ b/protocol/testutil/constants/prices.go @@ -26,6 +26,7 @@ const ( MaticUsdPair = "MATIC-USD" SolUsdPair = "SOL-USD" LtcUsdPair = "LTC-USD" + IsoUsdPair = "ISO-USD" BtcUsdExponent = -5 EthUsdExponent = -6 @@ -34,6 +35,7 @@ const ( CrvUsdExponent = -10 SolUsdExponent = -8 LtcUsdExponent = -7 + IsoUsdExponent = -8 CoinbaseExchangeName = "Coinbase" BinanceExchangeName = "Binance" @@ -201,6 +203,15 @@ var TestMarketExchangeConfigs = map[pricefeedclient.MarketId]string{ } ] }`, + exchange_config.MARKET_ISO_USD: `{ + "exchanges": [ + { + "exchangeName": "Binance", + "ticker": "ISOUSDT", + "adjustByMarket": "USDT-USD" + } + ] + }`, } var TestMarketParams = []types.MarketParam{ @@ -228,6 +239,14 @@ var TestMarketParams = []types.MarketParam{ MinPriceChangePpm: 50, ExchangeConfigJson: TestMarketExchangeConfigs[exchange_config.MARKET_SOL_USD], }, + { + Id: 3, + Pair: IsoUsdPair, + Exponent: IsoUsdExponent, + MinExchanges: 1, + MinPriceChangePpm: 50, + ExchangeConfigJson: TestMarketExchangeConfigs[exchange_config.MARKET_ISO_USD], + }, } var TestMarketPrices = []types.MarketPrice{ @@ -246,12 +265,18 @@ var TestMarketPrices = []types.MarketPrice{ Exponent: SolUsdExponent, Price: FiveBillion, // 50$ == 1 SOL }, + { + Id: 3, + Exponent: IsoUsdExponent, + Price: FiveBillion, // 50$ == 1 ISO + }, } var TestMarketIdsToExponents = map[uint32]int32{ 0: BtcUsdExponent, 1: EthUsdExponent, 2: SolUsdExponent, + 3: IsoUsdExponent, } var TestPricesGenesisState = types.GenesisState{ @@ -264,6 +289,7 @@ var ( types.NewMarketPriceUpdate(MarketId0, Price5), types.NewMarketPriceUpdate(MarketId1, Price6), types.NewMarketPriceUpdate(MarketId2, Price7), + types.NewMarketPriceUpdate(MarketId3, Price4), } // `MsgUpdateMarketPrices`. diff --git a/protocol/testutil/daemons/pricefeed/exchange_config/market_id.go b/protocol/testutil/daemons/pricefeed/exchange_config/market_id.go index d463702dea..2123f76b1c 100644 --- a/protocol/testutil/daemons/pricefeed/exchange_config/market_id.go +++ b/protocol/testutil/daemons/pricefeed/exchange_config/market_id.go @@ -75,6 +75,9 @@ const ( // MARKET_TEST_USD is the id used for the TEST-USD market pair. MARKET_TEST_USD types.MarketId = 33 + // Arbitrary isolated market + MARKET_ISO_USD types.MarketId = 999_999 + // Non-trading markets. // MARKET_USDT_USD is the id for the USDT-USD market pair. MARKET_USDT_USD types.MarketId = 1_000_000 diff --git a/protocol/testutil/keeper/perpetuals.go b/protocol/testutil/keeper/perpetuals.go index a7c9c3bef7..622ffc25d2 100644 --- a/protocol/testutil/keeper/perpetuals.go +++ b/protocol/testutil/keeper/perpetuals.go @@ -167,6 +167,22 @@ func PopulateTestPremiumStore( } } +func CreateTestPerpetuals(t *testing.T, ctx sdk.Context, k *keeper.Keeper) { + for _, p := range constants.TestMarketPerpetuals { + _, err := k.CreatePerpetual( + ctx, + p.Params.Id, + p.Params.Ticker, + p.Params.MarketId, + p.Params.AtomicResolution, + p.Params.DefaultFundingPpm, + p.Params.LiquidityTier, + p.Params.MarketType, + ) + require.NoError(t, err) + } +} + func CreateTestLiquidityTiers(t *testing.T, ctx sdk.Context, k *keeper.Keeper) { for _, l := range constants.LiquidityTiers { _, err := k.SetLiquidityTier( diff --git a/protocol/x/clob/abci.go b/protocol/x/clob/abci.go index ad4bfd9c0d..bc26300040 100644 --- a/protocol/x/clob/abci.go +++ b/protocol/x/clob/abci.go @@ -119,7 +119,7 @@ func EndBlocker( // Emit relevant metrics at the end of every block. metrics.SetGauge( metrics.InsuranceFundBalance, - metrics.GetMetricValueFromBigInt(keeper.GetInsuranceFundBalance(ctx)), + metrics.GetMetricValueFromBigInt(keeper.GetCrossInsuranceFundBalance(ctx)), ) } diff --git a/protocol/x/clob/keeper/deleveraging.go b/protocol/x/clob/keeper/deleveraging.go index 0e83249b8b..87ed66f59a 100644 --- a/protocol/x/clob/keeper/deleveraging.go +++ b/protocol/x/clob/keeper/deleveraging.go @@ -6,7 +6,7 @@ import ( "math/big" "time" - types2 "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/telemetry" @@ -130,20 +130,36 @@ func (k Keeper) MaybeDeleverageSubaccount( return quantumsDeleveraged, err } -// GetInsuranceFundBalance returns the current balance of the insurance fund (in quote quantums). +// GetInsuranceFundBalance returns the current balance of the specific insurance fund based on the +// perpetual (in quote quantums). // This calls the Bank Keeper’s GetBalance() function for the Module Address of the insurance fund. -func (k Keeper) GetInsuranceFundBalance( - ctx sdk.Context, -) ( - balance *big.Int, -) { +func (k Keeper) GetInsuranceFundBalance(ctx sdk.Context, perpetualId uint32) (balance *big.Int) { usdcAsset, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) if !exists { panic("GetInsuranceFundBalance: Usdc asset not found in state") } + insuranceFundAddr, err := k.perpetualsKeeper.GetInsuranceFundModuleAddress(ctx, perpetualId) + if err != nil { + return nil + } insuranceFundBalance := k.bankKeeper.GetBalance( ctx, - types2.InsuranceFundModuleAddress, + insuranceFundAddr, + usdcAsset.Denom, + ) + + // Return as big.Int. + return insuranceFundBalance.Amount.BigInt() +} + +func (k Keeper) GetCrossInsuranceFundBalance(ctx sdk.Context) (balance *big.Int) { + usdcAsset, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) + if !exists { + panic("GetCrossInsuranceFundBalance: Usdc asset not found in state") + } + insuranceFundBalance := k.bankKeeper.GetBalance( + ctx, + perptypes.InsuranceFundModuleAddress, usdcAsset.Denom, ) @@ -261,10 +277,7 @@ func (k Keeper) GateWithdrawalsIfNegativeTncSubaccountSeen( // fund delta. Specifically, this function returns true if either of the following are true: // - The `insuranceFundDelta` is non-negative. // - The insurance fund balance + `insuranceFundDelta` is greater-than-or-equal-to 0. -func (k Keeper) IsValidInsuranceFundDelta( - ctx sdk.Context, - insuranceFundDelta *big.Int, -) bool { +func (k Keeper) IsValidInsuranceFundDelta(ctx sdk.Context, insuranceFundDelta *big.Int, perpetualId uint32) bool { // Non-negative insurance fund deltas are valid. if insuranceFundDelta.Sign() >= 0 { return true @@ -272,7 +285,7 @@ func (k Keeper) IsValidInsuranceFundDelta( // The insurance fund delta is valid if the insurance fund balance is non-negative after adding // the delta. - currentInsuranceFundBalance := k.GetInsuranceFundBalance(ctx) + currentInsuranceFundBalance := k.GetInsuranceFundBalance(ctx, perpetualId) return new(big.Int).Add(currentInsuranceFundBalance, insuranceFundDelta).Sign() >= 0 } diff --git a/protocol/x/clob/keeper/deleveraging_test.go b/protocol/x/clob/keeper/deleveraging_test.go index da6a119876..1bfeda123a 100644 --- a/protocol/x/clob/keeper/deleveraging_test.go +++ b/protocol/x/clob/keeper/deleveraging_test.go @@ -34,6 +34,8 @@ func TestGetInsuranceFundBalance(t *testing.T) { // Setup assets []assettypes.Asset insuranceFundBalance *big.Int + perpetualId uint32 + perpetual *perptypes.Perpetual // Expectations. expectedInsuranceFundBalance *big.Int @@ -43,6 +45,7 @@ func TestGetInsuranceFundBalance(t *testing.T) { assets: []assettypes.Asset{ *constants.Usdc, }, + perpetualId: 0, insuranceFundBalance: new(big.Int), expectedInsuranceFundBalance: big.NewInt(0), }, @@ -50,6 +53,7 @@ func TestGetInsuranceFundBalance(t *testing.T) { assets: []assettypes.Asset{ *constants.Usdc, }, + perpetualId: 0, insuranceFundBalance: big.NewInt(100), expectedInsuranceFundBalance: big.NewInt(100), }, @@ -57,6 +61,7 @@ func TestGetInsuranceFundBalance(t *testing.T) { assets: []assettypes.Asset{ *constants.Usdc, }, + perpetualId: 0, insuranceFundBalance: new(big.Int).Add( new(big.Int).SetUint64(math.MaxUint64), new(big.Int).SetUint64(math.MaxUint64), @@ -66,8 +71,25 @@ func TestGetInsuranceFundBalance(t *testing.T) { new(big.Int).SetUint64(math.MaxUint64), ), }, + "can get zero balance - isolated market": { + assets: []assettypes.Asset{ + *constants.Usdc, + }, + perpetualId: 3, // Isolated market. + insuranceFundBalance: new(big.Int), + expectedInsuranceFundBalance: big.NewInt(0), + }, + "can get positive balance - isolated market": { + assets: []assettypes.Asset{ + *constants.Usdc, + }, + perpetualId: 3, // Isolated market. + insuranceFundBalance: big.NewInt(100), + expectedInsuranceFundBalance: big.NewInt(100), + }, "panics when asset not found in state": { assets: []assettypes.Asset{}, + perpetualId: 0, expectedError: errors.New("GetInsuranceFundBalance: Usdc asset not found in state"), }, } @@ -79,6 +101,15 @@ func TestGetInsuranceFundBalance(t *testing.T) { bankMock := &mocks.BankKeeper{} ks := keepertest.NewClobKeepersTestContext(t, memClob, bankMock, &mocks.IndexerEventManager{}) + ctx := ks.Ctx.WithIsCheckTx(true) + // Create the default markets. + keepertest.CreateTestMarkets(t, ctx, ks.PricesKeeper) + + // Create liquidity tiers. + keepertest.CreateTestLiquidityTiers(t, ctx, ks.PerpetualsKeeper) + + keepertest.CreateTestPerpetuals(t, ctx, ks.PerpetualsKeeper) + for _, a := range tc.assets { _, err := ks.AssetsKeeper.CreateAsset( ks.Ctx, @@ -93,11 +124,13 @@ func TestGetInsuranceFundBalance(t *testing.T) { require.NoError(t, err) } + insuranceFundAddr, err := ks.PerpetualsKeeper.GetInsuranceFundModuleAddress(ks.Ctx, tc.perpetualId) + require.NoError(t, err) if tc.insuranceFundBalance != nil { bankMock.On( "GetBalance", mock.Anything, - perptypes.InsuranceFundModuleAddress, + insuranceFundAddr, constants.Usdc.Denom, ).Return( sdk.NewCoin(constants.Usdc.Denom, sdkmath.NewIntFromBigInt(tc.insuranceFundBalance)), @@ -109,14 +142,14 @@ func TestGetInsuranceFundBalance(t *testing.T) { t, tc.expectedError.Error(), func() { - ks.ClobKeeper.GetInsuranceFundBalance(ks.Ctx) + ks.ClobKeeper.GetInsuranceFundBalance(ks.Ctx, tc.perpetualId) }, ) } else { require.Equal( t, tc.expectedInsuranceFundBalance, - ks.ClobKeeper.GetInsuranceFundBalance(ks.Ctx), + ks.ClobKeeper.GetInsuranceFundBalance(ks.Ctx, tc.perpetualId), ) } }) @@ -192,6 +225,14 @@ func TestIsValidInsuranceFundDelta(t *testing.T) { err := keepertest.CreateUsdcAsset(ks.Ctx, ks.AssetsKeeper) require.NoError(t, err) + ctx := ks.Ctx.WithIsCheckTx(true) + keepertest.CreateTestMarkets(t, ctx, ks.PricesKeeper) + + // Create liquidity tiers. + keepertest.CreateTestLiquidityTiers(t, ctx, ks.PerpetualsKeeper) + + keepertest.CreateTestPerpetuals(t, ctx, ks.PerpetualsKeeper) + bankMock.On( "GetBalance", mock.Anything, @@ -203,10 +244,7 @@ func TestIsValidInsuranceFundDelta(t *testing.T) { require.Equal( t, tc.expectedIsValidInsuranceFundDelta, - ks.ClobKeeper.IsValidInsuranceFundDelta( - ks.Ctx, - tc.insuranceFundDelta, - ), + ks.ClobKeeper.IsValidInsuranceFundDelta(ks.Ctx, tc.insuranceFundDelta, 0), ) }) } diff --git a/protocol/x/clob/keeper/liquidations.go b/protocol/x/clob/keeper/liquidations.go index 1fea74bedc..b96284095b 100644 --- a/protocol/x/clob/keeper/liquidations.go +++ b/protocol/x/clob/keeper/liquidations.go @@ -1133,7 +1133,7 @@ func (k Keeper) validateMatchedLiquidation( // Validate that processing the liquidation fill does not leave insufficient funds // in the insurance fund (such that the liquidation couldn't have possibly continued). - if !k.IsValidInsuranceFundDelta(ctx, insuranceFundDelta) { + if !k.IsValidInsuranceFundDelta(ctx, insuranceFundDelta, perpetualId) { log.DebugLog(ctx, "ProcessMatches: insurance fund has insufficient balance to process the liquidation.") return nil, errorsmod.Wrapf( types.ErrInsuranceFundHasInsufficientFunds, diff --git a/protocol/x/clob/keeper/liquidations_test.go b/protocol/x/clob/keeper/liquidations_test.go index e44f6804e7..a69a7c817d 100644 --- a/protocol/x/clob/keeper/liquidations_test.go +++ b/protocol/x/clob/keeper/liquidations_test.go @@ -242,10 +242,10 @@ func TestPlacePerpetualLiquidation(t *testing.T) { mock.Anything, ).Return(nil) mockBankKeeper.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), mock.Anything, ).Return(nil) // Fee collector does not have any funds. @@ -873,6 +873,13 @@ func TestPlacePerpetualLiquidation_PreexistingLiquidation(t *testing.T) { mock.Anything, mock.Anything, ).Return(nil) + bk.On( + "SendCoins", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil) bk.On( "GetBalance", mock.Anything, @@ -954,6 +961,13 @@ func TestPlacePerpetualLiquidation_PreexistingLiquidation(t *testing.T) { mock.Anything, mock.Anything, ).Return(nil) + bk.On( + "SendCoins", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil) bk.On( "GetBalance", mock.Anything, @@ -1079,6 +1093,13 @@ func TestPlacePerpetualLiquidation_PreexistingLiquidation(t *testing.T) { mock.Anything, mock.Anything, ).Return(nil) + bankKeeper.On( + "SendCoins", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil) bankKeeper.On( "GetBalance", mock.Anything, @@ -1961,6 +1982,13 @@ func TestPlacePerpetualLiquidation_Deleveraging(t *testing.T) { mock.Anything, mock.Anything, ).Return(nil) + bankKeeper.On( + "SendCoins", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil) bankKeeper.On( "GetBalance", mock.Anything, @@ -4597,6 +4625,13 @@ func TestMaybeGetLiquidationOrder(t *testing.T) { // Setup keeper state. memClob := memclob.NewMemClobPriceTimePriority(false) mockBankKeeper := &mocks.BankKeeper{} + mockBankKeeper.On( + "SendCoins", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil) mockBankKeeper.On( "SendCoinsFromModuleToModule", mock.Anything, diff --git a/protocol/x/clob/keeper/mev_test.go b/protocol/x/clob/keeper/mev_test.go index 4697be5bd3..9c12550d27 100644 --- a/protocol/x/clob/keeper/mev_test.go +++ b/protocol/x/clob/keeper/mev_test.go @@ -836,6 +836,13 @@ func TestRecordMevMetrics(t *testing.T) { mock.Anything, mock.Anything, ).Return(nil) + mockBankKeeper.On( + "SendCoins", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil) mockBankKeeper.On( "GetBalance", mock.Anything, diff --git a/protocol/x/clob/keeper/process_operations_liquidations_test.go b/protocol/x/clob/keeper/process_operations_liquidations_test.go index 382d9a0a76..ef86f581f1 100644 --- a/protocol/x/clob/keeper/process_operations_liquidations_test.go +++ b/protocol/x/clob/keeper/process_operations_liquidations_test.go @@ -1,12 +1,13 @@ package keeper_test import ( - storetypes "cosmossdk.io/store/types" "errors" "fmt" "math" "testing" + storetypes "cosmossdk.io/store/types" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -199,10 +200,10 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(10_000_000)), ).Return(nil) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Subaccount pays $250 to insurance fund for liquidating 1 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(250_000_000)), ).Return(nil).Once() @@ -283,10 +284,10 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.Anything, ).Return(sdk.NewCoin("USDC", sdkmath.NewIntFromUint64(math.MaxUint64))) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - perptypes.InsuranceFundName, - satypes.ModuleName, + authtypes.NewModuleAddress(perptypes.InsuranceFundName), + authtypes.NewModuleAddress(satypes.ModuleName), // Insurance fund covers $1 loss for liquidating 1 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(1_000_000)), ).Return(nil).Once() @@ -363,10 +364,10 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(2_500_000)), ).Return(nil) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Subaccount pays $62.5 to insurance fund for liquidating 0.25 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(62_500_000)), ).Return(nil).Twice() @@ -467,10 +468,10 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.Anything, ).Return(sdk.NewCoin("USDC", sdkmath.NewIntFromUint64(math.MaxUint64))) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - perptypes.InsuranceFundName, - satypes.ModuleName, + authtypes.NewModuleAddress(perptypes.InsuranceFundName), + authtypes.NewModuleAddress(satypes.ModuleName), // Insurance fund covers $0.25 loss for liquidating 0.25 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(250_000)), ).Return(nil).Twice() @@ -572,18 +573,18 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.Anything, ).Return(sdk.NewCoin("USDC", sdkmath.NewIntFromUint64(math.MaxUint64))) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Pays insurance fund $0.75 for liquidating 0.75 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(750_000)), ).Return(nil).Once() bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - perptypes.InsuranceFundName, - satypes.ModuleName, + authtypes.NewModuleAddress(perptypes.InsuranceFundName), + authtypes.NewModuleAddress(satypes.ModuleName), // Insurance fund covers $0.25 loss for liquidating 0.25 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(250_000)), ).Return(nil).Once() @@ -676,19 +677,19 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.Anything, ).Return(sdk.NewCoin("USDC", sdkmath.NewIntFromUint64(math.MaxUint64))) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Pays insurance fund $0.378735 (capped by MaxLiquidationFeePpm) // for liquidating 0.75 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(378_735)), ).Return(nil).Once() bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Pays insurance fund $0.121265. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(121_265)), ).Return(nil).Once() @@ -783,10 +784,10 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(5_000_000)), ).Return(nil) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Subaccount pays $125 to insurance fund for liquidating 0.5 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(125_000_000)), ).Return(nil).Once() @@ -896,10 +897,10 @@ func TestProcessProposerMatches_Liquidation_Success(t *testing.T) { mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(1)), ).Return(nil) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(25)), ).Return(nil) }, @@ -1269,10 +1270,10 @@ func TestProcessProposerMatches_Liquidation_Failure(t *testing.T) { mock.Anything, ).Return(fmt.Errorf("transfer failed")) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, mock.Anything, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(perptypes.InsuranceFundName), mock.Anything, ).Return(nil) }, diff --git a/protocol/x/clob/keeper/process_operations_long_term_test.go b/protocol/x/clob/keeper/process_operations_long_term_test.go index 6dd2c4c83d..13f1f8de11 100644 --- a/protocol/x/clob/keeper/process_operations_long_term_test.go +++ b/protocol/x/clob/keeper/process_operations_long_term_test.go @@ -578,10 +578,10 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(10_000_000)), ).Return(nil) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Subaccount pays $250 to insurance fund for liquidating 1 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(250_000_000)), ).Return(nil).Once() @@ -666,10 +666,10 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(10_000_000)), ).Return(nil) bk.On( - "SendCoinsFromModuleToModule", + "SendCoins", mock.Anything, - satypes.ModuleName, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(satypes.ModuleName), + authtypes.NewModuleAddress(perptypes.InsuranceFundName), // Subaccount pays $250 to insurance fund for liquidating 1 BTC. mock.MatchedBy(testutil_bank.MatchUsdcOfAmount(250_000_000)), ).Return(nil).Once() diff --git a/protocol/x/clob/keeper/process_operations_test.go b/protocol/x/clob/keeper/process_operations_test.go index 1bd06c54ee..a631110803 100644 --- a/protocol/x/clob/keeper/process_operations_test.go +++ b/protocol/x/clob/keeper/process_operations_test.go @@ -2159,6 +2159,13 @@ func setupProcessProposerOperationsTestCase( mock.Anything, mock.Anything, ).Return(nil) + mockBankKeeper.On( + "SendCoins", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil) mockBankKeeper.On( "GetBalance", mock.Anything, diff --git a/protocol/x/clob/keeper/process_single_match.go b/protocol/x/clob/keeper/process_single_match.go index 8a74c90dd2..189465d3a5 100644 --- a/protocol/x/clob/keeper/process_single_match.go +++ b/protocol/x/clob/keeper/process_single_match.go @@ -432,7 +432,7 @@ func (k Keeper) persistMatchedOrders( ) } - if err := k.subaccountsKeeper.TransferInsuranceFundPayments(ctx, insuranceFundDelta); err != nil { + if err := k.subaccountsKeeper.TransferInsuranceFundPayments(ctx, insuranceFundDelta, perpetualId); err != nil { return takerUpdateResult, makerUpdateResult, err } diff --git a/protocol/x/clob/types/expected_keepers.go b/protocol/x/clob/types/expected_keepers.go index d59acece75..81b4aef392 100644 --- a/protocol/x/clob/types/expected_keepers.go +++ b/protocol/x/clob/types/expected_keepers.go @@ -71,6 +71,7 @@ type SubaccountsKeeper interface { TransferInsuranceFundPayments( ctx sdk.Context, amount *big.Int, + perpetualId uint32, ) error } @@ -139,6 +140,7 @@ type PerpetualsKeeper interface { err error, ) MaybeProcessNewFundingTickEpoch(ctx sdk.Context) + GetInsuranceFundModuleAddress(ctx sdk.Context, perpetualId uint32) (sdk.AccAddress, error) } type PricesKeeper interface { diff --git a/protocol/x/clob/types/liquidations_keeper.go b/protocol/x/clob/types/liquidations_keeper.go index 07281ad3d2..b649a8e6c8 100644 --- a/protocol/x/clob/types/liquidations_keeper.go +++ b/protocol/x/clob/types/liquidations_keeper.go @@ -51,11 +51,7 @@ type LiquidationsKeeper interface { fillablePrice *big.Rat, err error, ) - GetInsuranceFundBalance( - ctx sdk.Context, - ) ( - balance *big.Int, - ) + GetInsuranceFundBalance(ctx sdk.Context, perpetualId uint32) (balance *big.Int) GetLiquidationInsuranceFundDelta( ctx sdk.Context, subaccountId satypes.SubaccountId, diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index 01fbda8e5e..eda836e508 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -7,6 +7,8 @@ import ( "sort" "time" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + storetypes "cosmossdk.io/store/types" "github.com/dydxprotocol/v4-chain/protocol/daemons/pricefeed/client/constants" @@ -29,6 +31,33 @@ import ( gometrics "github.com/hashicorp/go-metrics" ) +// 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". +func (k Keeper) GetInsuranceFundName(ctx sdk.Context, perpetualId uint32) (string, error) { + perpetual, err := k.GetPerpetual(ctx, perpetualId) + if err != nil { + return "", err + } + + if perpetual.Params.MarketType == types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED { + return types.InsuranceFundName + ":" + lib.UintToString(perpetualId), nil + } else if perpetual.Params.MarketType == types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS { + return types.InsuranceFundName, nil + } + + panic(fmt.Sprintf("invalid market type %v for perpetual %d", perpetual.Params.MarketType, perpetualId)) +} + +// GetInsuranceFundModuleAddress returns the address of the insurance fund account for a given perpetual. +func (k Keeper) GetInsuranceFundModuleAddress(ctx sdk.Context, perpetualId uint32) (sdk.AccAddress, error) { + insuranceFundName, err := k.GetInsuranceFundName(ctx, perpetualId) + if err != nil { + return nil, err + } + return authtypes.NewModuleAddress(insuranceFundName), nil +} + // CreatePerpetual creates a new perpetual in the store. // Returns an error if any of the perpetual fields fail validation, // or if the `marketId` does not exist. diff --git a/protocol/x/prices/keeper/msg_server_update_market_prices_test.go b/protocol/x/prices/keeper/msg_server_update_market_prices_test.go index da97038947..145d363cbe 100644 --- a/protocol/x/prices/keeper/msg_server_update_market_prices_test.go +++ b/protocol/x/prices/keeper/msg_server_update_market_prices_test.go @@ -1,10 +1,11 @@ package keeper_test import ( - errorsmod "cosmossdk.io/errors" "errors" "testing" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" "github.com/dydxprotocol/v4-chain/protocol/daemons/pricefeed/api" "github.com/dydxprotocol/v4-chain/protocol/lib" @@ -46,6 +47,7 @@ func TestUpdateMarketPrices_Valid(t *testing.T) { constants.MarketId0: constants.FiveBillion, // no change constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Multiple updates": { @@ -55,6 +57,7 @@ func TestUpdateMarketPrices_Valid(t *testing.T) { constants.MarketId0: constants.Price5, constants.MarketId1: constants.Price6, constants.MarketId2: constants.Price7, + constants.MarketId3: constants.Price4, }, }, "Towards index price = true (current < update < index price)": { @@ -77,6 +80,7 @@ func TestUpdateMarketPrices_Valid(t *testing.T) { constants.MarketId0: price_5_005_000_000, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Index price crossing = true (price increase), old_ticks > 1, new_ticks <= sqrt(old_ticks) = true": { @@ -99,6 +103,7 @@ func TestUpdateMarketPrices_Valid(t *testing.T) { constants.MarketId0: price_5_005_000_000, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Index price crossing = true (price decrease), old_ticks > 1, new_ticks <= sqrt(old_ticks) = true": { @@ -121,6 +126,7 @@ func TestUpdateMarketPrices_Valid(t *testing.T) { constants.MarketId0: price_4_995_000_000, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Index price crossing = true (price increase), old_ticks <= 1, new_ticks <= old_ticks = true": { @@ -143,6 +149,7 @@ func TestUpdateMarketPrices_Valid(t *testing.T) { constants.MarketId0: price_5_000_500_000, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Index price crossing = true (price decrease), old_ticks <= 1, new_ticks <= old_ticks = true": { @@ -165,6 +172,7 @@ func TestUpdateMarketPrices_Valid(t *testing.T) { constants.MarketId0: price_4_999_500_000, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, } @@ -217,6 +225,7 @@ func TestUpdateMarketPrices_SkipNonDeterministicCheck_Valid(t *testing.T) { constants.MarketId0: 11, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Index price trends in the opposite direction of update price from current price, but still updates state": { @@ -239,6 +248,7 @@ func TestUpdateMarketPrices_SkipNonDeterministicCheck_Valid(t *testing.T) { constants.MarketId0: price_4_995_000_001, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Index price crossing = true, old_ticks > 1, new_ticks <= sqrt(old_ticks) = false": { @@ -261,6 +271,7 @@ func TestUpdateMarketPrices_SkipNonDeterministicCheck_Valid(t *testing.T) { constants.MarketId0: price_5_015_000_000, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, "Index price crossing = true, old_ticks <= 1, new_ticks <= old_ticks = false": { @@ -283,6 +294,7 @@ func TestUpdateMarketPrices_SkipNonDeterministicCheck_Valid(t *testing.T) { constants.MarketId0: price_5_015_000_000, constants.MarketId1: constants.ThreeBillion, // no change constants.MarketId2: constants.FiveBillion, // no change + constants.MarketId3: constants.FiveBillion, // no change }, }, } diff --git a/protocol/x/prices/keeper/smoothed_price_test.go b/protocol/x/prices/keeper/smoothed_price_test.go index 5d02a1fae6..a8ce09400e 100644 --- a/protocol/x/prices/keeper/smoothed_price_test.go +++ b/protocol/x/prices/keeper/smoothed_price_test.go @@ -2,12 +2,13 @@ package keeper_test import ( "fmt" + "testing" + "github.com/dydxprotocol/v4-chain/protocol/daemons/pricefeed/api" "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/stretchr/testify/require" - "testing" ) var ( @@ -57,6 +58,7 @@ func TestUpdateSmoothedPrices(t *testing.T) { constants.MarketId0: constants.Exchange0_Price4_TimeT.Price, constants.MarketId1: constants.Exchange1_Price1_TimeT.Price + 7, constants.MarketId2: constants.Exchange2_Price2_TimeT.Price + 35, + constants.MarketId3: constants.Exchange3_Price3_TimeT.Price, constants.MarketId7: constants.Price1, }, linearInterpolateFunc: lib.Uint64LinearInterpolate, @@ -78,18 +80,21 @@ func TestUpdateSmoothedPrices(t *testing.T) { linearInterpolateFunc: errInterpolator, expectedErr: "Error updating smoothed price for market 0: error while interpolating\n" + "Error updating smoothed price for market 1: error while interpolating\n" + - "Error updating smoothed price for market 2: error while interpolating", + "Error updating smoothed price for market 2: error while interpolating\n" + + "Error updating smoothed price for market 3: error while interpolating", expectedResult: constants.AtTimeTSingleExchangeSmoothedPricesPlus10, // no change }, "Single interpolation error - returns error, continues updating other markets": { indexPrices: constants.AtTimeTSingleExchangePriceUpdate, smoothedPrices: constants.AtTimeTSingleExchangeSmoothedPricesPlus10, linearInterpolateFunc: alternatingErrInterpolator(), - expectedErr: "Error updating smoothed price for market 1: error while interpolating", + expectedErr: "Error updating smoothed price for market 1: error while interpolating\n" + + "Error updating smoothed price for market 3: error while interpolating", expectedResult: map[uint32]uint64{ constants.MarketId0: constants.AtTimeTSingleExchangeSmoothedPricesPlus7[constants.MarketId0], // update constants.MarketId1: constants.AtTimeTSingleExchangeSmoothedPricesPlus10[constants.MarketId1], // no change constants.MarketId2: constants.AtTimeTSingleExchangeSmoothedPricesPlus7[constants.MarketId2], // update + constants.MarketId3: constants.AtTimeTSingleExchangeSmoothedPricesPlus10[constants.MarketId3], // update }, // no change }, } diff --git a/protocol/x/prices/keeper/update_price_test.go b/protocol/x/prices/keeper/update_price_test.go index 676b5910ed..c339289509 100644 --- a/protocol/x/prices/keeper/update_price_test.go +++ b/protocol/x/prices/keeper/update_price_test.go @@ -200,6 +200,7 @@ func TestGetValidMarketPriceUpdates(t *testing.T) { types.NewMarketPriceUpdate(constants.MarketId0, constants.Price4), types.NewMarketPriceUpdate(constants.MarketId1, constants.Price1+1), types.NewMarketPriceUpdate(constants.MarketId2, constants.Price2), + types.NewMarketPriceUpdate(constants.MarketId3, constants.Price3), }, }, }, @@ -230,6 +231,7 @@ func TestGetValidMarketPriceUpdates(t *testing.T) { types.NewMarketPriceUpdate(constants.MarketId0, constants.Price4), types.NewMarketPriceUpdate(constants.MarketId1, constants.Price1), types.NewMarketPriceUpdate(constants.MarketId2, constants.Price2), + types.NewMarketPriceUpdate(constants.MarketId3, constants.Price3), }, }, }, @@ -247,6 +249,7 @@ func TestGetValidMarketPriceUpdates(t *testing.T) { MarketPriceUpdates: []*types.MsgUpdateMarketPrices_MarketPrice{ types.NewMarketPriceUpdate(constants.MarketId1, constants.Price1), types.NewMarketPriceUpdate(constants.MarketId2, constants.Price2), + types.NewMarketPriceUpdate(constants.MarketId3, constants.Price3), }, }, }, diff --git a/protocol/x/prices/keeper/validate_market_price_updates_test.go b/protocol/x/prices/keeper/validate_market_price_updates_test.go index 88b94f41bf..58e44ef5ff 100644 --- a/protocol/x/prices/keeper/validate_market_price_updates_test.go +++ b/protocol/x/prices/keeper/validate_market_price_updates_test.go @@ -1,9 +1,10 @@ package keeper_test import ( - errorsmod "cosmossdk.io/errors" "testing" + errorsmod "cosmossdk.io/errors" + "github.com/dydxprotocol/v4-chain/protocol/daemons/pricefeed/api" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" @@ -372,7 +373,9 @@ func TestGetMarketsMissingFromPriceUpdates(t *testing.T) { indexPrices: constants.AtTimeTSingleExchangePriceUpdate, smoothedIndexPrices: constants.AtTimeTSingleExchangeSmoothedPrices, // The returned market ids must be sorted. - expectedMarketIds: []uint32{constants.MarketId0, constants.MarketId1, constants.MarketId2}, + expectedMarketIds: []uint32{ + constants.MarketId0, constants.MarketId1, constants.MarketId2, constants.MarketId3, + }, }, "Non-empty proposed updates, Empty local updates": { msgUpdateMarketPrices: constants.ValidMarketPriceUpdates, @@ -388,6 +391,7 @@ func TestGetMarketsMissingFromPriceUpdates(t *testing.T) { msgUpdateMarketPrices: []*types.MsgUpdateMarketPrices_MarketPrice{ types.NewMarketPriceUpdate(constants.MarketId0, constants.Price5), types.NewMarketPriceUpdate(constants.MarketId1, constants.Price6), + types.NewMarketPriceUpdate(constants.MarketId3, constants.Price7), }, indexPrices: constants.AtTimeTSingleExchangePriceUpdate, smoothedIndexPrices: constants.AtTimeTSingleExchangeSmoothedPrices, @@ -400,7 +404,7 @@ func TestGetMarketsMissingFromPriceUpdates(t *testing.T) { indexPrices: constants.AtTimeTSingleExchangePriceUpdate, smoothedIndexPrices: constants.AtTimeTSingleExchangeSmoothedPrices, // The returned market ids must be sorted. - expectedMarketIds: []uint32{constants.MarketId0, constants.MarketId2}, + expectedMarketIds: []uint32{constants.MarketId0, constants.MarketId2, constants.MarketId3}, }, } for name, tc := range tests { diff --git a/protocol/x/subaccounts/keeper/transfer.go b/protocol/x/subaccounts/keeper/transfer.go index af130faa8c..6e6ef31e3b 100644 --- a/protocol/x/subaccounts/keeper/transfer.go +++ b/protocol/x/subaccounts/keeper/transfer.go @@ -3,8 +3,6 @@ package keeper import ( "math/big" - perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -260,6 +258,7 @@ func (k Keeper) TransferFeesToFeeCollectorModule( func (k Keeper) TransferInsuranceFundPayments( ctx sdk.Context, insuranceFundDelta *big.Int, + perpetualId uint32, ) error { if insuranceFundDelta.Sign() == 0 { return nil @@ -278,7 +277,10 @@ func (k Keeper) TransferInsuranceFundPayments( // Determine the sender and receiver. // Send coins from `subaccounts` to the `insurance_fund` module account by default. fromModule := types.ModuleName - toModule := perptypes.InsuranceFundName + toModule, err := k.perpetualsKeeper.GetInsuranceFundName(ctx, perpetualId) + if err != nil { + panic(err) + } if insuranceFundDelta.Sign() < 0 { // Insurance fund needs to cover losses from liquidations. @@ -286,10 +288,12 @@ func (k Keeper) TransferInsuranceFundPayments( fromModule, toModule = toModule, fromModule } - return k.bankKeeper.SendCoinsFromModuleToModule( + // Use SendCoins API instead of SendCoinsFromModuleToModule since we don't need the + // module account features + return k.bankKeeper.SendCoins( ctx, - fromModule, - toModule, + authtypes.NewModuleAddress(fromModule), + authtypes.NewModuleAddress(toModule), []sdk.Coin{coinToTransfer}, ) } diff --git a/protocol/x/subaccounts/keeper/transfer_test.go b/protocol/x/subaccounts/keeper/transfer_test.go index 0dc46e6c4f..58ed6ff53c 100644 --- a/protocol/x/subaccounts/keeper/transfer_test.go +++ b/protocol/x/subaccounts/keeper/transfer_test.go @@ -662,6 +662,7 @@ func TestTransferInsuranceFundPayments(t *testing.T) { // Module account state. subaccountModuleAccBalance int64 insuranceFundBalance int64 + perpetual perptypes.Perpetual // Transfer details. quantums *big.Int @@ -673,6 +674,7 @@ func TestTransferInsuranceFundPayments(t *testing.T) { expectedInsuranceFundBalance int64 }{ "success - send to insurance fund module account": { + perpetual: constants.BtcUsd_SmallMarginRequirement, insuranceFundBalance: 2500, subaccountModuleAccBalance: 600, quantums: big.NewInt(500), @@ -680,6 +682,7 @@ func TestTransferInsuranceFundPayments(t *testing.T) { expectedInsuranceFundBalance: 3000, // 2500 + 500 }, "success - send from insurance fund module account": { + perpetual: constants.BtcUsd_SmallMarginRequirement, insuranceFundBalance: 2500, subaccountModuleAccBalance: 600, quantums: big.NewInt(-500), @@ -687,13 +690,31 @@ func TestTransferInsuranceFundPayments(t *testing.T) { expectedInsuranceFundBalance: 2000, // 2500 - 500 }, "success - can send zero payment": { + perpetual: constants.BtcUsd_SmallMarginRequirement, insuranceFundBalance: 2500, subaccountModuleAccBalance: 600, quantums: big.NewInt(0), expectedSubaccountsModuleAccBalance: 600, expectedInsuranceFundBalance: 2500, }, + "success - send to isolated insurance fund account": { + perpetual: constants.IsoUsd_IsolatedMarket, + insuranceFundBalance: 2500, + subaccountModuleAccBalance: 600, + quantums: big.NewInt(500), + expectedSubaccountsModuleAccBalance: 100, // 600 - 500 + expectedInsuranceFundBalance: 3000, // 2500 + 500 + }, + "success - send from isolated insurance fund account": { + perpetual: constants.IsoUsd_IsolatedMarket, + insuranceFundBalance: 2500, + subaccountModuleAccBalance: 600, + quantums: big.NewInt(-500), + expectedSubaccountsModuleAccBalance: 1100, // 600 + 500 + expectedInsuranceFundBalance: 2000, // 2500 - 500 + }, "failure - subaccounts module does not have sufficient funds": { + perpetual: constants.BtcUsd_SmallMarginRequirement, insuranceFundBalance: 2500, subaccountModuleAccBalance: 300, quantums: big.NewInt(500), @@ -702,6 +723,16 @@ func TestTransferInsuranceFundPayments(t *testing.T) { expectedErr: sdkerrors.ErrInsufficientFunds, }, "failure - insurance fund does not have sufficient funds": { + perpetual: constants.BtcUsd_SmallMarginRequirement, + insuranceFundBalance: 300, + subaccountModuleAccBalance: 2500, + quantums: big.NewInt(-500), + expectedSubaccountsModuleAccBalance: 2500, + expectedInsuranceFundBalance: 300, + expectedErr: sdkerrors.ErrInsufficientFunds, + }, + "failure - isolated market insurance fund does not have sufficient funds": { + perpetual: constants.IsoUsd_IsolatedMarket, insuranceFundBalance: 300, subaccountModuleAccBalance: 2500, quantums: big.NewInt(-500), @@ -710,6 +741,7 @@ func TestTransferInsuranceFundPayments(t *testing.T) { expectedErr: sdkerrors.ErrInsufficientFunds, }, "panics - asset doesn't exist": { + perpetual: constants.BtcUsd_SmallMarginRequirement, insuranceFundBalance: 1500, skipSetUpUsdc: true, subaccountModuleAccBalance: 500, @@ -723,9 +755,13 @@ func TestTransferInsuranceFundPayments(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - ctx, keeper, pricesKeeper, _, accountKeeper, bankKeeper, assetsKeeper, _, _ := keepertest.SubaccountsKeepers(t, true) + ctx, keeper, pricesKeeper, perpsKeeper, accountKeeper, bankKeeper, assetsKeeper, _, _ := + keepertest.SubaccountsKeepers(t, true) keepertest.CreateTestMarkets(t, ctx, pricesKeeper) + // Create liquidity tiers. + keepertest.CreateTestLiquidityTiers(t, ctx, perpsKeeper) + // Set up Subaccounts module account. auth_testutil.CreateTestModuleAccount(ctx, accountKeeper, types.ModuleName, []string{}) // Set up insurance fund module account. @@ -742,11 +778,26 @@ func TestTransferInsuranceFundPayments(t *testing.T) { }, }) + _, err := perpsKeeper.CreatePerpetual( + ctx, + tc.perpetual.GetId(), + tc.perpetual.Params.Ticker, + tc.perpetual.Params.MarketId, + tc.perpetual.Params.AtomicResolution, + tc.perpetual.Params.DefaultFundingPpm, + tc.perpetual.Params.LiquidityTier, + tc.perpetual.Params.MarketType, + ) + require.NoError(t, err) + + insuranceFundName, err := perpsKeeper.GetInsuranceFundName(ctx, tc.perpetual.GetId()) + require.NoError(t, err) + // Mint asset in the receipt/sender module account for transfer. if tc.insuranceFundBalance > 0 { - err := bank_testutil.FundModuleAccount( + err := bank_testutil.FundAccount( ctx, - perptypes.InsuranceFundName, + authtypes.NewModuleAddress(insuranceFundName), sdk.Coins{ sdk.NewInt64Coin(constants.Usdc.Denom, tc.insuranceFundBalance), }, @@ -779,18 +830,18 @@ func TestTransferInsuranceFundPayments(t *testing.T) { tc.expectedErr.Error(), func() { //nolint:errcheck - keeper.TransferInsuranceFundPayments(ctx, tc.quantums) + keeper.TransferInsuranceFundPayments(ctx, tc.quantums, tc.perpetual.GetId()) }, ) } else { require.ErrorIs( t, - keeper.TransferInsuranceFundPayments(ctx, tc.quantums), + keeper.TransferInsuranceFundPayments(ctx, tc.quantums, tc.perpetual.GetId()), tc.expectedErr, ) } } else { - require.NoError(t, keeper.TransferInsuranceFundPayments(ctx, tc.quantums)) + require.NoError(t, keeper.TransferInsuranceFundPayments(ctx, tc.quantums, tc.perpetual.GetId())) } // Check the subaccount module balance. @@ -803,7 +854,7 @@ func TestTransferInsuranceFundPayments(t *testing.T) { // Check the fee module account balance has been updated as expected. toModuleBalance := bankKeeper.GetBalance( - ctx, authtypes.NewModuleAddress(perptypes.InsuranceFundName), + ctx, authtypes.NewModuleAddress(insuranceFundName), constants.Usdc.Denom, ) require.Equal(t, diff --git a/protocol/x/subaccounts/types/expected_keepers.go b/protocol/x/subaccounts/types/expected_keepers.go index d71ada3203..40a21e203d 100644 --- a/protocol/x/subaccounts/types/expected_keepers.go +++ b/protocol/x/subaccounts/types/expected_keepers.go @@ -72,6 +72,7 @@ type PerpetualsKeeper interface { err error, ) GetAllPerpetuals(ctx sdk.Context) []perptypes.Perpetual + GetInsuranceFundName(ctx sdk.Context, perpetualId uint32) (string, error) } // BankKeeper defines the expected interface needed to retrieve account balances. @@ -88,6 +89,7 @@ type BankKeeper interface { amt sdk.Coins, ) error SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error + SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error } type BlocktimeKeeper interface {