Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Performance] Remove the need to get each perpetual and price twice when checking collateralization #1681

Merged
merged 1 commit into from
Jun 13, 2024

Conversation

BrendanChou
Copy link
Contributor

@BrendanChou BrendanChou commented Jun 12, 2024

Changelist

Speed up collateralization checks by not getting the perpetual and price from state twice (once for net collateral, once for margin requirements). Instead, just get it a single time.

Breaking change as this reduces the number of state reads which modifies the gas used.

Test Plan

Updated and added unit tests

Blocked on

#1678

Summary by CodeRabbit

  • New Features

    • Introduced consolidated function GetPerpetualAndMarketPriceAndLiquidityTier to retrieve perpetual details, market price, and liquidity tier.
  • Refactor

    • Refactored and streamlined margin requirement calculations using the new perplib functions.
  • Tests

    • Updated tests to cover new scenarios for margin requirements and perpetual-related calculations, ensuring a broader scope of testing.
  • Removals

    • Removed GetMarginRequirements function from several modules to centralize calculations using the new perplib library.

Copy link
Contributor

coderabbitai bot commented Jun 12, 2024

Walkthrough

This update involves significant refactoring related to the handling of margin requirements, net collateral, and related financial calculations in the Perpetuals module. Key changes include the removal of the GetMarginRequirements function, introduction of the GetPerpetualAndMarketPriceAndLiquidityTier function, and the addition of new calculation functions in the perplib package. These modifications centralize calculations, streamline function usage, and update test cases to ensure accuracy across various scenarios.

Changes

File(s) Change Summary
protocol/mocks/PerpetualsKeeper.go, protocol/x/perpetuals/keeper/perpetual.go, protocol/x/perpetuals/types/types.go Removed the GetMarginRequirements function.
protocol/x/clob/keeper/deleveraging.go, protocol/x/clob/keeper/liquidations.go Imported perplib, refactored calculations to use new functions from perplib.
protocol/x/perpetuals/keeper/perpetual.go Added GetPerpetualAndMarketPriceAndLiquidityTier function.
protocol/x/clob/types/expected_keepers.go, protocol/x/subaccounts/types/expected_keepers.go Updated interfaces to include the new GetPerpetualAndMarketPriceAndLiquidityTier function.
protocol/x/perpetuals/lib/lib.go Added new function GetNetCollateralAndMarginRequirements.
protocol/x/perpetuals/lib/lib_test.go, protocol/x/perpetuals/keeper/perpetual_test.go Added and refactored test cases to reflect new logic for margin requirements, net collateral, etc.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ClobKeeper as CLOB Keeper
    participant SubaccountKeeper as Subaccount Keeper
    participant PerpetualsKeeper as Perpetuals Keeper
    participant Perplib as Perplib

    Client->>ClobKeeper: Request Deleveraging
    ClobKeeper->>PerpetualsKeeper: GetPerpetualAndMarketPrice
    PerpetualsKeeper->>ClobKeeper: Return Perpetual, MarketPrice
    ClobKeeper->>Perplib: Calculate DeleveragedQuoteQuantums
    Perplib-->>ClobKeeper: Return Calculations

    Client->>SubaccountKeeper: Query Net Collateral
    SubaccountKeeper->>PerpetualsKeeper: GetPerpetualAndMarketPrice
    PerpetualsKeeper->>SubaccountKeeper: Return Perpetual, MarketPrice
    SubaccountKeeper->>Perplib: Calculate Net Collateral
    Perplib-->>SubaccountKeeper: Return Calculations
    SubaccountKeeper-->>Client: Return Net Collateral
Loading

Poem

