-
Notifications
You must be signed in to change notification settings - Fork 129
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
[TRA-81] Check isolated market constraints in UpdateSubaccount. #1158
Changes from 2 commits
78eee26
151a5d7
9d996f9
104e3b3
8123415
603f470
d51033f
f4a7413
733c4c3
3b52873
ea0985b
124bc3f
a0ed3da
6245b7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package keeper | ||
|
||
import ( | ||
errorsmod "cosmossdk.io/errors" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/dydxprotocol/v4-chain/protocol/lib" | ||
perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" | ||
"github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" | ||
) | ||
|
||
// checkIsolatedSubaccountConstaints will validate all `updates` to the relevant subaccounts against | ||
// isolated subaccount constraints. | ||
// The `updates` have to contain `Subaccounts` with unique `SubaccountIds`. | ||
// The input subaccounts must be settled. | ||
// | ||
// Returns a `success` value of `true` if all updates are valid. | ||
// Returns a `successPerUpdates` value, which is a slice of `UpdateResult`. | ||
// These map to the updates and are used to indicate which of the updates | ||
// caused a failure, if any. | ||
func (k Keeper) checkIsolatedSubaccountConstraints( | ||
ctx sdk.Context, | ||
settledUpdates []settledUpdate, | ||
perpetuals []perptypes.Perpetual, | ||
) ( | ||
success bool, | ||
successPerUpdate []types.UpdateResult, | ||
err error, | ||
) { | ||
success = true | ||
successPerUpdate = make([]types.UpdateResult, len(settledUpdates)) | ||
var idOfSettledUpdates = make(map[types.SubaccountId]struct{}) | ||
var perpIdToMarketType = make(map[uint32]perptypes.PerpetualMarketType) | ||
|
||
for _, perpetual := range perpetuals { | ||
perpIdToMarketType[perpetual.GetId()] = perpetual.Params.MarketType | ||
} | ||
|
||
for i, u := range settledUpdates { | ||
_, exists := idOfSettledUpdates[*u.SettledSubaccount.Id] | ||
|
||
if exists { | ||
return false, nil, types.ErrNonUniqueUpdatesSubaccount | ||
} | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
result, err := isValidIsolatedPerpetualUpdates(u, perpIdToMarketType) | ||
if err != nil { | ||
return false, nil, err | ||
} | ||
if result != types.Success { | ||
success = false | ||
} | ||
|
||
successPerUpdate[i] = result | ||
idOfSettledUpdates[*u.SettledSubaccount.Id] = struct{}{} | ||
} | ||
|
||
return success, successPerUpdate, nil | ||
} | ||
|
||
// Checks whether the perpetual updates to a settled subaccount violates constraints for isolated | ||
// perpetuals. This function assumes the settled subaccount is valid and does not violate the | ||
// the constraints. | ||
// The constraint being checked is: | ||
// - a subaccount with a position in an isolated perpetual cannot have updates other perpetuals | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// - a subaccount with a position in a non-isolated perpetual cannot have updates for isolated | ||
// perpetuals | ||
// - a subaccount with no positions cannot be updated to have positions in multiple isolated | ||
// perpetuals or a combination of isolated and non-isolated perpetuals | ||
func isValidIsolatedPerpetualUpdates( | ||
settledUpdate settledUpdate, | ||
perpIdToMarketType map[uint32]perptypes.PerpetualMarketType, | ||
) (types.UpdateResult, error) { | ||
// If there are no perpetual updates, then this update does not violate constraints for isolated | ||
// markets. | ||
if len(settledUpdate.PerpetualUpdates) == 0 { | ||
return types.Success, nil | ||
} | ||
|
||
// Check if the updates contain an update to an isolated perpetual. | ||
hasIsolatedUpdate := false | ||
isolatedUpdatePerpetualId := uint32(0) | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for _, perpetualUpdate := range settledUpdate.PerpetualUpdates { | ||
marketType, exists := perpIdToMarketType[perpetualUpdate.PerpetualId] | ||
if !exists { | ||
return types.UpdateCausedError, errorsmod.Wrap( | ||
perptypes.ErrPerpetualDoesNotExist, lib.UintToString(perpetualUpdate.PerpetualId), | ||
) | ||
} | ||
|
||
if marketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED { | ||
hasIsolatedUpdate = true | ||
isolatedUpdatePerpetualId = perpetualUpdate.PerpetualId | ||
} | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Check if the subaccount has a position in an isolated perpetual. | ||
// Assumes the subaccount itself does not violate the isolated perpetual constraints. | ||
isIsolatedSubaccount := false | ||
isolatedPositionPerpetualId := uint32(0) | ||
hasPerpetualPositions := len(settledUpdate.SettledSubaccount.PerpetualPositions) > 0 | ||
for _, perpetualPosition := range settledUpdate.SettledSubaccount.PerpetualPositions { | ||
marketType, exists := perpIdToMarketType[perpetualPosition.PerpetualId] | ||
if !exists { | ||
return types.UpdateCausedError, errorsmod.Wrap( | ||
perptypes.ErrPerpetualDoesNotExist, lib.UintToString(perpetualPosition.PerpetualId), | ||
) | ||
} | ||
|
||
if marketType == perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED { | ||
isIsolatedSubaccount = true | ||
isolatedPositionPerpetualId = perpetualPosition.PerpetualId | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
// A subaccount with a perpetual position in an isolated perpetual cannot have updates to other | ||
// non-isolated perpetuals. | ||
if isIsolatedSubaccount && !hasIsolatedUpdate { | ||
return types.ViolatesIsolatedSubaccountConstraints, nil | ||
} | ||
|
||
// A subaccount with perpetual positions in non-isolated perpetuals cannot have an update | ||
// to an isolated perpetual. | ||
if !isIsolatedSubaccount && hasPerpetualPositions && hasIsolatedUpdate { | ||
return types.ViolatesIsolatedSubaccountConstraints, nil | ||
} | ||
|
||
// There cannot be more than a single perpetual update if an update to an isolated perpetual | ||
// exists in the slice of perpetual updates. | ||
if hasIsolatedUpdate && len(settledUpdate.PerpetualUpdates) > 1 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible we have multiple perpetual update entries with the same isolated perpetual? |
||
return types.ViolatesIsolatedSubaccountConstraints, nil | ||
} | ||
|
||
// Note we can assume that if `hasIsolatedUpdate` is true, there is only a single perpetual | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// update for the subaccount, given the above check. | ||
// A subaccount with a perpetual position in an isolated perpetual cannot have an update to | ||
// another isolated perpetual. | ||
if isIsolatedSubaccount && | ||
hasIsolatedUpdate && | ||
isolatedPositionPerpetualId != isolatedUpdatePerpetualId { | ||
return types.ViolatesIsolatedSubaccountConstraints, nil | ||
} | ||
|
||
return types.Success, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -292,6 +292,13 @@ func (k Keeper) UpdateSubaccounts( | |
perpIdToFundingIndex[perp.Params.Id] = perp.FundingIndex | ||
} | ||
|
||
// Check if the updates satisfy the isolated perpetual constraints. | ||
success, successPerUpdate, err = k.checkIsolatedSubaccountConstraints( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we put this in Either way, should probably put this check before the collateralization check logic in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 to this comment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's true that it can be added to the orderbook since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the unique subaccount check in |
||
ctx, settledUpdates, allPerps) | ||
if !success || err != nil { | ||
return success, successPerUpdate, err | ||
} | ||
|
||
// Apply the updates to perpetual positions. | ||
UpdatePerpetualPositions( | ||
settledUpdates, | ||
vincentwschau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my understanding, do we ever expect these constraints to fail during normal operations? If isolated subaccounts is abstracted away from the user, then upstream
x/clob
logic would be responsible for populating the updates correctly right? So this is more of an internal sanity check?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upstream
x/clob
logic relies onUpdateSubaccounts
to determine if the updates to a subaccount for a match are valid. This isn't an internal sanity check, this would be the function / logic that would ensure orders which result in a match that would lead to an invalid subaccount state (based on these new constraints from isolated markets) result in an error.So during "normal" operations, it is possible for a user with an isolated subaccount to place an order for a different perpetual. Only when a match occurs, would the update fail and the order fail.
There is additional non-scoped work to add logic to
x/clob
to prevent users from even sending orders that would lead to matches that are invalid based on the isolated market constraints.