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

Optimize OffsetSubaccountPerpetualPosition subaccount iteration #906

Merged
merged 7 commits into from
Dec 22, 2023

Conversation

jayy04
Copy link
Contributor

@jayy04 jayy04 commented Dec 22, 2023

Changelist

  • move daemon liquidation info from module to keeper
  • Updated OffsetSubaccountPerpetualPosition to iterate over subaccounts on a specific side instead of pseudo randomly

Test Plan

[Describe how this PR was tested (if applicable)]

Author/Reviewer Checklist

  • If this PR has changes that result in a different app state given the same prior state and transaction list, manually add the state-breaking label.
  • If the PR has breaking postgres changes to the indexer add the indexer-postgres-breaking label.
  • If this PR isn't state-breaking but has changes that modify behavior in PrepareProposal or ProcessProposal, manually add the label proposal-breaking.
  • If this PR is one of many that implement a specific feature, manually label them all feature:[feature-name].
  • If you wish to for mergify-bot to automatically create a PR to backport your change to a release branch, manually add the label backport/[branch-name].
  • Manually add any of the following labels: refactor, chore, bug.

Copy link
Contributor

coderabbitai bot commented Dec 22, 2023

Walkthrough

The codebase has undergone a refactoring that centralizes the handling of liquidation information within the DaemonLiquidationInfo type. This has led to the removal of daemonLiquidationInfo from various function calls and the introduction of new methods to manage subaccounts with open positions. The logic for iterating through subaccounts has been simplified, removing the need for random starting points. These changes streamline the process of subaccount management and liquidation within the system.

Changes

File(s) Change Summary
protocol/app/app.go Removed daemonLiquidationInfo from clobmodule.NewAppModule function call and added it to New function call.
protocol/.../daemon_liquidation_info.go Added methods GetSubaccountsWithOpenPositions and GetSubaccountsWithOpenPositionsOnSide.
protocol/testutil/clob/open_positions.go Added function GetOpenPositionsFromSubaccounts.
protocol/testutil/keeper/clob.go, protocol/x/clob/keeper/keeper.go Added creation and initialization of DaemonLiquidationInfo instance.
protocol/x/clob/abci.go, protocol/x/clob/module.go, protocol/x/clob/module_test.go Removed daemonLiquidationInfo parameter and references, refactored deleveraging logic using DaemonLiquidationInfo.
protocol/x/clob/keeper/deleveraging.go Refactored iteration and processing of deleveraging operations, added error handling and logging.
protocol/x/clob/types/expected_keepers.go, protocol/x/subaccounts/keeper/subaccount.go, protocol/x/subaccounts/keeper/subaccount_test.go Removed ForEachSubaccountRandomStart method and associated tests.
protocol/x/clob/e2e/... Modified tests to adapt to new liquidation and deleveraging logic.

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 X ?


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • You can directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
  • You can tag CodeRabbit on specific lines of code or entire files in the PR by tagging @coderabbitai in a comment. Examples:
  • You can tag @coderabbitai in a PR comment and ask questions about the PR and the codebase. Use quoted replies to pass the context for follow-up questions. Examples:
    • @coderabbitai render interesting statistics about this repository as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai generate unit tests for the src/utils.ts file.

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 a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @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.
  • The JSON schema for the configuration file is available here.
  • 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/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

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.

Review Status

Actionable comments generated: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 0459917 and dc0b82e.
Files selected for processing (16)
  • protocol/app/ante_whitebox_test.go (3 hunks)
  • protocol/app/app.go (1 hunks)
  • protocol/daemons/server/types/liquidations/daemon_liquidation_info.go (1 hunks)
  • protocol/testutil/clob/open_positions.go (1 hunks)
  • protocol/testutil/keeper/clob.go (2 hunks)
  • protocol/x/clob/abci.go (3 hunks)
  • protocol/x/clob/abci_test.go (4 hunks)
  • protocol/x/clob/e2e/liquidation_deleveraging_test.go (2 hunks)
  • protocol/x/clob/keeper/deleveraging.go (1 hunks)
  • protocol/x/clob/keeper/deleveraging_test.go (2 hunks)
  • protocol/x/clob/keeper/keeper.go (4 hunks)
  • protocol/x/clob/module.go (4 hunks)
  • protocol/x/clob/module_test.go (2 hunks)
  • protocol/x/clob/types/expected_keepers.go (1 hunks)
  • protocol/x/subaccounts/keeper/subaccount.go (1 hunks)
  • protocol/x/subaccounts/keeper/subaccount_test.go (2 hunks)