In the code where numbers play,
Margin calls have found their way,
Perpetual's dance with prices anew,
Calculations clear, precise through and through.
Rabbits code and tests align,
Liquidations on a streamlined line,
🐇 Code refactors, systems soar,
For traders' peace, and margins more. 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@BrendanChou BrendanChou force-pushed the bc/perf/get-perpetuals-twice branch from 8aef11e to 4dce300 Compare June 12, 2024 17:06
@BrendanChou BrendanChou changed the title [No-Merge] Remove the need to get each perpetual and price twice when checking collateralization [Performance] Remove the need to get each perpetual and price twice when checking collateralization Jun 12, 2024
Base automatically changed from bc/perpetuals/lib to main June 13, 2024 14:10
@BrendanChou BrendanChou force-pushed the bc/perf/get-perpetuals-twice branch from 4dce300 to 9065511 Compare June 13, 2024 15:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Outside diff range and nitpick comments (4)
protocol/mocks/PerpetualsKeeper.go (1)

Line range hint 21-21: Issues with _m.Called and mock.AssertExpectations methods.

It appears that methods _m.Called and mock.AssertExpectations are undefined for the PerpetualsKeeper type, which likely indicates missing imports or incorrect mock setup. Please verify the mock setup and ensure that all necessary packages are correctly imported.

Also applies to: 39-39, 67-67, 441-441

protocol/x/clob/keeper/deleveraging.go (1)

Line range hint 29-29: Undefined type Keeper could indicate a missing import or declaration.

Please ensure that Keeper is correctly defined or imported in this file.

protocol/x/subaccounts/keeper/subaccount.go (1)

959-974: Optimize the use of GetPerpetualAndMarketPriceAndLiquidityTier by caching results.

The function GetPerpetualAndMarketPriceAndLiquidityTier is called within a loop, which could lead to performance issues if each call is expensive. Consider caching the results of this function, especially if the data does not change frequently or if the function is called with the same parameters multiple times during a single transaction.

protocol/x/clob/keeper/liquidations.go (1)

Line range hint 683-683: The function getFillQuoteQuantums is used but not defined or imported. This could lead to runtime errors.

Please ensure that this function is properly defined or imported. If you need assistance implementing this function, feel free to ask.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 87a919e and 9065511.

Files selected for processing (11)
  • protocol/mocks/PerpetualsKeeper.go (1 hunks)
  • protocol/x/clob/keeper/deleveraging.go (3 hunks)
  • protocol/x/clob/keeper/liquidations.go (3 hunks)
  • protocol/x/clob/types/expected_keepers.go (1 hunks)
  • protocol/x/perpetuals/keeper/perpetual.go (2 hunks)
  • protocol/x/perpetuals/keeper/perpetual_test.go (3 hunks)
  • protocol/x/perpetuals/lib/lib.go (1 hunks)
  • protocol/x/perpetuals/lib/lib_test.go (3 hunks)
  • protocol/x/perpetuals/types/types.go (1 hunks)
  • protocol/x/subaccounts/keeper/subaccount.go (1 hunks)
  • protocol/x/subaccounts/types/expected_keepers.go (3 hunks)
Files skipped from review due to trivial changes (1)
  • protocol/x/perpetuals/types/types.go
Additional context used
golangci-lint
protocol/mocks/PerpetualsKeeper.go

21-21: _m.Called undefined (type *PerpetualsKeeper has no field or method Called) (typecheck)


39-39: _m.Called undefined (type *PerpetualsKeeper has no field or method Called) (typecheck)


67-67: _m.Called undefined (type *PerpetualsKeeper has no field or method Called) (typecheck)


441-441: mock.AssertExpectations undefined (type *PerpetualsKeeper has no field or method AssertExpectations) (typecheck)

protocol/x/clob/keeper/deleveraging.go

29-29: undefined: Keeper (typecheck)


136-136: undefined: Keeper (typecheck)


155-155: undefined: Keeper (typecheck)


641-641: undefined: subaccountToDeleverage (typecheck)


678-678: undefined: subaccountToDeleverage (typecheck)


659-659: undefined: subaccountToDeleverage (typecheck)

protocol/x/clob/keeper/liquidations.go

36-36: undefined: Keeper (typecheck)


165-165: undefined: Keeper (typecheck)


198-198: undefined: Keeper (typecheck)


683-683: undefined: getFillQuoteQuantums (typecheck)

Additional comments not posted (17)
protocol/x/subaccounts/types/expected_keepers.go (2)

17-23: Introducing the IsPositionUpdatable method in the ProductKeeper interface.

