From 87a919e67b490b0ff469083120af95a16f3a0a90 Mon Sep 17 00:00:00 2001 From: Brendan Chou <3680392+BrendanChou@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:10:43 -0400 Subject: [PATCH] [chore] Move `perpetuals/keeper` helper functions to `perpetuals/lib` (#1678) --- .../liquidation/client/sub_task_runner.go | 6 +- protocol/x/perpetuals/keeper/perpetual.go | 110 +------ .../x/perpetuals/keeper/perpetual_test.go | 22 +- protocol/x/perpetuals/lib/lib.go | 106 +++++++ protocol/x/perpetuals/lib/lib_test.go | 278 ++++++++++++++++++ protocol/x/subaccounts/keeper/subaccount.go | 4 +- 6 files changed, 402 insertions(+), 124 deletions(-) create mode 100644 protocol/x/perpetuals/lib/lib.go create mode 100644 protocol/x/perpetuals/lib/lib_test.go diff --git a/protocol/daemons/liquidation/client/sub_task_runner.go b/protocol/daemons/liquidation/client/sub_task_runner.go index e768db2819..7918e9db39 100644 --- a/protocol/daemons/liquidation/client/sub_task_runner.go +++ b/protocol/daemons/liquidation/client/sub_task_runner.go @@ -13,7 +13,7 @@ import ( assetstypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" clobkeeper "github.com/dydxprotocol/v4-chain/protocol/x/clob/keeper" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" - perpkeeper "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/keeper" + perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" sakeeper "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/keeper" @@ -344,7 +344,7 @@ func (c *Client) CheckSubaccountCollateralization( bigQuantums := perpetualPosition.GetBigQuantums() // Get the net collateral for the position. - bigNetCollateralQuoteQuantums := perpkeeper.GetNetNotionalInQuoteQuantums(perpetual, marketPrice, bigQuantums) + bigNetCollateralQuoteQuantums := perplib.GetNetNotionalInQuoteQuantums(perpetual, marketPrice, bigQuantums) bigTotalNetCollateral.Add(bigTotalNetCollateral, bigNetCollateralQuoteQuantums) liquidityTier, ok := liquidityTiers[perpetual.Params.LiquidityTier] @@ -357,7 +357,7 @@ func (c *Client) CheckSubaccountCollateralization( } // Get the maintenance margin requirement for the position. - _, bigMaintenanceMarginQuoteQuantums := perpkeeper.GetMarginRequirementsInQuoteQuantums( + _, bigMaintenanceMarginQuoteQuantums := perplib.GetMarginRequirementsInQuoteQuantums( perpetual, marketPrice, liquidityTier, diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index f155256ca5..08a9cc7eba 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -28,6 +28,7 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" epochstypes "github.com/dydxprotocol/v4-chain/protocol/x/epochs/types" "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/funding" + perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" gometrics "github.com/hashicorp/go-metrics" @@ -817,31 +818,7 @@ func (k Keeper) GetNetNotional( return new(big.Int), err } - return GetNetNotionalInQuoteQuantums(perpetual, marketPrice, bigQuantums), nil -} - -// GetNetNotionalInQuoteQuantums returns the net notional in quote quantums, which can be -// represented by the following equation: -// -// `quantums / 10^baseAtomicResolution * marketPrice * 10^marketExponent * 10^quoteAtomicResolution`. -// Note that longs are positive, and shorts are negative. -// -// Also note that this is a stateless function. -func GetNetNotionalInQuoteQuantums( - perpetual types.Perpetual, - marketPrice pricestypes.MarketPrice, - bigQuantums *big.Int, -) ( - bigNetNotionalQuoteQuantums *big.Int, -) { - bigQuoteQuantums := lib.BaseToQuoteQuantums( - bigQuantums, - perpetual.Params.AtomicResolution, - marketPrice.Price, - marketPrice.Exponent, - ) - - return bigQuoteQuantums + return perplib.GetNetNotionalInQuoteQuantums(perpetual, marketPrice, bigQuantums), nil } // GetNotionalInBaseQuantums returns the net notional in base quantums, which can be represented @@ -946,7 +923,7 @@ func (k Keeper) GetMarginRequirements( } bigInitialMarginQuoteQuantums, - bigMaintenanceMarginQuoteQuantums = GetMarginRequirementsInQuoteQuantums( + bigMaintenanceMarginQuoteQuantums = perplib.GetMarginRequirementsInQuoteQuantums( perpetual, marketPrice, liquidityTier, @@ -955,56 +932,6 @@ func (k Keeper) GetMarginRequirements( return bigInitialMarginQuoteQuantums, bigMaintenanceMarginQuoteQuantums, nil } -// GetMarginRequirementsInQuoteQuantums returns initial and maintenance margin requirements -// in quote quantums, given the position size in base quantums. -// -// Note that this is a stateless function. -func GetMarginRequirementsInQuoteQuantums( - perpetual types.Perpetual, - marketPrice pricestypes.MarketPrice, - liquidityTier types.LiquidityTier, - bigQuantums *big.Int, -) ( - bigInitialMarginQuoteQuantums *big.Int, - bigMaintenanceMarginQuoteQuantums *big.Int, -) { - // Always consider the magnitude of the position regardless of whether it is long/short. - bigAbsQuantums := new(big.Int).Set(bigQuantums).Abs(bigQuantums) - - // Calculate the notional value of the position in quote quantums. - bigQuoteQuantums := lib.BaseToQuoteQuantums( - bigAbsQuantums, - perpetual.Params.AtomicResolution, - marketPrice.Price, - marketPrice.Exponent, - ) - // Calculate the perpetual's open interest in quote quantums. - openInterestQuoteQuantums := lib.BaseToQuoteQuantums( - perpetual.OpenInterest.BigInt(), // OpenInterest is represented as base quantums. - perpetual.Params.AtomicResolution, - marketPrice.Price, - marketPrice.Exponent, - ) - - // Initial margin requirement quote quantums = size in quote quantums * initial margin PPM. - bigBaseInitialMarginQuoteQuantums := liquidityTier.GetInitialMarginQuoteQuantums( - bigQuoteQuantums, - big.NewInt(0), // pass in 0 as open interest to get base IMR. - ) - // Maintenance margin requirement quote quantums = IM in quote quantums * maintenance fraction PPM. - bigMaintenanceMarginQuoteQuantums = lib.BigMulPpm( - bigBaseInitialMarginQuoteQuantums, - lib.BigU(liquidityTier.MaintenanceFractionPpm), - true, - ) - - bigInitialMarginQuoteQuantums = liquidityTier.GetInitialMarginQuoteQuantums( - bigQuoteQuantums, - openInterestQuoteQuantums, // pass in current OI to get scaled IMR. - ) - return bigInitialMarginQuoteQuantums, bigMaintenanceMarginQuoteQuantums -} - // GetSettlementPpm returns the net settlement amount ppm (in quote quantums) given // the perpetual Id and position size (in base quantums). // When handling rounding, always round positive settlement amount to zero, and @@ -1031,7 +958,7 @@ func (k Keeper) GetSettlementPpm( return big.NewInt(0), big.NewInt(0), err } - bigNetSettlementPpm, newFundingIndex = GetSettlementPpmWithPerpetual( + bigNetSettlementPpm, newFundingIndex = perplib.GetSettlementPpmWithPerpetual( perpetual, quantums, index, @@ -1039,35 +966,6 @@ func (k Keeper) GetSettlementPpm( return bigNetSettlementPpm, newFundingIndex, nil } -// GetSettlementPpm returns the net settlement amount ppm (in quote quantums) given -// the perpetual and position size (in base quantums). -// -// Note that this function is a stateless utility function. -func GetSettlementPpmWithPerpetual( - perpetual types.Perpetual, - quantums *big.Int, - index *big.Int, -) ( - bigNetSettlementPpm *big.Int, - newFundingIndex *big.Int, -) { - indexDelta := new(big.Int).Sub(perpetual.FundingIndex.BigInt(), index) - - // if indexDelta is zero, then net settlement is zero. - if indexDelta.Sign() == 0 { - return big.NewInt(0), perpetual.FundingIndex.BigInt() - } - - bigNetSettlementPpm = new(big.Int).Mul(indexDelta, quantums) - - // `bigNetSettlementPpm` carries sign. `indexDelta` is the increase in `fundingIndex`, so if - // the position is long (positive), the net settlement should be short (negative), and vice versa. - // Thus, always negate `bigNetSettlementPpm` here. - bigNetSettlementPpm = bigNetSettlementPpm.Neg(bigNetSettlementPpm) - - return bigNetSettlementPpm, perpetual.FundingIndex.BigInt() -} - // GetPremiumSamples reads premium samples from the current `funding-tick` epoch, // stored in a `PremiumStore` struct. func (k Keeper) GetPremiumSamples(ctx sdk.Context) ( diff --git a/protocol/x/perpetuals/keeper/perpetual_test.go b/protocol/x/perpetuals/keeper/perpetual_test.go index 165c8a2442..f24929021a 100644 --- a/protocol/x/perpetuals/keeper/perpetual_test.go +++ b/protocol/x/perpetuals/keeper/perpetual_test.go @@ -7,24 +7,18 @@ import ( "sort" "testing" - "github.com/cosmos/gogoproto/proto" - "github.com/dydxprotocol/v4-chain/protocol/app/module" - errorsmod "cosmossdk.io/errors" - - "github.com/dydxprotocol/v4-chain/protocol/dtypes" - "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" - "github.com/dydxprotocol/v4-chain/protocol/mocks" + "cosmossdk.io/store/prefix" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/dydxprotocol/v4-chain/protocol/lib" - "github.com/stretchr/testify/mock" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/store/prefix" + "github.com/cosmos/gogoproto/proto" + "github.com/dydxprotocol/v4-chain/protocol/app/module" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events" + "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/mocks" big_testutil "github.com/dydxprotocol/v4-chain/protocol/testutil/big" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" @@ -37,6 +31,8 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/keeper" "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) func TestModifyPerpetual_Success(t *testing.T) { diff --git a/protocol/x/perpetuals/lib/lib.go b/protocol/x/perpetuals/lib/lib.go new file mode 100644 index 0000000000..87cb0aab33 --- /dev/null +++ b/protocol/x/perpetuals/lib/lib.go @@ -0,0 +1,106 @@ +package lib + +import ( + "math/big" + + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" +) + +// GetSettlementPpm returns the net settlement amount ppm (in quote quantums) given +// the perpetual and position size (in base quantums). +func GetSettlementPpmWithPerpetual( + perpetual types.Perpetual, + quantums *big.Int, + index *big.Int, +) ( + bigNetSettlementPpm *big.Int, + newFundingIndex *big.Int, +) { + indexDelta := new(big.Int).Sub(perpetual.FundingIndex.BigInt(), index) + + // if indexDelta is zero, then net settlement is zero. + if indexDelta.Sign() == 0 { + return big.NewInt(0), perpetual.FundingIndex.BigInt() + } + + bigNetSettlementPpm = new(big.Int).Mul(indexDelta, quantums) + + // `bigNetSettlementPpm` carries sign. `indexDelta` is the increase in `fundingIndex`, so if + // the position is long (positive), the net settlement should be short (negative), and vice versa. + // Thus, always negate `bigNetSettlementPpm` here. + bigNetSettlementPpm = bigNetSettlementPpm.Neg(bigNetSettlementPpm) + + return bigNetSettlementPpm, perpetual.FundingIndex.BigInt() +} + +// GetNetNotionalInQuoteQuantums returns the net notional in quote quantums, which can be +// represented by the following equation: +// +// `quantums / 10^baseAtomicResolution * marketPrice * 10^marketExponent * 10^quoteAtomicResolution`. +// Note that longs are positive, and shorts are negative. +func GetNetNotionalInQuoteQuantums( + perpetual types.Perpetual, + marketPrice pricestypes.MarketPrice, + bigQuantums *big.Int, +) ( + bigNetNotionalQuoteQuantums *big.Int, +) { + bigQuoteQuantums := lib.BaseToQuoteQuantums( + bigQuantums, + perpetual.Params.AtomicResolution, + marketPrice.Price, + marketPrice.Exponent, + ) + + return bigQuoteQuantums +} + +// GetMarginRequirementsInQuoteQuantums returns initial and maintenance margin requirements +// in quote quantums, given the position size in base quantums. +func GetMarginRequirementsInQuoteQuantums( + perpetual types.Perpetual, + marketPrice pricestypes.MarketPrice, + liquidityTier types.LiquidityTier, + bigQuantums *big.Int, +) ( + bigInitialMarginQuoteQuantums *big.Int, + bigMaintenanceMarginQuoteQuantums *big.Int, +) { + // Always consider the magnitude of the position regardless of whether it is long/short. + bigAbsQuantums := new(big.Int).Abs(bigQuantums) + + // Calculate the notional value of the position in quote quantums. + bigQuoteQuantums := lib.BaseToQuoteQuantums( + bigAbsQuantums, + perpetual.Params.AtomicResolution, + marketPrice.Price, + marketPrice.Exponent, + ) + // Calculate the perpetual's open interest in quote quantums. + openInterestQuoteQuantums := lib.BaseToQuoteQuantums( + perpetual.OpenInterest.BigInt(), // OpenInterest is represented as base quantums. + perpetual.Params.AtomicResolution, + marketPrice.Price, + marketPrice.Exponent, + ) + + // Initial margin requirement quote quantums = size in quote quantums * initial margin PPM. + bigBaseInitialMarginQuoteQuantums := liquidityTier.GetInitialMarginQuoteQuantums( + bigQuoteQuantums, + big.NewInt(0), // pass in 0 as open interest to get base IMR. + ) + // Maintenance margin requirement quote quantums = IM in quote quantums * maintenance fraction PPM. + bigMaintenanceMarginQuoteQuantums = lib.BigMulPpm( + bigBaseInitialMarginQuoteQuantums, + lib.BigU(liquidityTier.MaintenanceFractionPpm), + true, + ) + + bigInitialMarginQuoteQuantums = liquidityTier.GetInitialMarginQuoteQuantums( + bigQuoteQuantums, + openInterestQuoteQuantums, // pass in current OI to get scaled IMR. + ) + return bigInitialMarginQuoteQuantums, bigMaintenanceMarginQuoteQuantums +} diff --git a/protocol/x/perpetuals/lib/lib_test.go b/protocol/x/perpetuals/lib/lib_test.go new file mode 100644 index 0000000000..3723a86e91 --- /dev/null +++ b/protocol/x/perpetuals/lib/lib_test.go @@ -0,0 +1,278 @@ +package lib_test + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + big_testutil "github.com/dydxprotocol/v4-chain/protocol/testutil/big" + "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" +) + +func BenchmarkGetSettlementPpmWithPerpetual(b *testing.B) { + perpetual := types.Perpetual{ + FundingIndex: dtypes.NewInt(64_123_456_789), + } + quantums := big.NewInt(45_454_545_454) + index := big.NewInt(87_654_321_99) + for i := 0; i < b.N; i++ { + lib.GetSettlementPpmWithPerpetual( + perpetual, + quantums, + index, + ) + } +} + +func TestGetSettlementPpmWithPerpetual(t *testing.T) { + tests := map[string]struct { + perpetual types.Perpetual + quantums *big.Int + index *big.Int + expectedNetSettlementPpm *big.Int + expectedNewFundingIndex *big.Int + }{ + "zero indexDelta": { + perpetual: types.Perpetual{ + FundingIndex: dtypes.NewInt(4_000_000_000), + }, + quantums: big.NewInt(1_000_000), + index: big.NewInt(4_000_000_000), + expectedNetSettlementPpm: big.NewInt(0), + expectedNewFundingIndex: big.NewInt(4_000_000_000), + }, + "positive indexDelta, positive quantums": { + perpetual: types.Perpetual{ + FundingIndex: dtypes.NewInt(123_456_789_123), + }, + quantums: big.NewInt(1_000_000), + index: big.NewInt(100_000_000_000), + expectedNetSettlementPpm: big.NewInt(-23_456_789_123_000_000), + expectedNewFundingIndex: big.NewInt(123_456_789_123), + }, + "positive indexDelta, negative quantums": { + perpetual: types.Perpetual{ + FundingIndex: dtypes.NewInt(123_456_789_123), + }, + quantums: big.NewInt(-1_000_000), + index: big.NewInt(100_000_000_000), + expectedNetSettlementPpm: big.NewInt(23_456_789_123_000_000), + expectedNewFundingIndex: big.NewInt(123_456_789_123), + }, + "negative indexDelta, positive quantums": { + perpetual: types.Perpetual{ + FundingIndex: dtypes.NewInt(-123_456_789_123), + }, + quantums: big.NewInt(1_000_000), + index: big.NewInt(-100_000_000_000), + expectedNetSettlementPpm: big.NewInt(23_456_789_123_000_000), + expectedNewFundingIndex: big.NewInt(-123_456_789_123), + }, + "negative indexDelta, negative quantums": { + perpetual: types.Perpetual{ + FundingIndex: dtypes.NewInt(-123_456_789_123), + }, + quantums: big.NewInt(-1_000_000), + index: big.NewInt(-100_000_000_000), + expectedNetSettlementPpm: big.NewInt(-23_456_789_123_000_000), + expectedNewFundingIndex: big.NewInt(-123_456_789_123), + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + netSettlementPpm, newFundingIndex := lib.GetSettlementPpmWithPerpetual( + test.perpetual, + test.quantums, + test.index, + ) + require.Equal(t, test.expectedNetSettlementPpm, netSettlementPpm) + require.Equal(t, test.expectedNewFundingIndex, newFundingIndex) + }) + } +} + +func BenchmarkGetNetNotionalInQuoteQuantums(b *testing.B) { + perpetual := types.Perpetual{ + Params: types.PerpetualParams{ + AtomicResolution: 12, + }, + } + marketPrice := pricestypes.MarketPrice{ + Price: 123_456_789_123, + Exponent: -8, + } + quantums := big.NewInt(987_654_321_321) + for i := 0; i < b.N; i++ { + lib.GetNetNotionalInQuoteQuantums( + perpetual, + marketPrice, + quantums, + ) + } +} + +func TestGetNetNotionalInQuoteQuantums(t *testing.T) { + testPerpetual := types.Perpetual{ + Params: types.PerpetualParams{ + AtomicResolution: -16, + }, + OpenInterest: dtypes.NewInt(1_000_000_000_000), + } + testMarketPrice := pricestypes.MarketPrice{ + Price: 123_456_789_123, + Exponent: -5, + } + tests := map[string]struct { + perpetual types.Perpetual + marketPrice pricestypes.MarketPrice + quantums *big.Int + expectedNetNotional *big.Int + }{ + "zero quantums": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + quantums: big.NewInt(0), + expectedNetNotional: big.NewInt(1).SetUint64(0), // non-nil natural + }, + "positive quantums": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + quantums: big.NewInt(1_000_000_000_000), + expectedNetNotional: big.NewInt(123_456_789), + }, + "negative quantums": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + quantums: big.NewInt(-1_000_000_000_000), + expectedNetNotional: big.NewInt(-123_456_789), + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + netNotional := lib.GetNetNotionalInQuoteQuantums( + test.perpetual, + test.marketPrice, + test.quantums, + ) + require.Equal(t, test.expectedNetNotional, netNotional) + }) + } +} + +func BenchmarkGetMarginRequirementsInQuoteQuantums(b *testing.B) { + perpetual := types.Perpetual{ + Params: types.PerpetualParams{ + AtomicResolution: 8, + }, + OpenInterest: dtypes.NewInt(1_000_000_000_000), + } + marketPrice := pricestypes.MarketPrice{ + Price: 123_456_789_123, + Exponent: -5, + } + liquidityTier := types.LiquidityTier{ + InitialMarginPpm: 200_000, + MaintenanceFractionPpm: 500_000, + } + quantums := big.NewInt(1_000_000_000_000) + for i := 0; i < b.N; i++ { + lib.GetMarginRequirementsInQuoteQuantums( + perpetual, + marketPrice, + liquidityTier, + quantums, + ) + } +} + +func TestGetMarginRequirementsInQuoteQuantums(t *testing.T) { + testPerpetual := types.Perpetual{ + Params: types.PerpetualParams{ + AtomicResolution: 4, + }, + OpenInterest: dtypes.NewInt(1_000_000_000_000), + } + testMarketPrice := pricestypes.MarketPrice{ + Price: 123_456_789_123, + Exponent: -5, + } + testLiquidityTier := types.LiquidityTier{ + InitialMarginPpm: 200_000, + MaintenanceFractionPpm: 500_000, + } + tests := map[string]struct { + perpetual types.Perpetual + marketPrice pricestypes.MarketPrice + liquidityTier types.LiquidityTier + quantums *big.Int + expectedImr *big.Int + expectedMmr *big.Int + }{ + "zero quantums": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + liquidityTier: testLiquidityTier, + quantums: big.NewInt(0), + expectedImr: big.NewInt(0), + expectedMmr: big.NewInt(0), + }, + "positive quantums": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + liquidityTier: testLiquidityTier, + quantums: big.NewInt(1), + expectedImr: big_testutil.MustFirst(new(big.Int).SetString("2469135782460000", 10)), + expectedMmr: big_testutil.MustFirst(new(big.Int).SetString("1234567891230000", 10)), + }, + "positive quantums, open interest above cap": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + liquidityTier: types.LiquidityTier{ + InitialMarginPpm: 200_000, + MaintenanceFractionPpm: 500_000, + OpenInterestLowerCap: 1_000_000_000_000, + OpenInterestUpperCap: 2_000_000_000_000, + }, + quantums: big.NewInt(1), + expectedImr: big_testutil.MustFirst(new(big.Int).SetString("12345678912300000", 10)), + expectedMmr: big_testutil.MustFirst(new(big.Int).SetString("1234567891230000", 10)), + }, + "negative quantums": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + liquidityTier: testLiquidityTier, + quantums: big.NewInt(-1), + expectedImr: big_testutil.MustFirst(new(big.Int).SetString("2469135782460000", 10)), + expectedMmr: big_testutil.MustFirst(new(big.Int).SetString("1234567891230000", 10)), + }, + "negative quantums, open interest above cap": { + perpetual: testPerpetual, + marketPrice: testMarketPrice, + liquidityTier: types.LiquidityTier{ + InitialMarginPpm: 200_000, + MaintenanceFractionPpm: 500_000, + OpenInterestLowerCap: 1_000_000_000_000, + OpenInterestUpperCap: 2_000_000_000_000, + }, + quantums: big.NewInt(-1), + expectedImr: big_testutil.MustFirst(new(big.Int).SetString("12345678912300000", 10)), + expectedMmr: big_testutil.MustFirst(new(big.Int).SetString("1234567891230000", 10)), + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + imr, mmr := lib.GetMarginRequirementsInQuoteQuantums( + test.perpetual, + test.marketPrice, + test.liquidityTier, + test.quantums, + ) + require.Equal(t, test.expectedImr, imr) + require.Equal(t, test.expectedMmr, mmr) + }) + } +} diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index 521520a04c..a937757fe4 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -22,7 +22,7 @@ import ( indexer_manager "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" - perpkeeper "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/keeper" + perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" gometrics "github.com/hashicorp/go-metrics" @@ -488,7 +488,7 @@ func GetSettledSubaccountWithPerpetuals( } // Call the stateless utility function to get the net settlement and new funding index. - bigNetSettlementPpm, newFundingIndex := perpkeeper.GetSettlementPpmWithPerpetual( + bigNetSettlementPpm, newFundingIndex := perplib.GetSettlementPpmWithPerpetual( perpetual, p.GetBigQuantums(), p.FundingIndex.BigInt(),