Files skipped from review due to trivial changes (3)
  • protocol/x/clob/module_test.go
  • protocol/x/subaccounts/keeper/subaccount.go
  • protocol/x/subaccounts/keeper/subaccount_test.go
Additional comments: 22
protocol/testutil/clob/open_positions.go (1)
  • 8-42: The function GetOpenPositionsFromSubaccounts is well-structured and follows best practices for iterating over slices and appending to slices in Go. The use of a map to track positions and then converting it to a slice before returning is efficient for avoiding duplicate entries and ensuring unique PerpetualId values.
protocol/x/clob/types/expected_keepers.go (1)
  • 42-47: The removal of the ForEachSubaccountRandomStart method from the SubaccountsKeeper interface aligns with the PR's objective to change the iteration logic for subaccounts. The remaining method GetRandomSubaccount is unchanged, which is consistent with the provided summary.
protocol/app/ante_whitebox_test.go (1)
  • 77-77: The addition of liquidationtypes.NewDaemonLiquidationInfo() in the newTestHandlerOptions function is consistent with the changes made across the codebase to move daemon liquidation information to the keeper level.
protocol/daemons/server/types/liquidations/daemon_liquidation_info.go (1)
  • 119-136: The new method GetSubaccountsWithOpenPositionsForPerpetual added to the DaemonLiquidationInfo type is correctly implemented. It locks the shared resource to prevent race conditions and provides a clear way to retrieve subaccount IDs with open positions based on the perpetual ID and position type.
protocol/x/clob/module.go (1)
  • 115-127: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [106-124]

The restructuring of the AppModule struct to include subaccountsKeeper instead of daemonLiquidationInfo and the corresponding changes in the NewAppModule function are in line with the PR's objective to refactor the subaccount iteration logic and move daemon liquidation information to the keeper level.

protocol/x/clob/keeper/keeper.go (1)
  • 61-61: The declaration of DaemonLiquidationInfo within the Keeper struct and its initialization in the NewKeeper function are consistent with the architectural shift of moving liquidation responsibilities to the keeper level.
protocol/x/clob/abci.go (1)
  • 198-201: The logic to retrieve potentially liquidatable subaccount IDs and attempt to liquidate them using the DaemonLiquidationInfo instance is correctly placed within the PrepareCheckState function, which is part of the ABCI lifecycle where such operations should occur.
protocol/testutil/keeper/clob.go (1)
  • 221-221: The addition of liquidationtypes.NewDaemonLiquidationInfo() in the createClobKeeper function is consistent with the changes made across the codebase to move daemon liquidation information to the keeper level.
protocol/x/clob/keeper/deleveraging.go (1)
  • 286-292: The use of panic within the OffsetSubaccountPerpetualPosition function when encountering an ErrInvalidPerpetualPositionSizeDelta error is a critical behavior. Panicking in a blockchain context can be dangerous as it can halt the entire node. It is essential to ensure that this is the desired behavior and that there are no alternative error handling strategies that could be employed here. If this panic is indeed necessary, it should be clearly documented as to why this is the case.
protocol/x/clob/e2e/liquidation_deleveraging_test.go (2)
  • 13-13: The addition of clobtest package import seems to be necessary for the new test logic introduced. Ensure that this new dependency is properly managed and does not introduce any versioning conflicts.

  • 1127-1128: The LiquidateSubaccountsRequest now includes SubaccountOpenPositionInfo, which is populated using clobtest.GetOpenPositionsFromSubaccounts(tc.subaccounts). This change aligns with the PR's objective to update the test setup to reflect the new subaccount iteration logic. Ensure that the GetOpenPositionsFromSubaccounts function is thoroughly tested to prevent incorrect test behavior.

protocol/x/clob/keeper/deleveraging_test.go (2)
  • 18-18: The addition of the clobtest import is appropriate for the new test utilities being used.

  • 720-721: The introduction of positions and the call to UpdateSubaccountsWithPositions align with the PR's objective to update subaccounts with positions. Ensure that the clobtest.GetOpenPositionsFromSubaccounts function is thoroughly tested to prevent incorrect position updates.