This addition aligns with the PR's goal of enhancing functionality around position updates, allowing for more granular control over position states.


75-83: Added GetPerpetualAndMarketPriceAndLiquidityTier method to the PerpetualsKeeper interface.

This new method consolidates the retrieval of perpetual details, market price, and liquidity tier into a single call, which is a clear improvement in terms of reducing the number of state reads, aligning perfectly with the PR's objectives.

protocol/x/clob/types/expected_keepers.go (1)

108-114: Added GetPerpetualAndMarketPriceAndLiquidityTier method in the PerpetualsKeeper interface.

This method is crucial for the centralization of data retrieval in the system, reducing the number of state reads and thus improving performance as per the PR's objectives.

protocol/x/perpetuals/lib/lib.go (1)

38-62: Introduced GetNetCollateralAndMarginRequirements function.

This function centralizes the calculations for net collateral, initial margin, and maintenance margin requirements, which enhances code modularity and maintainability. It's a solid implementation that supports the PR's goal of performance optimization by reducing redundant calculations.

protocol/x/clob/keeper/deleveraging.go (4)

Line range hint 136-136: The type Keeper is flagged again as undefined.

This is a duplicate of the previous comment regarding the undefined Keeper type.


Line range hint 155-155: The type Keeper is undefined, which is a recurring issue in this file.

This is a duplicate of the previous comment regarding the undefined Keeper type.


595-600: The new function GetPerpetualAndMarketPrice and its usage here are crucial for the performance improvements described in the PR. Ensure it is implemented correctly and integrates well with existing systems.

This change aligns with the PR objectives to optimize performance by reducing state reads.


20-21: Ensure the new import perplib is utilized effectively in the file.

Verification successful

The new import perplib is effectively utilized in the file.

  • perplib is used in the line involving the GetNetNotionalInQuoteQuantums function.
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the usage of the newly imported `perplib` library.

# Test: Search for `perplib` usage. Expect: Multiple occurrences across the file.
rg --type go $'perplib' protocol/x/clob/keeper/deleveraging.go

Length of output: 200

protocol/x/perpetuals/lib/lib_test.go (1)

100-164: LGTM! Good coverage and use of assertions in TestGetNetCollateralAndMarginRequirements.

protocol/x/subaccounts/keeper/subaccount.go (1)

959-974: Ensure the new function GetPerpetualAndMarketPriceAndLiquidityTier is thoroughly tested.

Verification successful

The search results reveal that the function GetPerpetualAndMarketPriceAndLiquidityTier is tested for scenarios where the perpetual, market, or liquidity tier is not found. These test cases indicate that the function's error handling is being tested comprehensively.

  • TestGetPerpetualAndMarketPriceAndLiquidityTier_PerpetualNotFound
  • TestGetPerpetualAndMarketPriceAndLiquidityTier_MarketNotFound
  • TestGetPerpetualAndMarketPriceAndLiquidityTier_LiquidityTierNotFound

These tests cover key aspects of the function's behavior, ensuring that it correctly handles cases where required data is missing.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify thorough testing of the new function.
# Search for test cases for `GetPerpetualAndMarketPriceAndLiquidityTier`.
rg --type go 'TestGetPerpetualAndMarketPriceAndLiquidityTier'

Length of output: 461



Script:

#!/bin/bash
# Description: Check the content of the test cases for thorough testing.
# Display the content of the test cases for `GetPerpetualAndMarketPriceAndLiquidityTier`.
rg --type go --context 10 'func TestGetPerpetualAndMarketPriceAndLiquidityTier_' protocol/x/perpetuals/keeper/perpetual_test.go

Length of output: 1944

protocol/x/clob/keeper/liquidations.go (3)

19-19: Import of perplib aligns with refactoring to centralize margin and collateral calculations.


459-481: The refactoring of GetBankruptcyPriceInQuoteQuantums to use GetPerpetualAndMarketPriceAndLiquidityTier and perplib.GetNetCollateralAndMarginRequirements effectively reduces the number of state reads, aligning with the PR's performance goals.


554-567: The refactoring of GetFillablePrice to use GetPerpetualAndMarketPriceAndLiquidityTier and perplib.GetNetCollateralAndMarginRequirements effectively reduces the number of state reads, aligning with the PR's performance goals.

protocol/x/perpetuals/keeper/perpetual.go (1)

1189-1213: Review the implementation of GetPerpetualAndMarketPriceAndLiquidityTier.

The new function GetPerpetualAndMarketPriceAndLiquidityTier consolidates the retrieval of perpetual, market price, and liquidity tier into a single function call. This should effectively reduce the number of state reads, aligning with the PR's goal of optimizing performance. Ensure that all calls to the old GetPerpetual and GetMarketPrice functions are replaced with calls to this new function to fully realize the performance benefits.

protocol/x/perpetuals/keeper/perpetual_test.go (3)

677-681: Ensure error handling for non-existent perpetual ID is thoroughly tested.

This test case correctly validates that the system handles non-existent perpetual IDs gracefully by returning an appropriate error. It's good practice to include such negative tests to ensure robust error handling.


Line range hint 717-733: Validate edge cases for liquidity tier not found scenarios.

This test case effectively checks the system's response when a non-existent liquidity tier is referenced. Including such edge cases in tests ensures that the system can handle unexpected inputs gracefully.


Line range hint 688-714: Consider adding more assertions to validate the state after the operation.

While the test checks for the error condition when the market ID does not exist, it might be beneficial to also assert that no changes were made to the state as a side effect of the failed operation. This helps ensure that the function is not only returning the correct error but also not altering any state unexpectedly.

Comment on lines +348 to +656
openInterestLowerCap: 25_000_000_000_000,
openInterestUpperCap: 50_000_000_000_000,
// openInterestNotional = 1_123_456_789 * 36_750 = 41_287_036_995_750
// percentageOfCap = (openInterestNotional - lowerCap) / (upperCap - lowerCap) = 0.65148147983
// adjustedIMF = (0.65148147983) * 0.8 + 0.2 = 0.721185183864 (rounded is 721_185 ppm)
// bigExpectedInitialMargin = bigBaseQuantums * price * adjustedIMF = 318_042_585
bigExpectedInitialMargin: big.NewInt(318_042_585),
bigExpectedMaintenanceMargin: big.NewInt(88_200_000 / 2),
},
}

// Run tests.
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// Individual test setup.
pc := keepertest.PerpetualsKeepers(t)
// Create a new market param and price.
marketId := keepertest.GetNumMarkets(t, pc.Ctx, pc.PricesKeeper)
_, err := pc.PricesKeeper.CreateMarket(
pc.Ctx,
pricestypes.MarketParam{
Id: marketId,
Pair: "marketName",
Exponent: tc.exponent,
MinExchanges: uint32(1),
MinPriceChangePpm: uint32(50),
ExchangeConfigJson: "{}",
},
pricestypes.MarketPrice{
Id: marketId,
Exponent: tc.exponent,
Price: 1_000, // leave this as a placeholder b/c we cannot set the price to 0
},
)
require.NoError(t, err)

// Update `Market.price`. By updating prices this way, we can simulate conditions where the oracle
// price may become 0.
err = pc.PricesKeeper.UpdateMarketPrices(
pc.Ctx,
[]*pricestypes.MsgUpdateMarketPrices_MarketPrice{pricestypes.NewMarketPriceUpdate(
marketId,
tc.price,
)},
)
require.NoError(t, err)

// Create `LiquidityTier` struct.
_, err = pc.PerpetualsKeeper.SetLiquidityTier(
pc.Ctx,
0,
"name",
tc.initialMarginPpm,
tc.maintenanceFractionPpm,
1, // dummy impact notional value
tc.openInterestLowerCap,
tc.openInterestUpperCap,
)
require.NoError(t, err)

// Create `Perpetual` struct with baseAssetAtomicResolution and marketId.
perpetual, err := pc.PerpetualsKeeper.CreatePerpetual(
pc.Ctx,
0, // PerpetualId
"getMarginRequirementsTicker", // Ticker
marketId, // MarketId
tc.baseCurrencyAtomicResolution, // AtomicResolution
int32(0), // DefaultFundingPpm
0, // LiquidityTier
types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, // MarketType
)
require.NoError(t, err)