protocol/x/clob/abci_test.go (5)
  • 25-30: The import removal of "liquidationtypes" and the changes in the test functions to use "ks.ClobKeeper.DaemonLiquidationInfo" directly are consistent with the PR's objective to move daemon liquidation information to the keeper level.

  • 1098-1103: The test TestPrepareCheckState_WithProcessProposerMatchesEventsWithBadBlockHeight is designed to ensure that the system panics if the block height does not match the stored events. This is a good test for ensuring data consistency and correct behavior in the face of incorrect block height data.

  • 1124-1129: The test TestCommitBlocker_WithProcessProposerMatchesEventsWithBadBlockHeight appears to be a duplicate of TestPrepareCheckState_WithProcessProposerMatchesEventsWithBadBlockHeight as it tests the same panic condition for block height mismatch. It's important to ensure that this is intentional and that the test is not redundant.

  • 1475-1481: The test TestBeginBlocker_Success correctly initializes the next block's process proposer matches events, which is a critical part of the block lifecycle in the clob module. This test ensures that the state is correctly overwritten, which is essential for the integrity of the block processing.

  • 1475-1481: The test TestPrepareCheckState is comprehensive and tests the clob module's state preparation logic with various scenarios, including edge cases like replaying operations. This ensures that the module behaves correctly under different conditions and that the memclob state is consistent with expectations.

protocol/app/app.go (4)
  • 882-882: The addition of daemonLiquidationInfo as an argument to the New function aligns with the PR's objective to move daemon liquidation information to the keeper level. Ensure that all instances where the New function is called have been updated to pass this new argument.

  • 882-892: The daemonLiquidationInfo is correctly passed to the clobmodulekeeper.NewKeeper. This change centralizes the liquidation logic within the keeper, which is consistent with the PR's objectives. Confirm that the clobmodulekeeper is properly utilizing daemonLiquidationInfo for its operations.

  • 879-892: The initialization order within the New function appears to be correct. However, it's crucial to ensure that the newly added daemonLiquidationInfo does not introduce any dependency issues, especially since it's now being used in the initialization of the ClobKeeper.

  • 879-892: Review the integration of the new daemonLiquidationInfo parameter throughout the New function to ensure it does not disrupt existing module initializations and that it is correctly utilized in all relevant places.

Comment on lines 213 to 236
subaccountsWithOpenPositions := k.DaemonLiquidationInfo.GetSubaccountsWithOpenPositionsForPerpetual(
perpetualId,
deltaQuantumsTotal.Sign() == 1,
)
for _, subaccountId := range subaccountsWithOpenPositions {
// Iterate at most `MaxDeleveragingSubaccountsToIterate` subaccounts.
if numSubaccountsIterated >= k.Flags.MaxDeleveragingSubaccountsToIterate {
break
}

if deltaQuantumsRemaining.Sign() == 0 {
break
}

numSubaccountsIterated++
offsettingSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, subaccountId)
offsettingPosition, _ := offsettingSubaccount.GetPerpetualPositionForId(perpetualId)
bigOffsettingPositionQuantums := offsettingPosition.GetBigQuantums()