// If test case contains non-nil open interest, set it up.
if tc.openInterest != nil {
require.NoError(t, pc.PerpetualsKeeper.ModifyOpenInterest(
pc.Ctx,
perpetual.Params.Id,
tc.openInterest, // initialized as zero, so passing `openInterest` as delta amount.
))
}

// Verify initial and maintenance margin requirements are calculated correctly.
perpetual, marketPrice, liquidityTier, err := pc.PerpetualsKeeper.GetPerpetualAndMarketPriceAndLiquidityTier(
pc.Ctx,
perpetual.Params.Id,
)
require.NoError(t, err)
imr, mmr := lib.GetMarginRequirementsInQuoteQuantums(
perpetual,
marketPrice,
liquidityTier,
tc.bigBaseQuantums,
)

require.Equal(t, tc.bigExpectedInitialMargin, imr, "Initial margin mismatch")
require.Equal(t, tc.bigExpectedMaintenanceMargin, mmr, "Maintenance margin mismatch")
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider refactoring TestGetMarginRequirementsInQuoteQuantums_2 to enhance readability and maintainability. Splitting this large test into smaller, more focused tests could improve clarity and make individual scenarios easier to manage.

Comment on lines +934 to +954
// Iterate over all assets and updates and calculate change to net collateral and margin requirements.
for _, size := range assetSizes {
id := size.GetId()
bigQuantums := size.GetBigQuantums()

bigNetCollateralQuoteQuantums, err := pk.GetNetCollateral(ctx, id, bigQuantums)
nc, err := k.assetsKeeper.GetNetCollateral(ctx, id, bigQuantums)
if err != nil {
return err
return big.NewInt(0), big.NewInt(0), big.NewInt(0), err
}

bigNetCollateral.Add(bigNetCollateral, bigNetCollateralQuoteQuantums)

bigInitialMarginRequirements,
bigMaintenanceMarginRequirements,
err := pk.GetMarginRequirements(
imr, mmr, err := k.assetsKeeper.GetMarginRequirements(
ctx,
id,
bigQuantums,
)
if err != nil {
return err
}

bigInitialMargin.Add(bigInitialMargin, bigInitialMarginRequirements)
bigMaintenanceMargin.Add(bigMaintenanceMargin, bigMaintenanceMarginRequirements)

return nil
}

// Iterate over all assets and updates and calculate change to net collateral and margin requirements.
for _, size := range assetSizes {
err := calculate(k.assetsKeeper, size)
if err != nil {
return big.NewInt(0), big.NewInt(0), big.NewInt(0), err
}
bigNetCollateral.Add(bigNetCollateral, nc)
bigInitialMargin.Add(bigInitialMargin, imr)
bigMaintenanceMargin.Add(bigMaintenanceMargin, mmr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider consolidating repetitive code for margin and collateral calculations.

The loop iterating over assets to calculate net collateral and margin requirements is structurally similar to the one for perpetuals. It might be beneficial to abstract this logic into a helper function that can handle both assets and perpetuals, reducing code duplication and improving maintainability.

@BrendanChou BrendanChou merged commit edcc82b into main Jun 13, 2024
17 checks passed
@BrendanChou BrendanChou deleted the bc/perf/get-perpetuals-twice branch June 13, 2024 15:37
mergify bot pushed a commit that referenced this pull request Jun 27, 2024
…hen checking collateralization (#1681)

(cherry picked from commit edcc82b)

# Conflicts:
#	protocol/x/perpetuals/keeper/perpetual_test.go
teddyding pushed a commit that referenced this pull request Jun 27, 2024
…hen checking collateralization (#1681)

(cherry picked from commit edcc82b)

# Conflicts:
#	protocol/x/perpetuals/keeper/perpetual_test.go
teddyding added a commit that referenced this pull request Jun 27, 2024
…hen checking collateralization (backport #1681) (#1794)

Co-authored-by: Brendan Chou <[email protected]>
Co-authored-by: Teddy Ding <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

2 participants