// Skip subaccounts that do not have a position in the opposite direction as the liquidated subaccount.
if deltaQuantumsRemaining.Sign() != bigOffsettingPositionQuantums.Sign() {
numSubaccountsWithNoOpenPositionOnOppositeSide++
continue
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The refactoring of the iteration logic in OffsetSubaccountPerpetualPosition to use DaemonLiquidationInfo for obtaining subaccount IDs is a significant change. Ensure that the new logic correctly handles the iteration and early return conditions, especially the check for the sign of deltaQuantumsRemaining and bigOffsettingPositionQuantums to determine if the subaccount has a position in the opposite direction as required.

Additionally, the presence of the TODO comment on line 238 suggests that there is a decision pending on how to determine the max amount to offset per offsetting subaccount. This should be resolved or at least tracked in an issue management system to ensure it is not forgotten.

Comment on lines 246 to 265
// Fetch delta quote quantums. Calculated at bankruptcy price for standard
// deleveraging and at oracle price for final settlement deleveraging.
deltaQuoteQuantums, err := k.getDeleveragingQuoteQuantumsDelta(
ctx,
perpetualId,
liquidatedSubaccountId,
deltaQuantums,
)
if err != nil {
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
k.Logger(ctx).Error(
"Encountered error when getting quote quantums for deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"perpetualId", perpetualId,
"deltaQuantums", deltaQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
if err != nil {
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
k.Logger(ctx).Error(
"Encountered error when getting quote quantums for deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"perpetualId", perpetualId,
"deltaQuantums", deltaQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
return false
}

// Try to process the deleveraging operation for both subaccounts.
if err := k.ProcessDeleveraging(
ctx,
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

The error handling within the iteration loop of OffsetSubaccountPerpetualPosition has been updated. It is important to ensure that the error from getDeleveragingQuoteQuantumsDelta is handled correctly. The current implementation logs the error and continues the loop, which is a reasonable approach if the error is not critical and does not affect the overall operation. However, if the error indicates a more serious issue that could compromise the integrity of the deleveraging process, it may be more appropriate to halt the operation and return the error.

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between dc0b82e and e93a953.
Files selected for processing (2)
  • protocol/x/clob/keeper/deleveraging.go (1 hunks)
  • protocol/x/clob/keeper/liquidations_test.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • protocol/x/clob/keeper/deleveraging.go

deltaQuantums,
// Find subaccounts with open positions on the opposite side of the liquidated subaccount.
isDeleveragingLong := deltaQuantumsTotal.Sign() == -1
subaccountsWithOpenPositions := k.DaemonLiquidationInfo.GetSubaccountsWithOpenPositionsForPerpetual(
Copy link
Contributor

Choose a reason for hiding this comment

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

By doing this we are losing the property that we are start deleveraging with at a random subaccount as the offsetting account. Is this a concern?

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between e93a953 and a5b3b0e.
Files selected for processing (5)
  • protocol/app/app.go (1 hunks)
  • protocol/x/clob/abci.go (3 hunks)
  • protocol/x/clob/keeper/deleveraging.go (1 hunks)
  • protocol/x/clob/keeper/deleveraging_test.go (2 hunks)
  • protocol/x/clob/keeper/liquidations_test.go (1 hunks)
Files skipped from review as they are similar to previous changes (4)
  • protocol/app/app.go
  • protocol/x/clob/abci.go
  • protocol/x/clob/keeper/deleveraging_test.go
  • protocol/x/clob/keeper/liquidations_test.go
Additional comments: 1
protocol/x/clob/keeper/deleveraging.go (1)
  • 236-351: The refactoring of OffsetSubaccountPerpetualPosition to use DaemonLiquidationInfo.GetSubaccountsWithOpenPositionsForPerpetual for iterating subaccounts is a significant change. Ensure that the new logic correctly handles the iteration and that the concerns raised in the previous comment about losing the random start property are addressed or mitigated.

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.

Review Status

Actionable comments generated: 3

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between a5b3b0e and f500089.
Files selected for processing (4)
  • protocol/daemons/server/types/liquidations/daemon_liquidation_info.go (1 hunks)
  • protocol/daemons/server/types/liquidations/daemon_liquidation_info_test.go (8 hunks)
  • protocol/x/clob/abci.go (3 hunks)
  • protocol/x/clob/keeper/deleveraging.go (3 hunks)
Files skipped from review as they are similar to previous changes (2)
  • protocol/x/clob/abci.go
  • protocol/x/clob/keeper/deleveraging.go
Additional comments: 3
protocol/daemons/server/types/liquidations/daemon_liquidation_info.go (1)
  • 115-132: The method GetSubaccountsWithOpenPositionsOnSide is similar to GetSubaccountsWithOpenPositions but filters based on the isLong parameter. The code is clean and follows best practices for locking and defer unlocking. The logic is straightforward and there are no apparent issues with the implementation.
protocol/daemons/server/types/liquidations/daemon_liquidation_info_test.go (2)
  • 65-71: The test TestSubaccountsWithOpenPositions_Multiple_Reads correctly checks the expected results against the output of GetSubaccountsWithOpenPositions. The test ensures that multiple reads return consistent results, which is good for validating the immutability of the read operation.

  • 211-211: The test TestSubaccountsWithOpenPosition_Empty_Update correctly checks that the update with an empty slice results in an empty slice being retrieved. This is a good test for ensuring that the update method handles empty inputs correctly.

Comment on lines +100 to +113
// GetSubaccountsWithOpenPositions returns the list of subaccount ids with open positions for a perpetual.
func (ls *DaemonLiquidationInfo) GetSubaccountsWithOpenPositions(
perpetualId uint32,
) []satypes.SubaccountId {
ls.Lock()
defer ls.Unlock()

result := make(map[uint32]*clobtypes.SubaccountOpenPositionInfo)
for perpetualId, info := range ls.subaccountsWithPositions {
clone := &clobtypes.SubaccountOpenPositionInfo{
PerpetualId: perpetualId,
SubaccountsWithLongPosition: make([]satypes.SubaccountId, len(info.SubaccountsWithLongPosition)),
SubaccountsWithShortPosition: make([]satypes.SubaccountId, len(info.SubaccountsWithShortPosition)),
result := make([]satypes.SubaccountId, 0)
if info, ok := ls.subaccountsWithPositions[perpetualId]; ok {
result = append(result, info.SubaccountsWithLongPosition...)
result = append(result, info.SubaccountsWithShortPosition...)
}
return result
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The implementation of GetSubaccountsWithOpenPositions correctly locks the shared resource, initializes a slice to store results, checks for the existence of the perpetual ID, and appends both long and short positions. However, there is no check for the validity of the perpetualId. Adding a validity check could prevent potential issues with invalid IDs.

func (ls *DaemonLiquidationInfo) GetSubaccountsWithOpenPositions(
	perpetualId uint32,
) []satypes.SubaccountId {
+	// Add validation for perpetualId if applicable
	ls.Lock()
	defer ls.Unlock()
	...
}

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
// GetSubaccountsWithOpenPositions returns the list of subaccount ids with open positions for a perpetual.
func (ls *DaemonLiquidationInfo) GetSubaccountsWithOpenPositions(
perpetualId uint32,
) []satypes.SubaccountId {
ls.Lock()
defer ls.Unlock()
result := make(map[uint32]*clobtypes.SubaccountOpenPositionInfo)
for perpetualId, info := range ls.subaccountsWithPositions {
clone := &clobtypes.SubaccountOpenPositionInfo{
PerpetualId: perpetualId,
SubaccountsWithLongPosition: make([]satypes.SubaccountId, len(info.SubaccountsWithLongPosition)),
SubaccountsWithShortPosition: make([]satypes.SubaccountId, len(info.SubaccountsWithShortPosition)),
result := make([]satypes.SubaccountId, 0)
if info, ok := ls.subaccountsWithPositions[perpetualId]; ok {
result = append(result, info.SubaccountsWithLongPosition...)
result = append(result, info.SubaccountsWithShortPosition...)
}
return result
}
// GetSubaccountsWithOpenPositions returns the list of subaccount ids with open positions for a perpetual.
func (ls *DaemonLiquidationInfo) GetSubaccountsWithOpenPositions(
perpetualId uint32,
) []satypes.SubaccountId {
// Add validation for perpetualId if applicable
ls.Lock()
defer ls.Unlock()
result := make([]satypes.SubaccountId, 0)
if info, ok := ls.subaccountsWithPositions[perpetualId]; ok {
result = append(result, info.SubaccountsWithLongPosition...)
result = append(result, info.SubaccountsWithShortPosition...)
}
return result
}

@@ -14,7 +14,7 @@ func TestNewDaemonLiquidationInfo(t *testing.T) {
ls := liquidationstypes.NewDaemonLiquidationInfo()
require.Empty(t, ls.GetLiquidatableSubaccountIds())
require.Empty(t, ls.GetNegativeTncSubaccountIds())
require.Empty(t, ls.GetSubaccountsWithPositions())
require.Empty(t, ls.GetSubaccountsWithOpenPositions(0))
Copy link
Contributor

Choose a reason for hiding this comment

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

The test TestNewDaemonLiquidationInfo has been updated to use the new method GetSubaccountsWithOpenPositions with a hardcoded 0 as the perpetual ID. This is a good change, but it would be beneficial to test with a non-zero perpetual ID as well to ensure the method works with different inputs.

func TestNewDaemonLiquidationInfo(t *testing.T) {
	...
+	require.Empty(t, ls.GetSubaccountsWithOpenPositions(1)) // Test with a non-zero perpetual ID
}

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
require.Empty(t, ls.GetSubaccountsWithOpenPositions(0))
require.Empty(t, ls.GetSubaccountsWithOpenPositions(1))

@@ -118,7 +119,7 @@ func TestNegativeTncSubaccounts_Multiple_Writes(t *testing.T) {

func TestSubaccountsWithOpenPositions_Multiple_Writes(t *testing.T) {
ls := liquidationstypes.NewDaemonLiquidationInfo()
require.Empty(t, ls.GetSubaccountsWithPositions())
require.Empty(t, ls.GetSubaccountsWithOpenPositions(0))
Copy link
Contributor

Choose a reason for hiding this comment

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

In TestSubaccountsWithOpenPositions_Multiple_Writes, the initial require.Empty check is good for ensuring the state before the test begins. It would be beneficial to include tests for different perpetual IDs to ensure that the update and retrieval methods work correctly across different IDs.

func TestSubaccountsWithOpenPositions_Multiple_Writes(t *testing.T) {
	...
+	// Additional tests for different perpetual IDs should be included
}

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
require.Empty(t, ls.GetSubaccountsWithOpenPositions(0))
require.Empty(t, ls.GetSubaccountsWithOpenPositions(0))
// Additional tests for different perpetual IDs should be included

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between f500089 and 6e8359d.
Files selected for processing (1)
  • protocol/x/clob/keeper/deleveraging.go (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • protocol/x/clob/keeper/deleveraging.go

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.

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 6e8359d and 3fc8810.
Files selected for processing (2)
  • protocol/x/clob/e2e/liquidation_deleveraging_test.go (5 hunks)
  • protocol/x/clob/keeper/deleveraging.go (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • protocol/x/clob/e2e/liquidation_deleveraging_test.go
Additional comments: 4
protocol/x/clob/keeper/deleveraging.go (4)
  • 236-241: The new logic introduced to find subaccounts with open positions on the opposite side is a significant change. Ensure that the GetSubaccountsWithOpenPositionsOnSide method is thoroughly tested, especially for edge cases where subaccounts may not have positions on the expected side.

  • 256-258: The use of pseudoRand to determine the starting index for iteration introduces a deterministic randomness based on the current context. Verify that the GetPseudoRand method generates a sufficiently random value and that this randomness is appropriate for the use case.

  • 574-577: The method GetSubaccountsWithOpenPositions is used to retrieve subaccounts with positions in final settlement markets. Ensure that this method is optimized for performance since it could potentially be a costly operation depending on the number of subaccounts and positions.

  • 233-369: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [368-371]

The metrics and telemetry calls are used to monitor the performance and behavior of the deleveraging process. Verify that the labels and metrics used are consistent with the rest of the application and that they provide meaningful insights for monitoring.

Comment on lines +263 to +365
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
deltaBaseQuantums,
deltaQuoteQuantums,
); err == nil {
// Update the remaining liquidatable quantums.
deltaQuantumsRemaining = new(big.Int).Sub(
deltaQuantumsRemaining,
deltaBaseQuantums,
)
fills = append(fills, types.MatchPerpetualDeleveraging_Fill{
OffsettingSubaccountId: *offsettingSubaccount.Id,
FillAmount: new(big.Int).Abs(deltaBaseQuantums).Uint64(),
})

// Send on-chain update for the deleveraging. The events are stored in a TransientStore which should be rolled-back
// if the branched state is discarded, so batching is not necessary.
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeDeleveraging,
indexerevents.DeleveragingEventVersion,
indexer_manager.GetBytes(
indexerevents.NewDeleveragingEvent(
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
satypes.BaseQuantums(new(big.Int).Abs(deltaBaseQuantums).Uint64()),
satypes.BaseQuantums(deltaQuoteQuantums.Uint64()),
deltaBaseQuantums.Sign() > 0,
isFinalSettlement,
),
indexerevents.SubtypeDeleveraging,
indexerevents.DeleveragingEventVersion,
indexer_manager.GetBytes(
indexerevents.NewDeleveragingEvent(
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
satypes.BaseQuantums(new(big.Int).Abs(deltaBaseQuantums).Uint64()),
satypes.BaseQuantums(deltaQuoteQuantums.Uint64()),
deltaBaseQuantums.Sign() > 0,
isFinalSettlement,
),
)
} else if errors.Is(err, types.ErrInvalidPerpetualPositionSizeDelta) {
panic(
fmt.Sprintf(
"Invalid perpetual position size delta when processing deleveraging. error: %v",
err,
),
)
} else {
// If an error is returned, it's likely because the subaccounts' bankruptcy prices do not overlap.
// TODO(CLOB-75): Support deleveraging subaccounts with non overlapping bankruptcy prices.
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
offsettingSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, *offsettingSubaccount.Id)
k.Logger(ctx).Debug(
"Encountered error when processing deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"checkTx", ctx.IsCheckTx(),
"perpetualId", perpetualId,
"deltaQuantums", deltaBaseQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
numSubaccountsWithNonOverlappingBankruptcyPrices++
}

return deltaQuantumsRemaining.Sign() == 0
},
k.GetPseudoRand(ctx),
)
),
)
} else if errors.Is(err, types.ErrInvalidPerpetualPositionSizeDelta) {
panic(
fmt.Sprintf(
"Invalid perpetual position size delta when processing deleveraging. error: %v",
err,
),
)
} else {
// If an error is returned, it's likely because the subaccounts' bankruptcy prices do not overlap.
// TODO(CLOB-75): Support deleveraging subaccounts with non overlapping bankruptcy prices.
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
offsettingSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, *offsettingSubaccount.Id)
k.Logger(ctx).Debug(
"Encountered error when processing deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"checkTx", ctx.IsCheckTx(),
"perpetualId", perpetualId,
"deltaBaseQuantums", deltaBaseQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
numSubaccountsWithNonOverlappingBankruptcyPrices++
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The loop iterates over a subset of subaccounts and processes deleveraging. It's important to ensure that the error handling within the loop is robust and that the panic condition at lines 342-348 is justified. Panics should be avoided in production code unless absolutely necessary. Consider replacing the panic with a more graceful error handling mechanism.

- panic(
-   fmt.Sprintf(
-     "Invalid perpetual position size delta when processing deleveraging. error: %v",
-     err,
-   ),
- )
+ // Handle the error more gracefully instead of panicking
+ k.Logger(ctx).Error(
+   "Invalid perpetual position size delta when processing deleveraging",
+   "error", err,
+ )
+ return err

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
for i := 0; i < numSubaccountsToIterate && deltaQuantumsRemaining.Sign() != 0; i++ {
index := (i + indexOffset) % numSubaccounts
subaccountId := subaccountsWithOpenPositions[index]
numSubaccountsIterated++
offsettingSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, subaccountId)
offsettingPosition, _ := offsettingSubaccount.GetPerpetualPositionForId(perpetualId)
bigOffsettingPositionQuantums := offsettingPosition.GetBigQuantums()
// Skip subaccounts that do not have a position in the opposite direction as the liquidated subaccount.
if deltaQuantumsRemaining.Sign() != bigOffsettingPositionQuantums.Sign() {
numSubaccountsWithNoOpenPositionOnOppositeSide++
continue
}
// TODO(DEC-1495): Determine max amount to offset per offsetting subaccount.
var deltaBaseQuantums *big.Int
if deltaQuantumsRemaining.CmpAbs(bigOffsettingPositionQuantums) > 0 {
deltaBaseQuantums = new(big.Int).Set(bigOffsettingPositionQuantums)
} else {
deltaBaseQuantums = new(big.Int).Set(deltaQuantumsRemaining)
}
// Fetch delta quote quantums. Calculated at bankruptcy price for standard
// deleveraging and at oracle price for final settlement deleveraging.
deltaQuoteQuantums, err := k.getDeleveragingQuoteQuantumsDelta(
ctx,
perpetualId,
liquidatedSubaccountId,
deltaBaseQuantums,
isFinalSettlement,
)
if err != nil {
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
k.Logger(ctx).Error(
"Encountered error when getting quote quantums for deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"perpetualId", perpetualId,
"deltaBaseQuantums", deltaBaseQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
if err != nil {
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
k.Logger(ctx).Error(
"Encountered error when getting quote quantums for deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"perpetualId", perpetualId,
"deltaBaseQuantums", deltaBaseQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
"isFinalSettlement", isFinalSettlement,
)
return false
}
// Try to process the deleveraging operation for both subaccounts.
if err := k.ProcessDeleveraging(
continue
}
// Try to process the deleveraging operation for both subaccounts.
if err := k.ProcessDeleveraging(
ctx,
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
deltaBaseQuantums,
deltaQuoteQuantums,
); err == nil {
// Update the remaining liquidatable quantums.
deltaQuantumsRemaining.Sub(deltaQuantumsRemaining, deltaBaseQuantums)
fills = append(fills, types.MatchPerpetualDeleveraging_Fill{
OffsettingSubaccountId: *offsettingSubaccount.Id,
FillAmount: new(big.Int).Abs(deltaBaseQuantums).Uint64(),
})
// Send on-chain update for the deleveraging. The events are stored in a TransientStore which should be rolled-back
// if the branched state is discarded, so batching is not necessary.
k.GetIndexerEventManager().AddTxnEvent(
ctx,
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
deltaBaseQuantums,
deltaQuoteQuantums,
); err == nil {
// Update the remaining liquidatable quantums.
deltaQuantumsRemaining = new(big.Int).Sub(
deltaQuantumsRemaining,
deltaBaseQuantums,
)
fills = append(fills, types.MatchPerpetualDeleveraging_Fill{
OffsettingSubaccountId: *offsettingSubaccount.Id,
FillAmount: new(big.Int).Abs(deltaBaseQuantums).Uint64(),
})
// Send on-chain update for the deleveraging. The events are stored in a TransientStore which should be rolled-back
// if the branched state is discarded, so batching is not necessary.
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeDeleveraging,
indexerevents.DeleveragingEventVersion,
indexer_manager.GetBytes(
indexerevents.NewDeleveragingEvent(
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
satypes.BaseQuantums(new(big.Int).Abs(deltaBaseQuantums).Uint64()),
satypes.BaseQuantums(deltaQuoteQuantums.Uint64()),
deltaBaseQuantums.Sign() > 0,
isFinalSettlement,
),
indexerevents.SubtypeDeleveraging,
indexerevents.DeleveragingEventVersion,
indexer_manager.GetBytes(
indexerevents.NewDeleveragingEvent(
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
satypes.BaseQuantums(new(big.Int).Abs(deltaBaseQuantums).Uint64()),
satypes.BaseQuantums(deltaQuoteQuantums.Uint64()),
deltaBaseQuantums.Sign() > 0,
isFinalSettlement,
),
)
} else if errors.Is(err, types.ErrInvalidPerpetualPositionSizeDelta) {
panic(
fmt.Sprintf(
"Invalid perpetual position size delta when processing deleveraging. error: %v",
err,
),
)
} else {
// If an error is returned, it's likely because the subaccounts' bankruptcy prices do not overlap.
// TODO(CLOB-75): Support deleveraging subaccounts with non overlapping bankruptcy prices.
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
offsettingSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, *offsettingSubaccount.Id)
k.Logger(ctx).Debug(
"Encountered error when processing deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"checkTx", ctx.IsCheckTx(),
"perpetualId", perpetualId,
"deltaQuantums", deltaBaseQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
numSubaccountsWithNonOverlappingBankruptcyPrices++
}
return deltaQuantumsRemaining.Sign() == 0
},
k.GetPseudoRand(ctx),
)
),
)
} else if errors.Is(err, types.ErrInvalidPerpetualPositionSizeDelta) {
panic(
fmt.Sprintf(
"Invalid perpetual position size delta when processing deleveraging. error: %v",
err,
),
)
} else {
// If an error is returned, it's likely because the subaccounts' bankruptcy prices do not overlap.
// TODO(CLOB-75): Support deleveraging subaccounts with non overlapping bankruptcy prices.
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
offsettingSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, *offsettingSubaccount.Id)
k.Logger(ctx).Debug(
"Encountered error when processing deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"checkTx", ctx.IsCheckTx(),
"perpetualId", perpetualId,
"deltaBaseQuantums", deltaBaseQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
numSubaccountsWithNonOverlappingBankruptcyPrices++
}
// Try to process the deleveraging operation for both subaccounts.
if err := k.ProcessDeleveraging(
ctx,
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
deltaBaseQuantums,
deltaQuoteQuantums,
); err == nil {
// Update the remaining liquidatable quantums.
deltaQuantumsRemaining.Sub(deltaQuantumsRemaining, deltaBaseQuantums)
fills = append(fills, types.MatchPerpetualDeleveraging_Fill{
OffsettingSubaccountId: *offsettingSubaccount.Id,
FillAmount: new(big.Int).Abs(deltaBaseQuantums).Uint64(),
})
// Send on-chain update for the deleveraging. The events are stored in a TransientStore which should be rolled-back
// if the branched state is discarded, so batching is not necessary.
k.GetIndexerEventManager().AddTxnEvent(
ctx,
indexerevents.SubtypeDeleveraging,
indexerevents.DeleveragingEventVersion,
indexer_manager.GetBytes(
indexerevents.NewDeleveragingEvent(
liquidatedSubaccountId,
*offsettingSubaccount.Id,
perpetualId,
satypes.BaseQuantums(new(big.Int).Abs(deltaBaseQuantums).Uint64()),
satypes.BaseQuantums(deltaQuoteQuantums.Uint64()),
deltaBaseQuantums.Sign() > 0,
isFinalSettlement,
),
),
)
} else if errors.Is(err, types.ErrInvalidPerpetualPositionSizeDelta) {
// Handle the error more gracefully instead of panicking
k.Logger(ctx).Error(
"Invalid perpetual position size delta when processing deleveraging",
"error", err,
)
return err
} else {
// If an error is returned, it's likely because the subaccounts' bankruptcy prices do not overlap.
// TODO(CLOB-75): Support deleveraging subaccounts with non overlapping bankruptcy prices.
liquidatedSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, liquidatedSubaccountId)
offsettingSubaccount := k.subaccountsKeeper.GetSubaccount(ctx, *offsettingSubaccount.Id)
k.Logger(ctx).Debug(
"Encountered error when processing deleveraging",
"error", err,
"blockHeight", ctx.BlockHeight(),
"checkTx", ctx.IsCheckTx(),
"perpetualId", perpetualId,
"deltaBaseQuantums", deltaBaseQuantums,
"liquidatedSubaccount", liquidatedSubaccount,
"offsettingSubaccount", offsettingSubaccount,
)
numSubaccountsWithNonOverlappingBankruptcyPrices++
}

@jayy04 jayy04 merged commit cc235f0 into main Dec 22, 2023
17 of 18 checks passed
@jayy04 jayy04 deleted the jy/optimize-deleveraging branch December 22, 2023 22:48
Comment on lines +247 to +252
"Failed to find subaccounts with open positions on opposite side of liquidated subaccount",
"blockHeight", ctx.BlockHeight(),
"perpetualId", perpetualId,
"deltaQuantumsTotal", deltaQuantumsTotal,
"liquidatedSubaccount", liquidatedSubaccount,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: is it worth noting in the error log that the daemon might have an invalid historical view of the subaccounts with open positions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

3 participants