Austin Haines
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Margin Trading Module Implementation This companion document is an implementation guide to the margin trading system in the [Preliminary Math Specification](https://hackmd.io/maG-kkCeRbKOcup5gfxSWg) and [Mechanism Specification](https://hackmd.io/e2K_VzC-TFyC8MDjqHwczA). ### Notes: A few mechanisms and experiments from the Margin Trading Model list do not have counterparts in this implementation guide. Links are to the merged notebooks: - [excess_liabilities](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/jesse/excess_liabilities.ipynb) - [force_close_rewards](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/jesse/force_close_rewards.ipynb) - [force_close_any_address](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/jesse/force_close_address.ipynb) - [global_settlement](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/jesse/force_close_rewards.ipynb) - ## State ### Liquidity Pool Global State State necessary to maintain legal state of the liquidity module with margin trading enabled. | Name | Schema | | ---- | ------ | | Pool | Struct | | Variable Name | Variable Type | Descriptive Name | Description | Symbol | | ------------------- | ------------- | ------------------------------ | ---------------------------------------------------- | -------------- | | ExternalAssetBalance | sdk.Uint | External Assets | Quantity of external tokens as an asset | $X_{A}$ | | ExternalLiabilities | sdk.Uint | External Principal Liabilities | Quantity of external tokens as a principal liability | $X^P_{L}$ | | ExternalCustody | sdk.Uint | External Custody | Quantity of external tokens held in custody | $X_{C}$ | | NativeAssetBalance | sdk.Uint | Native Assets | Quantity of native tokens as an asset | $Y_{A}$ | | NativeLiabilities | sdk.Uint | Native Principal Liabilities | Quantity of native tokens as a liability | $Y^P_{L}$ | | NativeCustody | sdk.Uint | Native Custody | Quantity of native tokens held in custody | $Y_{C}$ | | Health | sdk.Dec | Pool Health | Metric describing the health of an individual Pool | $H(\mathbf X)$ | | InterestRate | sdk.Dec | Effective Interest Rate for the current epoch | Interest Rate for the current epoch, respecting the limits in both value and differential movement | $\beta$ | | MarginEnabled | bool | Margin Enabled Parameter | Param for enabling margin on individual pool | ### Local MTP State ###### tags: `Ready for Review` Local state is the state necessary for having a legal and active account with a margin trading position ($mtp$). | Name | Schema | | ---- | ------ | | MTP | Struct | | Variable Name | Variable Type | Descriptive Name | Description | Symbol | | ------------------- | -------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -------------- | | Address | sdk.AccAddress | Agent address | | CollateralAmount | sdk.Uint | Size of action (external) (asset) | Quantity of tokens of type X used to perform action (collateral) | $x_{A}$ | | CollateralAsset | clpTypes.Asset | Symbol of action (external) (asset) | Quantity of tokens of type X used to perform action (collateral) | | | Liabilities | sdk.Uint | Size of action (x) (liability | Quantity of tokens of type X in principal liabilities taken on | $x^P_{L}$ | | InterestUnpaidCollateral | sdk.Uint | Size of action (x) (liability) | Quantity of tokens of type X in interest liabilities accrued and pending payment. Denominated in collateral asset. | $x^E_{L}$ | | InterestPaidCollateral | sdk.Uint | Size of action (x) (liability | Quantity of tokens of type X accrued as interest and paid by position. Denominated in collateral asset. | | | InterestPaidCustody | sdk.Uint | Size of action (x) (liability | Quantity of tokens of type X accrued as interest and paid by position. Denominated in custody asset. | | | CustodyAmount | sdk.Uint | Size of action (y) (asset) | Quantity of tokens of type Y that are held in custody by taking on liability position in x | $y_{C}$ | | CustodyAsset | clpTypes.Asset | Symbol of action (y) (asset) | Quantity of tokens of type Y that are held in custody by taking on liability position in x | $y_{C}$ | | Leverage | sdk.Uint | Leverage | Leverage multiplier on amount borrowed | $\eta_{mtp}$ | | Health | sdk.Dec | MTP Health | Metric describing the health of an individual MTP | $h(\mathbf x)$ | ### Global Margin (Stateful) Parameters Stateful parameters are akin to the Parameter dictionary implemented in a Cosmos SDK. Values stored in the parameter dictionary may be constant, but they do not need to be. Methods to update parameter values may be implemented as well. The Stateful Parameter set is necessary to maintain the Global State of the state variables listed above. Thus, the distinction between the two is nuanced. Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/proto/sifnode/margin/v1/types.proto#L9 | Name| Schema | |-----|----------| | Params| Struct | | Variable Name | Variable Type | Descriptive Name | Description | Symbol | | -------------------- | ------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | | InterestRateMax | sdk.Dec | Maximum interest rate | Maximum interest rate that an $mtp$ can incur when it's open. Typically applies to unhealthy $mtp$'s. | $\beta_{max}$ | | InterestRateMin | sdk.Dec | Minimum interest rate | Minimum interest rate that an $mtp$ can incur when it's open. Set by the liquidity pool and typically applies to well performing $mtp$'s. | $\beta_{min}$ | | InterestRateIncrease | sdk.Dec | Interest Rate Change Increase | Allowable Change for Interest Rate in the Increasing Direction | $\Delta\beta_{\uparrow}$ | | InterestRateDecrease | sdk.Dec | Interest Rate Change Decrease | Allowable Change for Interest Rate in the Decreasing Direction | $\Delta\beta_{\downarrow}$ | | HealthGainFactor | sdk.Dec | Health Gain Factor | Gain factor used for borrow fee calculations | ${K_H}$ | | margin_maintain_a | sdk.Dec | Margin Maintenance Threshold | The value of h(x) upon mtp creation. Not implemented. | $a$ | | SafetyFactor | sdk.Dec | Default Threshold | Health level below which any agent in the pool can cover the defaulted MTP | $b$ | | LeverageMax | sdk.Uint | Maximum allowed leverage | Maximum leverage allowed when opening an MTP | $\eta_{max}$ | | opening_position_max | sdk.Uint | Maximum opening position | Maximum size of position permitted to open an MTP. Not implemented. | $\eta_{max}(X)$ | | EpochLength | int64 | Epoch Length | Defined number of blocks (or time) where upcoming borrow rate is computed for each $mtp$ | $T$ | | RemovalQueueThreshold | sdk.Dec | Removal Queue Threshold | Pool health level below which any removal request is queued | $\bar{H}$ | | MaxOpenPositions | uint64 | Maximum positions open | The cap of number of open positions at which no new positions can be opened. | | InsuranceFundAddress | string | Address of insurance fund wallet | The Sifchain address of the wallet to which insurance funds should go. | | ForceCloseFundPercentage | sdk.Dec | Force close insurance fund proceed percentage | The percentage of proceeds from force closes that should go to insurance fund | | PoolOpenThreshold | sdk.Dec | Pool health threshold for opens | The pool health threshold at which new positions cannot be opened | | SqModifier | sdk.Dec | Removal queue formula modifier | The modifier value for the removal queue's SQ formula | ClosedPools | []string | Pools that are closed | Closed pools cannot have new positions opened on them | IncrementalInterestPaymentEnabled | bool | Incremental interest payments enabled | Toggle to enable/disable incremental interest payments. | ### Rebalancing Enabled Parameter on the Liquidity Subsystem | Name | Schema | | ------------------------------ | ------ | | From_Rebalancing_Parameter_Set | Struct | | Variable Name | Variable Type | Descriptive Name | Description | Symbol | | ------------- | ------------- | ---------------- | ------------------------------------------------------------------ | ----------- | | lambda_v | sdk.Dec | Lambda Validator | Rebalancing coefficent for validator subsystem, computed in module | $\lambda_v$ | ## Messages `Agent Action Space` ### Open _mtp_ ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/types/tx.pb.go#L32 ```go type MsgOpen struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` CollateralAsset string `protobuf:"bytes,2,opt,name=collateral_asset,json=collateralAsset,proto3" json:"collateral_asset,omitempty"` CollateralAmount github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,3,opt,name=collateral_amount,json=collateralAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"collateral_amount"` BorrowAsset string `protobuf:"bytes,4,opt,name=borrow_asset,json=borrowAsset,proto3" json:"borrow_asset,omitempty"` Position Position `protobuf:"varint,5,opt,name=position,proto3,enum=sifnode.margin.v1.Position" json:"position,omitempty"` } ``` ### Close _mtp_ ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/types/tx.pb.go#L138 ```go type MsgClose struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` } ``` ### Force Close _mtp_ ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/types/tx.pb.go#L226 ```go type MsgForceClose struct { Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` MtpAddress string `protobuf:"bytes,2,opt,name=mtp_address,json=mtpAddress,proto3" json:"mtp_address,omitempty"` Id uint64 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` } ``` ## Errors ```go var ( ErrMTPDoesNotExist = sdkerrors.Register("margin", 1, "mtp not found") ErrMTPInvalid = sdkerrors.Register("margin", 2, "mtp invalid") ErrMTPDisabled = sdkerrors.Register("margin", 3, "margin not enabled for pool") ErrUnknownRequest = sdkerrors.Register("margin", 4, "unknown request") ErrMTPHealthy = sdkerrors.Register("margin", 5, "mtp health above force close threshold") ErrInvalidPosition = sdkerrors.Register("margin", 6, "mtp position invalid") ErrMaxOpenPositions = sdkerrors.Register("margin", 7, "max open positions reached") ErrUnauthorised = sdkerrors.Register("margin", 8, "address not on whitelist") ) ``` ```go var ( ErrQueued = sdkerrors.Register("clp", 40, "Cannot process immediately, request has been queued") ErrRemovalsBlockedByHealth = sdkerrors.Register("clp", 41, "Cannot remove liquidity due to low pool health") ) ``` ## Events `Tracing agent action execution` ### Open _mtp_ Event ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/msg_server.go#L75 ```go ctx.EventManager().EmitEvent(sdk.NewEvent("margin/mtp_open", sdk.NewAttribute("id", strconv.FormatInt(int64(mtp.Id), 10)), sdk.NewAttribute("position", mtp.Position.String()), sdk.NewAttribute("address", mtp.Address), sdk.NewAttribute("collateral_asset", mtp.CollateralAsset), sdk.NewAttribute("collateral_amount", mtp.CollateralAmount.String()), sdk.NewAttribute("custody_asset", mtp.CustodyAsset), sdk.NewAttribute("custody_amount", mtp.CustodyAmount.String()), sdk.NewAttribute("leverage", mtp.Leverage.String()), sdk.NewAttribute("liabilities", mtp.Liabilities.String()), sdk.NewAttribute("interest_paid", mtp.InterestPaid.String()), sdk.NewAttribute("interest_unpaid", mtp.InterestUnpaid.String()), sdk.NewAttribute("health", mtp.MtpHealth.String()), )) ``` ### Close _mtp_ Event ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/msg_server.go#L93 ```go ctx.EventManager().EmitEvent(sdk.NewEvent("margin/mtp_close", sdk.NewAttribute("id", strconv.FormatInt(int64(closedMtp.Id), 10)), sdk.NewAttribute("position", closedMtp.Position.String()), sdk.NewAttribute("address", closedMtp.Address), sdk.NewAttribute("collateral_asset", closedMtp.CollateralAsset), sdk.NewAttribute("collateral_amount", closedMtp.CollateralAmount.String()), sdk.NewAttribute("custody_asset", closedMtp.CustodyAsset), sdk.NewAttribute("custody_amount", closedMtp.CustodyAmount.String()), sdk.NewAttribute("repay_amount", repayAmount.String()), sdk.NewAttribute("leverage", closedMtp.Leverage.String()), sdk.NewAttribute("liabilities", closedMtp.Liabilities.String()), sdk.NewAttribute("interest_paid", mtp.InterestPaid.String()), sdk.NewAttribute("interest_unpaid", closedMtp.InterestUnpaid.String()), sdk.NewAttribute("health", closedMtp.MtpHealth.String()), )) ``` ### Force Close _mtp_ Event ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/events.go#L14 ```go ctx.EventManager().EmitEvent(sdk.NewEvent("margin/mtp_force_close", sdk.NewAttribute("id", strconv.FormatInt(int64(mtp.Id), 10)), sdk.NewAttribute("position", mtp.Position.String()), sdk.NewAttribute("address", mtp.Address), sdk.NewAttribute("collateral_asset", mtp.CollateralAsset), sdk.NewAttribute("collateral_amount", mtp.CollateralAmount.String()), sdk.NewAttribute("custody_asset", mtp.CustodyAsset), sdk.NewAttribute("custody_amount", mtp.CustodyAmount.String()), sdk.NewAttribute("repay_amount", repayAmount.String()), sdk.NewAttribute("leverage", mtp.Leverage.String()), sdk.NewAttribute("liabilities", mtp.Liabilities.String()), sdk.NewAttribute("interest_paid", mtp.InterestUnpaid.String()), sdk.NewAttribute("interest_unpaid", mtp.InterestUnpaid.String()), sdk.NewAttribute("health", mtp.MtpHealth.String()), sdk.NewAttribute("closer", closer), )) ``` ## Message Server `Permissible Agent Action Determination Posterior` Message Server functions fulfill requests submitted through messages. Margin Message Server Functions are representations of Composite Mechanisms as outlined in the [Sifchain Cryptoeconomics Paper](https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Composite_Mechanisms). Each message server function handles routing messages through relevant Keeper Functions (Primitive Mechanisms). ###### tags: `v1.0` ```go type msgServer struct { Keeper } ``` ### Open _mtp_ long ###### tags: `v1.0` Message Server Logic for Open MTP Long Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Open-mtp1 Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/open_swap_custody_x.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/5_open_mtp.ipynb - Status: Merged Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/msg_server_test.go#L34 Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/msg_server.go#L165 #### Open Long Keeper Function Flow 1. [CustodySwap](#Custody-Swap) 2. [Borrow](#Borrow) 3. [UpdatePoolHealth](#Update-Pool-Health) 4. [TakeInCustody](#Take-into-Custody) #### Opening a Position A user opens a long position with 1 Frax as collateral using 3x leverage. ```flow st=>start: Take In Collateral e=>end: End op=>operation: Borrow op2=>operation: Update Pool Health op3=>operation: Custody Swap op4=>operation: Take In Custody st->op->op2->op3->op4->end ``` 1. **Take In Collateral** The 1 Frax collateral is taken from the users wallet and moved into the clp module account, and added to the pool balance, as well as accounted for in the MTP.CollateralAmount property. 2. **Borrow** The borrow amount is 2 Frax, which is tracked as a pool liability, and on the MTP.LiabilityP property. 3. **Take In Custody** The collateral and liability amount is swapped to custody token and taken out of the pool. The value is stored in the pool's custody bucket and in MTP.CustodyAmount. ```go func (k msgServer) OpenLong(goCtx context.Context, msg *types.MsgOpenLong) (*types.MsgOpenLongResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) leverageMax := k.GetLeverageMaxParam(ctx) collateralAmount := msg.CollateralAmount mtp := types.NewMTP(ctx, msg.Signer, msg.CollateralAsset, msg.BorrowAsset) // create func TODO pool := clptypes.pool{} nativeAsset := types.GetSettlementAsset() if msg.CollateralAsset.Equals(nativeAsset) { pool = k.clpKeeper.getPool(ctx, msg.BorrowAsset) } else { pool = k.clpKeeper.getPool(ctx, msg.CollateralAsset) } leveragedAmount := CollateralAmount.Mul(sdk.Uint.NewUint(1).Add(leverageMax)) borrowAmount := k.CustodySwap(ctx, pool, msg.BorrowAsset, leveragedAmount) k.Borrow(ctx, msg.CollateralAsset, collateralAmount, msg.BorrowAsset, borrowAmount, mtp, pool, leverageMax) k.UpdatePoolHealth(ctx, pool) k.TakeInCustody(ctx, mtp, pool) } ``` ### Close _mtp_ long ###### tags: `v1.0` Message Server Logic for Close MTP Long Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Close-mtp1 Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/close.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/6_close_mtp.ipynb - Status: Needs to be re-run Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/msg_server_test.go#L215 Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/msg_server.go#L224 #### Close Long Keeper Function Flow 1. [TakeOutCustody](#Take-out-of-Custody) 2. [CustodySwap](#Custody-Swap) 3. [InterestRateComputation](#Interest-Computation) 4. [UpdateMTPInterestLiabilities](#Update-Interest-Liabilities) 5. [Repay](#Repay) #### Voluntary Closure ```flow start=>start: Take Out Custody op1=>operation: Swap op2=>operation: Update Interest end=>end: Repay start->op1->op2->end ``` 1. The mtp custody amount is taken out of the pool’s custody bucket and placed back into the pool. 2. The custody amount is swapped back to the collateral token as a repay amount. 3. Interest is calculated 4. The repay amount is used to repay the position. 5. The user is paid any profits 6. The mtp liability is removed from the pool liability bucket (in the collateral token) 7. The traders profits or loss are removed from the pool balance (in the collateral token) ```go func (k msgServer) CloseLong(goCtx context.Context, msg *types.MsgCloseLong) (*types.MsgCloseLongResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) mtp := k.getMTP(ctx, msg.Signer, msg.CollateralAsset, msg.BorrowAsset) pool := clptypes.pool{} nativeAsset := types.GetSettlementAsset() if msg.CollateralAsset.Equals(nativeAsset) { pool = k.clpKeeper.getPool(ctx, msg.BorrowAsset) } else { pool = k.clpKeeper.getPool(ctx, msg.CollateralAsset) } k.TakeOutCustody(ctx, mtp, pool) repayAmount := k.CustodySwap(ctx, pool, mtp.CollateralAsset, mtp.CustodyAmount) // interest comp interestRate = k.InterestRateComputation(ctx, pool) k.UpdateMTPInterestLiabilities(ctx, mtp, interestRate) repay := mtp.Repay(ctx, mtp, pool, repayAmount) } ``` ### Force Close _mtp_ long ###### tags: `v1.0` Message Server Logic for Force Close MTP Long Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Forced-Settlement-by-Keeper Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/2101d487c9c39cd9137ad3036198d45ebdf3ebe3/model/parts/keeper_maintainence.py#L7 Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/EC7_force_close_address.ipynb - Status: ? Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/msg_server_test.go#L443 Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/msg_server.go#L273 #### Force Close Long Keeper Function Flow 1. [InterestRateComputation](#Interest-Computation) 2. [UpdateMTPInterestLiabilities](#Update-Interest-Liabilities) 3. [UpdateMTPHealth](#Update-MTP-Health) 4. [TakeOutCustody](#Take-out-of-Custody) 5. [CustodySwap](#Custody-Swap) 6. [Repay](#Repay) #### Force Closure ```flow start=>start: Update Interest op1=>operation: Take Out Custody op2=>operation: Swap end=>end: Repay start->op1->op2->end ``` ```go func (k Keeper) ForceCloseLong(ctx sdk.Context, msg *types.MsgForceClose) (*types.MTP, error) { mtp, err := k.GetMTP(ctx, msg.MtpAddress, msg.Id) var pool clptypes.Pool nativeAsset := types.GetSettlementAsset() if strings.EqualFold(mtp.CollateralAsset, nativeAsset) { pool, err = k.ClpKeeper().GetPool(ctx, mtp.CustodyAsset) } else { pool, err = k.ClpKeeper().GetPool(ctx, mtp.CollateralAsset) } // check MTP health against threshold forceCloseThreshold := k.GetForceCloseThreshold(ctx) interestRate, err := k.InterestRateComputation(ctx, pool) err = k.UpdateMTPInterestLiabilities(ctx, &mtp, interestRate) mtpHealth, err := k.UpdateMTPHealth(ctx, mtp, pool) if err != nil { return nil, err } if mtpHealth.GT(forceCloseThreshold) { return nil, sdkerrors.Wrap(types.ErrMTPHealthy, msg.MtpAddress) } err = k.TakeOutCustody(ctx, mtp, &pool) repayAmount, err := k.CustodySwap(ctx, pool, mtp.CollateralAsset, mtp.CustodyAmount) err = k.Repay(ctx, &mtp, pool, repayAmount) return &mtp, nil } ``` ### Open _mtp_ short ###### tags: `WIP` Message Server Logic for Open MTP Short Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Open-Short Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/open_swap_custody_x.py ```go func (k msgServer) OpenShort(goCtx context.Context, msg *types.MsgOpenShort) (*types.MsgOpenShortResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) eta := k.GetEta(ctx) delta_x := msg.delta_x mtp := k.createMTP(ctx, msg.Signer, msg.asset) pool := k.clpKeeper.getPool(ctx, msg.asset) ExternalAssets := pool.X_Balance NativeAssets := pool.Y_Balance tot_x := delta_x.Mul(sdk.Uint.NewUint(1).Add(eta) tot_y := k.CustodySwap(ExternalAssets, NativeAssets, tot_x) k.Borrow(ctx, mtp, delta_x, eta) # sets xa, xl, and h k.UpdatePoolHealth(ctx, pool) k.TakeInCustody(ctx, mtp, pool, tot_y) ExternalCustody = k.CustodySwap(NativeAssets, ExternalAssets, tot_y) k.ShortCustody(ctx, mtp, pool, ExternalCustody, tot_y) } ``` ### Open _mtp_ long other pool ###### tags: `WIP` Message Server Logic for Open MTP Double Swap Long Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Double-Swap-Long Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/open_double_swap.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/jesse/double_swap_long.ipynb - Status: Merged ```go func (k msgServer) OpenLongDouble(goCtx context.Context, msg *types.MsgOpenLongDouble) (*types.MsgOpenLongDoubleResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) eta := k.GetEta(ctx) delta_x := msg.delta_x mtp := k.createMTP(ctx, msg.Signer, msg.asset_x) // change key to account for both assets? pool_x := k.clpKeeper.getPool(ctx, msg.asset_x) pool_z := k.clpKeeper.getPool(ctx, msg.asset_z) ExternalAssets := pool_x.X_Balance NativeAssets := pool_x.Y_Balance tot_x := delta_x.Mul(sdk.Uint.NewUint(1).Add(eta) tot_y := k.CustodySwap(ExternalAssets, NativeAssets, tot_x) k.Borrow(ctx, mtp, delta_x, eta) # sets xa, xl, and h k.UpdatePoolHealth(ctx, pool_x) k.TakeInCustody(ctx, mtp, pool_x, tot_y) Z_A := pool_z.Z_Balance NativeAssetsPool2 := pool_z.Y_Balance // TODO rename? tot_z := k.CustodySwap(NativeAssetsPool2, Z_A, tot_y) k.TakeInCustodyDouble(ctx, mtp, pool_x, pool_z, tot_y, tot_z) k.UpdatePoolHealthDouble(ctx) // TODO } ``` ### Open _mtp_ short other pool ###### tags: `WIP` Message Server Logic for Open MTP Short Double Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Double-Swap-Short Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/open_double_swap.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/14_double_swap_short_open.ipynb - Status: Merged ```go func (k msgServer) OpenShortDouble(goCtx context.Context, msg *types.MsgOpenShortDouble) (*types.MsgOpenShortDoubleResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) eta := k.GetEta(ctx)) delta_x := msg.delta_x mtp := k.createMTP(ctx, msg.Signer, msg.asset_x) // change key to account for both assets? pool_x := k.clpKeeper.getPool(ctx, msg.asset_x) pool_z := k.clpKeeper.getPool(ctx, msg.asset_z) ExternalAssets := pool_x.X_Balance NativeAssets := pool_x.Y_Balance tot_x := delta_x.Mul(sdk.Uint.NewUint(1).Add(eta) tot_y_x := k.CustodySwap(ExternalAssets, NativeAssets, tot_x) k.Borrow(ctx, mtp, delta_x, eta) # sets xa, xl, and h k.UpdatePoolHealth(ctx, pool_x) k.TakeInCustody(ctx, mtp, pool_x, tot_y) Z_A := pool_z.X_Balance NativeAssetsPool2 := pool_z.Y_Balance // calculate delta_z from tot_y tot_z := k.CustodySwap(NativeAssetsPool2, Z_A, tot_y) tot_y_z := k.CustodySwap(Z_A, NativeAssetsPool2, tot_z) ExternalCustody := k.CustodySwap(NativeAssetsPool2, ExternalAssets, tot_y_z) k.ShortCustodyDouble(ctx, mtp, pool_x, pool_z, tot_y_x, tot_y_z, tot_z, ExternalCustody) k.UpdatePoolHealthDouble(ctx) // TODO } ``` ### Close _mtp_ short ###### tags: `WIP` Message Server Logic for Close MTP Short Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Close-mtp1 Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/close.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/15c_open_close_short_y.ipynb - Status: Merged ```go func (k msgServer) CloseShort(goCtx context.Context, msg *types.MsgCloseShort) (*types.MsgCloseShortResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) mtp := k.getMTP(ctx, msg.Signer, msg.asset) pool := k.clpKeeper.getPool(ctx, msg.asset) ExternalAssets := pool.X_Balance NativeAssets := pool.Y_Balance ExternalLiabilities := pool.ExternalLiabilities NativeLiabilities := pool.NativeLiabilities X := ExternalAssets.Add(ExternalLiabilities) Y := NativeAssets.Add(NativeLiabilities) delta_x := msg.delta_x k.TakeOutCustody(ctx, mtp, pool, delta_x) delta_y := k.CustodySwap(Y, X, delta_x) mtp.Repay(ctx, mtp, pool, delta_y, closure, maintenance) k.UpdateHealth(ctx, mtp) } ``` ### Margin maintenance - Pay off Interest Liabilities ###### tags: `WIP` Message Server Logic for Margin Maintenance - Pay off Interest Liabilities: Reduce Liability Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Margin-Maintenance--Pay-off-Interest-Liabilities Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/margin_maintenance.py ```go func (k msgServer) ReduceLiabilityMaint(goCtx context.Context, msg *types.MsgReduceLiabilityMaint) (*types.MsgReduceLiabilityMaintResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) mtp := k.getMTP(msg.Signer, msg.asset) pool := k.clpKeeper.getPool(ctx, msg.asset) ExternalAssets := pool.X_Balance NativeAssets := pool.Y_Balance ExternalLiabilities := pool.ExternalLiabilities NativeLiabilities := pool.NativeLiabilities repayAmount := msg.repayAmount ExternalLiabilitiesP := mtp.ExternalLiabilitiesP ExternalLiabilitiesI := mtp.ExternalLiabilitiesI // interest liability comp TODO k.MaintenanceRepay(ctx, mtp, pool, repayAmount) k.UpdateMTPHealth(ctx, mtp) } ``` ### Margin maintenance - Pay off Interest Liabilities ###### tags: `WIP` Message Server Logic for Margin Maintenance - Pay off Interest Liabilities: Return Borrowed Tokens Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Margin-Maintenance--Pay-off-Interest-Liabilities Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/margin_maintenance.py ```go func (k msgServer) ReturnBorrowMaint(goCtx context.Context, msg *types.MsgReturnBorrowMaint) (*types.MsgReturnBorrowMaintResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) mtp := k.getMTP(ctx, msg.Signer, msg.asset) pool := k.clpKeeper.getPool(ctx, msg.asset) ExternalAssets := pool.X_Balance NativeAssets := pool.Y_Balance ExternalLiabilities := pool.ExternalLiabilities NativeLiabilities := pool.NativeLiabilities repayAmount := msg.repayAmount ExternalLiabilitiesP := mtp.ExternalLiabilitiesP ExternalLiabilitiesI := mtp.ExternalLiabilitiesI if msg.addCollateral { X := ExternalAssets.Add(ExternalLiabilities) Y := NativeAssets.Add(NativeLiabilities) delta_y := msg.delta_y k.TakeOutCustody(ctx, mtp, pool, delta_y) repayAmount := k.CustodySwap(X, Y, delta_y) // swap close } // interest liability comp TODO k.MaintenanceRepay(ctx, mtp, pool, repayAmount) k.UpdateMTPHealth(ctx, mtp) } ``` ## Keeper Functions Margin Keeper Functions are representations of Primitive Mechanisms as outlined in the [Sifchain Cryptoeconomics Paper](https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Primitive-Mechanisms). ### Borrow The Borrow function is responsible for updating accounting in both MTP and Pool objects for a borrow action. It also handles the transfer of collateral coins from the MTP holder to the pool. ###### tags: `v1.0` Global State Transition for Borrow Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Borrow Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/mtp.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/5_open_mtp.ipynb - Status: Merged Test Spec: https://hackmd.io/b3R1-ms8SECslTFkyhmwOA?view#Borrow Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L231 ```go /* Borrow updates the mtp collateral, and liabilities (based on leverage amount). The pool balance and liabilities are updated, and the funds are moved from mtp address to module. At this point the mtp health is also calculated. */ func (k Keeper) Borrow(ctx sdk.Context, collateralAsset clpTypes.Asset, collateralAmount sdk.Uint, borrowAmount sdk.Uint, mtp MTP, pool clpTypes.Pool, leverage sdk.Uint) error { mtp.CollateralAmount = mtp.CollateralAmount.Add(collateralAmount) mtp.LiabilitiesP = mtp.ExternalLiabilitiesP.Add(collateralAmount.Mul(leverage)) mtp.CustodyAssets = mtp.CustodyAssets.Add(borrowAmount) mtp.Leverage = leverage nativeAsset := clpTypes.GetSettlementAsset() if collateralAsset.Equals(nativeAsset) { pool.NativeAssetBalance = pool.NativeAssetBalance.Add(collateralAmount) pool.NativeLiabilities = poolNativeLiabilities.Add(collateralAmount.Mul(leverage)) } else { pool.ExternalAssetBalance = pool.ExternalAssetBalance.Add(collateralAmount) pool.ExternalLiabilities = pool.ExternalLiabilities.Add(collateralAmount.Mul(leverage)) } mtp.h = k.UpdateMTPHealth(ctx, mtp, pool) // set mtp in func or return h? var coins sdk.Coins collateralCoin := sdk.NewCoin(collateralAsset.Symbol, collateralAmount) collateralcoins := coins.Add(collateralCoin) k.supplyKeeper.SendCoinsFromAccountToModule(ctx, mtp.Address, types.ModuleName, collateralcoins) clpkeeper.SetPool(ctx, pool) mtpkeeper.SetMTP(ctx, mtp) } ``` ### Repay The Repay function is responsible for updating accounting in both MTP and Pool objects for a repay action. Repay uses an input amount of assets to settle accumulated debt in an MTP. The function first pays off principle liabilities, then interest liabilities, and any excess assets are paid back to the MTP holder as profit. For force closures, a percentage of the proceeds can go to a wallet address specified by governance. The percentage is also a governance parameter. ###### tags: `v1.0` Global State Transition for Repay Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Repay Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/mtp.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/6_close_mtp.ipynb Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L358 - Status: Merged Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L663 ```go func (k Keeper) Repay(ctx sdk.Context, mtp MTP, pool clpTypes.Pool, repayAmount sdk.Uint, takeInsurance bool) error { returnAmount, debtP, debtI := sdk.ZeroUint() LiabilitiesP := mtp.LiabilitiesP LiabilitiesI := mtp.LiabilitiesI mtp.h = k.UpdateMTPHealth(ctx, mtp, pool) have := repayAmount owe := LiabilitiesP.Add(LiabilitiesI) if have.LT(LiabilitiesP) { //can't affort principle liability returnAmount = sdk.ZeroUint() debtP = LiabilitiesP - have debtI = LiabilitiesI } else if have.LT(owe) { // v principle liability; x excess liability returnAmount = sdk.ZeroUint() debtP = sdk.ZeroUint() debtI = LiabilitiesP.Add(LiabilitiesI).Sub(have) } else { // can afford both returnAmount = have.Sub(LiabilitiesP).Sub(LiabilitiesI) debtP = sdk.ZeroUint() debtI = sdk.ZeroUint() } if !returnAmount.IsZero() { actualReturnAmount := returnAmount if takeInsurance { takePercentage := k.GetForceCloseFundPercentage(ctx) returnAmountDec := sdk.NewDecFromBigInt(returnAmount.BigInt()) takeAmount := sdk.NewUintFromBigInt(takePercentage.Mul(returnAmountDec).TruncateInt().BigInt()) actualReturnAmount = returnAmount.Sub(takeAmount) if !takeAmount.IsZero() { takeCoins := sdk.NewCoins(sdk.NewCoin(mtp.CollateralAsset, sdk.NewIntFromBigInt(takeAmount.BigInt()))) fundAddr := k.GetInsuranceFundAddress(ctx) err = k.BankKeeper().SendCoinsFromModuleToAccount(ctx, clptypes.ModuleName, fundAddr, takeCoins) if err != nil { return err } k.EmitRepayInsuranceFund(ctx, mtp, takeAmount) } } if !actualReturnAmount.IsZero() { var coins sdk.Coins returnCoin := sdk.NewCoin(collateralAsset.Symbol, returnAmount) returnCoins := coins.Add(returnCoin) k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, mtp.Address, returnCoins) } } nativeAsset := clpTypes.GetSettlementAsset() if mtp.CollateralAsset.Equals(nativeAsset) { pool.NativeAssetBalance = pool.NativeAssetBalance.Sub(debtI).Sub(debtP) pool.NativeLiabilities = pool.NativeLiabilities.Sub(mtp.LiabilitiesP) } else { pool.ExternalAssetBalance = pool.NativeAssetBalance.Sub(debtI).Sub(debtP) pool.ExternalLiabilities = pool.NativeLiabilities.Sub(mtp.LiabilitiesP) } mtpkeeper.DestroyMTP(ctx, mtp) k.clpKeeper.SetPool(ctx, pool) } ``` ### Take into Custody The Take into Custody function is responsible for updating accounting in a Pool object when assets are entered into custody. ###### tags: `v1.0` Global State Transition for Take into Custody Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Take_into_Custody Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/mtp.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/5_open_mtp.ipynb - Status: Merged Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L571 Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L330 ```go func (k Keeper) TakeInCustody(ctx sdk.Context, mtp MTP, pool clpTypes.Pool) error { nativeAsset := clpTypes.GetSettlementAsset() if mtp.CollateralAsset.Equals(nativeAsset) { pool.ExternalAssetBalance = pool.ExternalAssetBalance.Sub(mtp.CustodyAmount) pool.ExternalCustody = pool.ExternalCustody.Add(mtp.CustodyAmount) } else { pool.NativeAssetBalance = pool.NativeAssetBalance.Sub(mtp.CustodyAmount) pool.NativeCustody = pool.NativeCustody.Add(mtp.CustodyAmount) } k.clpKeeper.SetPool(ctx, pool) } ``` ### Take out of Custody The Take out of Custody function is responsible for updating accounting in a Pool object when assets are released from protocol custody. ###### tags: `v1.0` Global State Transition for Take into Custody Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Take_out_of_Custody Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/d28083fe6191c6d88d4f33ce5a4e2e1497d58b0d/model/parts/clp.py#L41 Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/6_close_mtp.ipynb - Status: Merged Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L617 Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L344 ```go func (k Keeper) TakeOutCustody(ctx sdk.Context, mtp MTP, pool clpTypes.Pool) error { nativeAsset := clpTypes.GetSettlementAsset() if mtp.CollateralAsset.Equals(nativeAsset) { pool.ExternalCustody = pool.ExternalCustody.Sub(mtp.CustodyAmount) } else { pool.NativeCustody = pool.NativeCustody.Sub(mtp.CustodyAmount) } k.clpKeeper.SetPool(ctx, pool) } ``` ### Swap Margin utilizes CLP swap functions to calculate custody swap results. ###### tags: `v1.0` Calculation Function for Custody Swap Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Swap_Open_mtp Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/open_swap_custody_x.py Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L172 Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L218 ```go func (k Keeper) CLPSwap(ctx sdk.Context, sentAmount sdk.Uint, to string, pool clptypes.Pool) (sdk.Uint, error) { toAsset := ToAsset(to) // add liabilities? and custody to pool depth pool.NativeAssetBalance = pool.NativeAssetBalance.Add(pool.NativeCustody).Add(pool.NativeLiabilities) pool.ExternalAssetBalance = pool.ExternalAssetBalance.Add(pool.ExternalCustody).Add(pool.ExternalLiabilities) swapResult, err := k.ClpKeeper().CLPCalcSwap(ctx, sentAmount, toAsset, pool) if err != nil { return sdk.Uint{}, err } return swapResult, nil } ``` ### Health and Interest Functions Health and interest functions are called both by [Message Server Functions](#Message-Server) and by [Periodic Health and Interest Updates](#Periodic-Health-and-Interest-Updates) through BeginBlocker. These functions compute updates to health #### Update MTP Health The Update MTP Health function is responsible for computing updates to an individual MTP's health metric. The health metric represents a position's ability to cover its liabilities with its collateral. ###### tags: `v1.0` Global State Transition for MTP Health Calculation Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#mtp_Health_Computation Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/health.py Experiment: EC9 tests 1-3 * Status: Merged * [Test 1](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/EC9_test1.ipynb) * [Test 2](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/EC9_test2.ipynb) * [Test 3](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/EC9_test3.ipynb) Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L440 Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L308 ```go /* MTP health is calculated as a ratio of custody to debt. The custody swap function is used to normalise the values. */ // in this model everything is calculated in y price, you can treat y as // a "inter protocle settlement currency", Sifchain team is kind of a // central bank that controls inflation. // does not account for double position func (k Keeper) UpdateMTPHealth(ctx sdk.Context, mtp MTP, pool clpTypes.Pool) sdk.Uint, error { yc := sdk.NewDecFromBigInt(mtp.CustodyAmount.BigInt()) xlp := mtp.LiabilitiesP if xlp.IsZero() { return sdk.ZeroDec(), nil } debt, err := k.CLPSwap(ctx, xlp, mtp.CustodyAsset, pool) if err != nil { return sdk.ZeroDec(), nil } lr := yc.Quo(sdk.NewDecFromBigInt(debt.BigInt())) return lr, nil } ``` #### Update Pool Health The Update Pool Health function is responsible for computing updates to an individual Pool's health metric. The health metric represents a ratio between the pool's assets and liabilities. ###### tags: `v1.0` Global State Transition for Pool Health Calculation Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Pool_Health_Computation Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/add_colat.py Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L420 EC8 tests 1-3 * Status: Merged * [Test 1]( https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/EC8_test1.ipynb) * [Test 2](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/EC8_test2.ipynb) * [Test 3](https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/EC8_test3.ipynb) Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L288 ```go /* The pool health is calculated as function of the pools (external and native) assets and liabilities. It is a ratio of the balance over the sum of balance and liabilities. */ func (k Keeper) UpdatePoolHealth(ctx sdk.Context, pool clpTypes.Pool) error { // can be both X and Y ExternalAssetBalance := pool.ExternalAssetBalance ExternalLiabilities := pool.ExternalLiabilities NativeAssetBalance := pool.NativeAssetBalance NativeLiabilities := pool.NativeLiabilities mul1 := ExternalAssetBalance.Quo(ExternalAssetBalance.Add(ExternalLiabilities)) mul2 := NativeAssetBalance.Quo(NativeAssetBalance.Add(NativeLiabilities)) H := mul1.Mul(mul2) pool.Health = H k.clpKeeper.SetPool(ctx, pool) } ``` #### Interest Computation The Interest Computation function is responsible for computing updates to an individual Pool's interest rate. The interest rate result is a function of the pool's health and a parameterized gain factor. ###### tags: `v1.0` Calculation Function for Interest Rate Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Target-Borrow-Fee-Domain-Mapping-from-Health-Metrics Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/interest_rate.py Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L501 Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L870 ```go /* When calculating a change to the interest rate, a mechanism is in place to cap the change increase or decrease according to parameters, as well as cap the max or min interest rate. The interest rate is derived from the health gain factor multiplied by a formula that is similar to the inverse of the pool health calculation (assets and liabilities over assets). */ func (k Keeper) InterestRateComputation(ctx sdk.Context, pool clpTypes.Pool) (sdk.Dec, error) { interestRateMax := k.GetInterestRateMax(ctx) interestRateMin := k.GetInterestRateMin(ctx) interestRateIncrease := k.GetInterestRateIncrease(ctx) interestRateDecrease := k.GetInterestRateDecrease(ctx) healthGainFactor := k.GetHealthGainFactor(ctx) prevInterestRate := pool.InterestRate mul1 := pool.ExternalAssetBalance.Add(pool.ExternalLiabilities).Quo(pool.ExternalAssetBalance) mul2 := pool.NativeAssetBalance.Add(pool.NativeLiabilities).Quo(pool.NativeAssetBalance) targetInterestRate := healthGainFactor.Mul(mul1).Mul(mul2) interestRateChange := targetInterestRate.Sub(prevInterestRate) interestRate := prevInterestRate if interestRateChange.LTE(interestRateDecrease.Mul(sdk.NewDec(-1))) && interestRateChange.LTE(interestRateIncrease) { interestRate = targetInterestRate } else if interestRateChange.GT(interestRateIncrease) { interestRate = prevInterestRate.Add(interestRateIncrease) } else if interestRateChange.LT(interestRateDecrease.Mul(sdk.NewDec(-1))) { interestRate = prevInterestRate.Sub(interestRateDecrease) } newInterestRate = interestRate if interestRate.GT(interestRateMin) && interestRate.LT(interestRateMax) { newInterestRate = interestRate } else if interestRate.LTE(interestRateMin) { newInterestRate = interestRateMin } else if interestRate.GTE(interestRateMax) { newInterestRate = interestRateMax } return newInterestRate, nil } ``` #### Update Interest Liabilities The Interest Computation function is responsible for computing updates to an individual MTP's interest liabilities. ###### tags: `v1.0` Global State Transition for MTP Interest Liabilities Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Interest_Liability_Computation_on_span-idMathJax-Element-68-Frame-classmjx-chtml-MathJax_CHTML-tabindex0-stylefont-size-113-position-relative-data-mathmlmtp-rolepresentationmtpmtpmtp Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/mtp.py Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/calculations.go#L13 Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L840 ```go /* When updating interest liabilities for a margin position, the interest rate is applied to the sum of the principle liabilties and interest liabilities. */ func CalcMTPInterestLiabilities(mtp *types.MTP, interestRate sdk.Dec, epochPosition, epochLength int64) sdk.Uint { var interestRational, liabilitiesRational, rate, epochPositionRational, epochLengthRational big.Rat rate.SetFloat64(interestRate.MustFloat64()) liabilitiesRational.SetInt(mtp.Liabilities.BigInt().Add(mtp.Liabilities.BigInt(), mtp.InterestUnpaid.BigInt())) interestRational.Mul(&rate, &liabilitiesRational) if epochPosition > 0 { // prorate interest if within epoch epochPositionRational.SetInt64(epochPosition) epochLengthRational.SetInt64(epochLength) epochPositionRational.Quo(&epochPositionRational, &epochLengthRational) interestRational.Mul(&interestRational, &epochPositionRational) } interestNew := interestRational.Num().Quo(interestRational.Num(), interestRational.Denom()) return sdk.NewUintFromBigInt(interestNew.Add(interestNew, mtp.InterestUnpaid.BigInt())) } ``` #### IncrementalInterestPayment ###### tags: `v1.0` `Keeper Function` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L445 Pay off interestPayment argument using mtp collateral. The interest payment is calculated in the collateral asset. It is then swapped to the custody asset and deducted from the position's custody. The payment is then deducted from the pool's custody and added back to the pool balance, minus any insurance fund percentage. ```go func (k Keeper) IncrementalInterestPayment(ctx sdk.Context, interestPayment sdk.Uint, mtp *types.MTP, pool clptypes.Pool) (sdk.Uint, error) { // if mtp has unpaid interest, add to payment if mtp.InterestUnpaidCollateral.GT(sdk.ZeroUint()) { interestPayment = interestPayment.Add(mtp.InterestUnpaidCollateral) } // swap interest payment to custody asset for payment interestPaymentCustody, err := k.CLPSwap(ctx, interestPayment, mtp.CustodyAsset, pool) if err != nil { return sdk.ZeroUint(), err } // if paying unpaid interest reset to 0 mtp.InterestUnpaidCollateral = sdk.ZeroUint() // edge case, not enough custody to cover payment if interestPaymentCustody.GT(mtp.CustodyAmount) { // swap custody amount to collateral for updating interest unpaid custodyAmountCollateral, err := k.CLPSwap(ctx, mtp.CustodyAmount, mtp.CollateralAsset, pool) // may need spot price here to not deduct fee if err != nil { return sdk.ZeroUint(), err } mtp.InterestUnpaidCollateral = interestPayment.Sub(custodyAmountCollateral) interestPayment = custodyAmountCollateral interestPaymentCustody = mtp.CustodyAmount } // add payment to total paid - collateral mtp.InterestPaidCollateral = mtp.InterestPaidCollateral.Add(interestPayment) // add payment to total paid - custody mtp.InterestPaidCustody = mtp.InterestPaidCustody.Add(interestPaymentCustody) // deduct interest payment from custody amount mtp.CustodyAmount = mtp.CustodyAmount.Sub(interestPaymentCustody) takePercentage := k.GetIncrementalInterestPaymentFundPercentage(ctx) fundAddr := k.GetIncrementalInterestPaymentInsuranceFundAddress(ctx) takeAmount, err := k.TakeInsurance(ctx, interestPaymentCustody, mtp.CustodyAsset, takePercentage, fundAddr) if err != nil { return sdk.ZeroUint(), err } actualInterestPaymentCustody := interestPaymentCustody.Sub(takeAmount) if !takeAmount.IsZero() { k.EmitInsuranceFundPayment(ctx, mtp, takeAmount, mtp.CustodyAsset, types.EventIncrementalPayInsuranceFund) } nativeAsset := types.GetSettlementAsset() if types.StringCompare(mtp.CustodyAsset, nativeAsset) { // custody is native pool.NativeCustody = pool.NativeCustody.Sub(interestPaymentCustody) pool.NativeAssetBalance = pool.NativeAssetBalance.Add(actualInterestPaymentCustody) } else { // custody is external pool.ExternalCustody = pool.ExternalCustody.Sub(interestPaymentCustody) pool.ExternalAssetBalance = pool.ExternalAssetBalance.Add(actualInterestPaymentCustody) } err = k.SetMTP(ctx, mtp) if err != nil { return sdk.ZeroUint(), err } return interestPayment, k.ClpKeeper().SetPool(ctx, &pool) } ``` ### Maintenance Repay ###### tags: `WIP` Global State Transition for Maintenance Repay Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Maintenance_Repay Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/mtp.py Experiment: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/experiments/8_maint_repay.ipynb - Status: Merged Test Spec: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/keeper/keeper_test.go#L663 ```go func (k Keeper) MaintenanceRepay(ctx sdk.Context, mtp MTP, pool clpTypes.Pool, repayAmount sdk.Uint) error { MTPExternalAssets := mtp.ExternalAssets ExternalLiabilitiesP := mtp.ExternalLiabilitiesP ExternalLiabilitiesI := mtp.ExternalLiabilitiesI //get from interest calc TODO if repayAmount.LTE(ExternalLiabilitiesI) { // add ExternalLiabilitiesI like above ExternalLiabilitiesI = ExternalLiabilitiesI.Sub(repayAmount) } // If owner pays off more than their excess liab, then principal liabilities next else if repayAmount.LTE(ExternalLiabilitiesP.Add(ExternalLiabilitiesI) { diff := repayAmount.Sub(ExternalLiabilitiesI) ExternalLiabilitiesI = 0 ExternalLiabilitiesP = ExternalLiabilitiesP.Sub(diff) } else { diff := repayAmount.Sub(ExternalLiabilitiesI.Sub(ExternalLiabilitiesP)) ExternalLiabilitiesI = 0 ExternalLiabilitiesP = 0 MTPExternalAssets = MTPExternalAssets.Add(diff) } mtp.ExternalLiabilitiesP = ExternalLiabilitiesP mtp.ExternalLiabilitiesI = ExternalLiabilitiesI mtp.ExternalAssets = MTPExternalAssets k.UpdateMTPHealth(ctx, mtp) k.SetMTP(ctx, mtp) pool.X_Balance = pool.X_Balance.Add(repayAmount) pool.ExternalLiabilities = pool.ExternalLiabilities.Sub(repayAmount) if repayAmount.LTE(ExternalLiabilitiesI) { pool.ExternalAssets = pool.ExternalAssets.Add(repayAmount) } else if repayAmount.LET(ExternalLiabilitiesI.Sub(ExternalLiabilitiesP) { pool.ExternalAssets = pool.ExternalAssets.Add(repayAmount) pool.ExternalLiabilities = pool.ExternalLiabilities.Sub(repayAmount) } k.clpKeeper.SetPool(ctx, pool) } ``` ### Short Custody ###### tags: `WIP` Global State Transition for Short Custody Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Custody-Swap-Mechanism-to-Short-in-X-Y ```go func (k Keeper) ShortCustody(ctx sdk.Context, mtp MTP, pool clpTypes.Pool, ExternalCustody sdk.Uint, tot_y sdk.Uint) error { pool.Y_Balance = pool.Y_Balance.Add(tot_y) pool.NativeLiabilities = pool.NativeLiabilities.Sub(tot_y) pool.NativeCustody = pool.NativeCustody.Sub(tot_y) pool.X_Balance -= pool.X_Balance.Sub(ExternalCustody) pool.ExternalLiabilities = pool.ExternalLiabilities.Add(ExternalCustody) pool.ExternalCustody = pool.ExternalCustody.Add(ExternalCustody) mtp.NativeAssets = 0 mtp.ExternalAssets = ExternalCustody k.clpKeeper.SetPool(ctx, pool) k.SetMTP(ctx, mtp) } ``` ### Take into Custody Double ###### tags: `WIP` Global State Transition for Take into Custody Double Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Custody-Swap-Mechanism-in-Y-Z Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/mtp.py#L92 ```go func (k Keeper) TakeInCustodyDouble(ctx sdk.Context, mtp MTP, pool_x sdk.Uint, pool_z sdk.Uint, tot_y sdk.Uint, tot_z sdk.Uint) error { pool_x.NativeCustody = pool_x.NativeCustody.Sub(tot_y) pool_z.Y_Balance = pool_z.Y_Balance.Add(tot_y) pool_z.X_Balance = pool_z.X_Balance.Sub(tot_z) pool_z.ExternalCustody = pool_z.ExternalCustody.Add(tot_z) mtp.NativeAssets = mtp.NativeAssets.Sub(tot_y) mtp.Z_A = mtp.Z_A.Add(tot_z) k.clpKeeper.SetPool(ctx, pool_x) k.clpKeeper.SetPool(ctx, pool_z) k.SetMTP(mtp) } ``` ### Short Custody Double ###### tags: `WIP` Global State Transition for Short Custody Double Mechanism Spec: https://hackmd.io/YJTLw7vWSLSDBZoDQDkOKw?view#Double-Custody-Swap-Mechanism-to-Short Reference Python cadCAD: https://github.com/Sifchain/margin_trading_cadCAD/blob/main/model/parts/mtp.py#L92 ```go func (k Keeper) ShortCustodyDouble(ctx sdk.Context, mtp MTP, pool_x sdk.Uint, pool_z sdk.Uint, tot_y_x sdk.Uint, tot_y_z sdk.Uint, tot_z sdk.Uint, ExternalCustody sdk.Uint) error { pool_x.X_Balance = pool_x.X_Balance.Sub(ExternalCustody) pool_x.ExternalCustody = pool_x.ExternalCustody.Add(ExternalCustody) pool_x.Y_Balance = pool_x.Y_Balance.Add(tot_y_z.Sub(tot_y_x)) pool_z.Y_Balance = pool_z.Y_Balance.Sub(tot_y_z) pool_z.X_Balance = pool_z.X_Balance.Sub(tot_z) mtp.NativeAssets = 0 mtp.Z_A = 0 mtp.ExternalCustody = ExternalCustody k.clpKeeper.SetPool(ctx, pool_x) k.clpKeeper.SetPool(ctx, pool_z) k.SetMTP(ctx, mtp) } ``` ## Periodic Health and Interest Updates The Margin module runs periodic computations to update health metrics for each active MTP and Pool as well as interest rates for each Pool. During these updates the module also checks for MTPs with health metric values under the current Force Close Threshold and Force Closes unhealthy positions. ### BeginBlocker ###### tags: `v1.0` `Epoch Action Space` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/abci.go#L14 Update health for MTPs and Pools, and recalculate interest rate every epoch. ```flow start=>start: Foreach Pool updatePool=>operation: Update Pool Health + Interest Rate foreachMTP=>operation: Foreach MTP updateMTP=>operation: Update MTP Health + Interest Liability maybeClose=>operation: Maybe Force Close MTP maybeCloseEnd=>condition: More MTPs end=>end: End start->updatePool->foreachMTP->updateMTP->maybeClose->maybeCloseEnd maybeCloseEnd(yes)->foreachMTP maybeCloseEnd(no)->end ``` ```go func (k Keeper) BeginBlocker(ctx sdk.Context) { //check if epoch has passed then execute currentHeight := ctx.BlockHeight() epochLength := k.GetEpochLength(ctx) if currentHeight%epochLength == 0 { // if epoch has passed pools := k.ClpKeeper().GetPools(ctx) for _, pool := range pools { if k.IsPoolEnabled(ctx, pool.ExternalAsset.Symbol) { rate, err := k.InterestRateComputation(ctx, *pool) if err != nil { ctx.Logger().Error(err.Error()) continue // ? } pool.InterestRate = rate _ = k.UpdatePoolHealth(ctx, pool) k.TrackSQBeginBlock(ctx, pool) _ = k.clpKeeper.SetPool(ctx, pool) mtps, _, _ := k.GetMTPsForPool(ctx, pool.ExternalAsset.Symbol) for _, mtp := range mtps { BeginBlockerProcessMTP(ctx, k, mtp, pool) } } } } } func BeginBlockerProcessMTP(ctx sdk.Context, k Keeper, mtp *types.MTP, pool *clptypes.Pool) { defer func() { if r := recover(); r != nil { if msg, ok := r.(string); ok { ctx.Logger().Error(msg) } } }() h, err := k.UpdateMTPHealth(ctx, *mtp, *pool) if err != nil { return } mtp.MtpHealth = h interestPayment = k.UpdateMTPInterestLiabilities(ctx, mtp, pool.InterestRate) _ = k.IncrementalInterestPayment(ctx, interestPayment, mtp, pool) _ = k.SetMTP(ctx, mtp) _, err = k.ForceCloseLong(ctx, &types.MsgForceClose{Id: mtp.Id, MtpAddress: mtp.Address}) if err == nil { // Emit event if position was closed k.EmitForceClose(ctx, mtp, "") } } ``` ## Keepers ###### tags: `v1.0` `Permissible Control Action Determination Prior` Calls state update functions, simplisticly get and set, but more complex in implementation Does a check on restriction on sender of message, for example, so incorporates Handler functionality ```go type Keeper struct { storeKey sdk.StoreKey cdc codec.BinaryCodec bankKeeper types.BankKeeper clpKeeper types.CLPKeeper adminKeeper adminkeeper.Keeper paramStore paramtypes.Subspace } ``` ### Set MTP ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L60 ```go func (k Keeper) SetMTP(ctx sdk.Context, mtp *types.MTP) error { store := ctx.KVStore(k.storeKey) count := k.GetMTPCount(ctx) if mtp.Id == 0 { count++ mtp.Id = count store.Set(types.MTPCountPrefix, types.GetIDBytes(count)) } if err := mtp.Validate(); err != nil { return err } key := types.GetMTPKey(mtp.Address, mtp.Id) store.Set(key, k.cdc.MustMarshal(mtp)) return nil } ``` ### Get MTP Count ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L49 ```go func (k Keeper) GetMTPCount(ctx sdk.Context) uint64 { var count uint64 countBz := ctx.KVStore(k.storeKey).Get(types.MTPCountPrefix) if countBz == nil { count = 0 } else { count = types.GetIDFromBytes(countBz) } return count } ``` ### Get MTP Open Count ###### tags: `v1.0` ```go= func (k Keeper) GetOpenMTPCount(ctx sdk.Context) uint64 { var count uint64 countBz := ctx.KVStore(k.storeKey).Get(types.OpenMTPCountPrefix) if countBz == nil { count = 0 } else { count = types.GetUint64FromBytes(countBz) } return count } ``` ### Get MTP ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L78 ```go func (k Keeper) GetMTP(ctx sdk.Context, mtpAddress string, id uint64) (types.MTP, error) { var mtp types.MTP key := types.GetMTPKey(mtpAddress, id) store := ctx.KVStore(k.storeKey) if !store.Has(key) { return mtp, types.ErrMTPDoesNotExist } bz := store.Get(key) k.cdc.MustUnmarshal(bz, &mtp) return mtp, nil } ``` ### Get MTPs ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L101 ```go func (k Keeper) GetMTPs(ctx sdk.Context, pagination *query.PageRequest) ([]*types.MTP, *query.PageResponse, error) { var mtpList []*types.MTP store := ctx.KVStore(k.storeKey) mtpStore := prefix.NewStore(store, types.MTPPrefix) if pagination == nil { pagination = &query.PageRequest{ Limit: math.MaxUint64 - 1, } } pageRes, err := query.Paginate(mtpStore, pagination, func(key []byte, value []byte) error { var mtp types.MTP k.cdc.MustUnmarshal(value, &mtp) mtpList = append(mtpList, &mtp) return nil }) return mtpList, pageRes, err } ``` ### Get MTP Iterator ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L90 ```go func (k Keeper) GetMTPIterator(ctx sdk.Context) sdk.Iterator { store := ctx.KVStore(k.storeKey) return sdk.KVStorePrefixIterator(store, types.MTPPrefix) } ``` ### Get MTPs For Address ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L160 ```go func (k Keeper) GetMTPsForAddress(ctx sdk.Context, mtpAddress sdk.Address, pagination *query.PageRequest) ([]*types.MTP, *query.PageResponse, error) { var mtps []*types.MTP store := ctx.KVStore(k.storeKey) mtpStore := prefix.NewStore(store, types.GetMTPPrefixForAddress(mtpAddress.String())) if pagination == nil { pagination = &query.PageRequest{ Limit: MaxPageLimit, } } if pagination.Limit > MaxPageLimit { return nil, nil, status.Error(codes.InvalidArgument, fmt.Sprintf("page size greater than max %d", MaxPageLimit)) } pageRes, err := query.Paginate(mtpStore, pagination, func(key []byte, value []byte) error { var mtp types.MTP k.cdc.MustUnmarshal(value, &mtp) mtps = append(mtps, &mtp) return nil }) if err != nil { return nil, nil, err } return mtps, pageRes, nil } ``` ### Get MTPs for Pool Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L139 ```go= func (k Keeper) GetMTPsForPool(ctx sdk.Context, asset string, pagination *query.PageRequest) ([]*types.MTP, *query.PageResponse, error) { var mtps []*types.MTP store := ctx.KVStore(k.storeKey) mtpStore := prefix.NewStore(store, types.MTPPrefix) if pagination == nil { pagination = &query.PageRequest{ Limit: math.MaxUint64 - 1, } } pageRes, err := query.FilteredPaginate(mtpStore, pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { var mtp types.MTP k.cdc.MustUnmarshal(value, &mtp) if accumulate && (strings.EqualFold(mtp.CustodyAsset, asset) || strings.EqualFold(mtp.CollateralAsset, asset)) { mtps = append(mtps, &mtp) return true, nil } return false, nil }) return mtps, pageRes, err } ``` ### Destroy MTP ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/keeper.go#L168 ```go func (k Keeper) DestroyMTP(ctx sdk.Context, mtpAddress string, id uint64) error { key := types.GetMTPKey(mtpAddress, id) store := ctx.KVStore(k.storeKey) if !store.Has(key) { return types.ErrMTPDoesNotExist } store.Delete(key) return nil } ``` ### GetSQBeginBlock ```go= func (k Keeper) GetSQBeginBlock(ctx sdk.Context, pool *clptypes.Pool) uint64 { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetSQBeginBlockKey(pool)) if bz == nil { return 0 } return types.GetUint64FromBytes(bz) } ``` ### SetSQBeginBlock ```go func (k Keeper) SetSQBeginBlock(ctx sdk.Context, pool *clptypes.Pool, height uint64) { store := ctx.KVStore(k.storeKey) store.Set(types.GetSQBeginBlockKey(pool), types.GetUint64Bytes(height)) } ``` ### TrackSQBeginBlock ```go func (k Keeper) TrackSQBeginBlock(ctx sdk.Context, pool *clptypes.Pool) { threshold := k.GetRemovalQueueThreshold(ctx) sqBeginBlock := k.GetSQBeginBlock(ctx, pool) if sqBeginBlock == 0 { if pool.Health.LTE(threshold) { k.SetSQBeginBlock(ctx, pool, uint64(ctx.BlockHeight())) k.EmitBelowRemovalThreshold(ctx, pool) } } else if pool.Health.GT(threshold) { k.SetSQBeginBlock(ctx, pool, 0) k.EmitAboveRemovalThreshold(ctx, pool) } } ``` ### GetSQFromBlocks ```go func (k Keeper) GetSQFromBlocks(ctx sdk.Context, pool clptypes.Pool, poolInterestRate sdk.Dec) sdk.Dec { beginBlock := k.GetSQBeginBlock(ctx, &pool) if beginBlock == 0 { return sdk.ZeroDec() } blocks := ctx.BlockHeight() - int64(beginBlock) maxInterestRate := k.GetInterestRateMax(ctx) poolInterestRateFloat, _ := poolInterestRate.Float64() minus := math.Pow(math.E, -1*poolInterestRateFloat*float64(blocks)) minusDec, err := sdk.NewDecFromStr(fmt.Sprintf("%v", minus)) if err != nil { minusDec = sdk.NewDec(0) } multipliedBy := sdk.NewDec(1).Sub(minusDec) return maxInterestRate.Mul(multipliedBy) } ``` ### Whitelist functions ```go= func (k Keeper) IsWhitelisted(ctx sdk.Context, address string) bool { store := ctx.KVStore(k.storeKey) return store.Has(types.GetWhitelistKey(address)) } func (k Keeper) WhitelistAddress(ctx sdk.Context, address string) { store := ctx.KVStore(k.storeKey) store.Set(types.GetWhitelistKey(address), []byte(address)) } func (k Keeper) DewhitelistAddress(ctx sdk.Context, address string) { store := ctx.KVStore(k.storeKey) store.Delete(types.GetWhitelistKey(address)) } func (k Keeper) GetWhitelist(ctx sdk.Context, pagination *query.PageRequest) ([]string, *query.PageResponse, error) { var list []string store := ctx.KVStore(k.storeKey) prefixStore := prefix.NewStore(store, types.WhitelistPrefix) if pagination == nil { pagination = &query.PageRequest{ Limit: math.MaxUint64 - 1, } } pageRes, err := query.Paginate(prefixStore, pagination, func(key []byte, value []byte) error { list = append(list, string(value)) return nil }) return list, pageRes, err } ``` ### IsPoolClosed ```go= func (k Keeper) IsPoolClosed(ctx sdk.Context, asset string) bool { params := k.GetParams(ctx) for _, p := range params.ClosedPools { if types.StringCompare(p, asset) { return true } } return false } ``` ## Query `Available Prescribed State Computation` The end-user would want to get information on the liquidity pool and the margin trading system. ### Get MTP ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/grpc_query.go#L23 ```go func (srv queryServer) GetMTP(ctx context.Context, request *types.MTPRequest) (*types.MTPResponse, error) { mtp, err := srv.keeper.GetMTP(sdk.UnwrapSDKContext(ctx), request.Address, request.Id) if err != nil { return nil, err } return &types.MTPResponse{Mtp: &mtp}, nil } ``` ### Get Positions For Address ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/grpc_query.go#L32 ```go func (srv queryServer) GetPositionsForAddress(ctx context.Context, request *types.PositionsForAddressRequest) (*types.PositionsForAddressResponse, error) { addr, err := sdk.AccAddressFromBech32(request.Address) if err != nil { return nil, err } mtps, pageRes, err := srv.keeper.GetMTPsForAddress(sdk.UnwrapSDKContext(goCtx), addr, request.Pagination) if err != nil { return nil, err } return &types.PositionsForAddressResponse{Mtps: mtps, Pagination: pageRes}, nil } ``` ### Get Positions For Pool ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/grpc_query.go#L55 ```go= func (srv queryServer) GetPositionsByPool(ctx context.Context, request *types.PositionsByPoolRequest) (*types.PositionsByPoolResponse, error) { mtps, pageRes, err := srv.keeper.GetMTPsForPool(sdk.UnwrapSDKContext(ctx), request.Asset, request.Pagination) if err != nil { return nil, err } return &types.PositionsByPoolResponse{ Mtps: mtps, Pagination: pageRes, }, nil } ``` ### Get Positions ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/grpc_query.go#L55 ```go= func (srv queryServer) GetPositions(ctx context.Context, request *types.PositionsRequest) (*types.PositionsResponse, error) { if request.Pagination.Limit > MaxPageLimit { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("page size greater than max %d", MaxPageLimit)) } mtps, page, err := srv.keeper.GetMTPs(sdk.UnwrapSDKContext(ctx), request.Pagination) if err != nil { return nil, err } return &types.PositionsResponse{ Mtps: mtps, Pagination: page, }, nil } ``` ### Get Status ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/grpc_query.go#L83 ```go= func (srv queryServer) GetStatus(ctx context.Context, request *types.StatusRequest) (*types.StatusResponse, error) { return &types.StatusResponse{ OpenMtpCount: srv.keeper.GetOpenMTPCount(sdk.UnwrapSDKContext(ctx)), LifetimeMtpCount: srv.keeper.GetMTPCount(sdk.UnwrapSDKContext(ctx)), }, nil } ``` ### Get Params ###### tags: `v1.0` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/keeper/grpc_query.go#L43 ```go func (srv queryServer) GetParams(ctx context.Context, request *types.ParamsRequest) (*types.ParamsResponse, error) { params := srv.keeper.GetParams(sdk.UnwrapSDKContext(ctx)) return &types.ParamsResponse{Params: &params}, nil } ``` ### Whitelist ```go= func (srv queryServer) GetWhitelist(ctx context.Context, request *types.WhitelistRequest) (*types.WhitelistResponse, error) { if request.Pagination.Limit > MaxPageLimit { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("page size greater than max %d", MaxPageLimit)) } whitelist, page, err := srv.keeper.GetWhitelist(sdk.UnwrapSDKContext(ctx), request.Pagination) if err != nil { return nil, err } return &types.WhitelistResponse{ Whitelist: whitelist, Pagination: page, }, nil } ``` ## Querier `Call on a Query` Calls on a Query that an end-user could make are referenced here. ## Rest API The legacy rest endpoints are documentated below. There is also a rest gateway autogenerated from the gRPC spec. ### Transactions Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/master/x/margin/client/rest/tx.go#L19 ```go= func registerTxRoutes(cliCtx client.Context, r *mux.Router) { r.HandleFunc( "/margin/open", openHandler(cliCtx), ).Methods("POST") r.HandleFunc( "/margin/close", closeHandler(cliCtx), ).Methods("POST") r.HandleFunc( "/margin/forceClose", forceCloseHandler(cliCtx), ).Methods("POST") } type ( OpenReq struct { BaseReq rest.BaseReq `json:"base_req"` Signer string `json:"signer"` // User who is trying to open margin position CollateralAsset clptypes.Asset `json:"collateral_asset"` // CollateralAsset for margin position CollateralAmount sdk.Uint `json:"collateral_amount"` // CollateralAmount is the amount of collateral being added BorrowAsset clptypes.Asset `json:"borrow_asset"` // BorrowAsset is asset being borrowed in margin position Position types.Position `json:"position"` // Position type for margin position } CloseReq struct { BaseReq rest.BaseReq `json:"base_req"` Signer string `json:"signer"` // User who is trying to close margin position // nolint:golint Id uint64 `json:"id"` // Id of the mtp } ForceCloseReq struct { BaseReq rest.BaseReq `json:"base_req"` Signer string `json:"signer"` // User who is trying to close margin position MtpAddress string `json:"mtp_address"` // MtpAddress for position to force close // nolint:golint Id uint64 `json:"id"` // Id of the mtp } ) ``` ### Queries Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/margin/client/rest/query.go#L19 ```go= func registerQueryRoutes(cliCtx client.Context, r *mux.Router) { r.HandleFunc("/margin/mtps-by-address", getMTPsForAddress(cliCtx)) r.HandleFunc("/margin/params", getParams(cliCtx)) } func getMTPsForAddress(cliCtx client.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { return } address, err := sdk.AccAddressFromBech32(r.URL.Query().Get("address")) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } var limit, offset uint64 if r.URL.Query().Get("limit") != "" { limit, err = strconv.ParseUint(r.URL.Query().Get("limit"), 10, 64) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } } if r.URL.Query().Get("offset") != "" { offset, err = strconv.ParseUint(r.URL.Query().Get("offset"), 10, 64) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } } params := types.PositionsForAddressRequest{ Address: address.String(), Pagination: &query.PageRequest{ Key: []byte(r.URL.Query().Get("key")), Offset: offset, Limit: limit, CountTotal: false, Reverse: false, }, } bz, err := cliCtx.LegacyAmino.MarshalJSON(params) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryMTPsForAddress) res, height, err := cliCtx.QueryWithData(route, bz) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } cliCtx = cliCtx.WithHeight(height) rest.PostProcessResponse(w, cliCtx, res) } } func getParams(cliCtx client.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { return } route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParams) res, height, err := cliCtx.Query(route) if err != nil { rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } cliCtx = cliCtx.WithHeight(height) rest.PostProcessResponse(w, cliCtx, res) } } ``` ## Hooks `Module Interface` `Define outputs to CLP module` `Changes to CLP module` ## CLP Message Server ### Remove Liquidity Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/clp/keeper/msg_server.go#L802 ```go func (srv msgServer) RemoveLiquidity(ctx context.Context, msg *types.MsgRemoveLiquidity) { // Calculate remove amounts as normal except without mutating pool on swap. withdrawNativeAssetAmount, withdrawExternalAssetAmount, lpUnitsLeft, swapAmount := CalculateWithdrawal(pool.PoolUnits, pool.NativeAssetBalance.String(), pool.ExternalAssetBalance.String(), lp.LiquidityProviderUnits.String(), msg.WBasisPoints.String(), msg.Asymmetry) // Use the unbond requests before placing on queue or removing immediately. err = k.Keeper.UseUnlockedLiquidity(ctx, lp, lp.LiquidityProviderUnits.Sub(lpUnitsLeft), false) if err != nil { return nil, err } // ensure requested removal amount is less than available - what is already on the queue lpQueuedUnits := srv.Keeper.GetRemovalQueueUnitsForLP(ctx, lp) if msg.Units > lp.Units - lpQueuedUnits { return nil, types.ErrInsufficientLiquidity } rowanValue = CalcWithdrawalRowanValue(pool, lp, msg...) newPool := pool newPool.NativeAssetBalance: pool.NativeAssetBalance - withdrawNativeAssetAmount newPool.ExternalAssetBalance: pool.ExternalAssetBalance - withdrawExternalAssetAmount if (marginKeeper.CalculatePoolHealth(newPool) < marginKeeper.GetParams().RemovalThreshold) { srv.Keeper.QueueRemoval(ctx, msg, rowanValue) return nil, types.ErrRemovalQueued } // continue as normal .... } ``` Sifchain Implementation: https://github.com/Sifchain/sifnode/blob/feature/margin-1/x/clp/keeper/msg_server.go#L717 ```go func (srv msgServer) RemoveLiquidityUnits(ctx context.Context, msg *types.MsgRemoveLiquidity) { // Calculate remove amounts as normal except without mutating pool on swap. withdrawNativeAssetAmount, withdrawExternalAssetAmount, lpUnitsLeft := CalculateWithdrawalFromUnits(pool.PoolUnits, pool.NativeAssetBalance.String(), pool.ExternalAssetBalance.String(), lp.LiquidityProviderUnits.String(), msg.WithdrawUnits) // .... // Use the unbond requests before both placing on queue and removing immediately. err = k.Keeper.UseUnlockedLiquidity(ctx, lp, lp.LiquidityProviderUnits.Sub(lpUnitsLeft), false) if err != nil { return nil, err } // ensure requested removal amount is less than available - what is already on the queue lpQueuedUnits := srv.Keeper.GetRemovalQueueUnitsForLP(ctx, lp) if msg.Units > lp.Units - lpQueuedUnits { return nil, types.ErrInsufficientLiquidity } rowanValue = CalcWithdrawalRowanValue(pool, lp, msg...) newPool := pool newPool.NativeAssetBalance: pool.NativeAssetBalance - removeNativeAssetAmount newPool.ExternalAssetBalance: pool.ExternalAssetBalance - removeExternalAssetAmount if (marginKeeper.CalculatePoolHealth(newPool) < marginKeeper.GetParams().RemovalThreshold) { srv.Keeper.QueueRemoval(ctx, types.MsgRemoveLiquidity{ Signer: msg.Signer, ExternalAsset: msg.ExternalAsset, WBasisPoints: ConvUnitsToWBasisPoints(lp.Units, msg.Units), Asymmetry: 0, }, rowanValue) return nil, types.ErrRemovalQueued } // continue as normal .... } ``` ### Add Liquidity ```go= func (srv msgServer) AddLiquidity(ctx context.Context, msg *types.AddLiquidityRequest) { // Add liquidity as normal then process queue newPoolUnits, lpUnits, err := CalculatePoolUnits(...) if (srv.Keeper.RemovalQueueLen() > 0) { srv.Keeper.ProcessRemovalQueue(ctx, msg, newPoolUnits) } } ``` ## CLP Keeper ### ProcessRemovalQueue ###### tags: `v1.0` ```go= func (k Keeper) ProcessRemovalQueue(ctx sdk.Context, msg *types.AddLiquidityRequest, unitsToDistribute sdk.Uint) { perRequestUnits := unitsToDistribute / k.RemovalQueueLen() for request := range k.GetRemovalQueue() { if (request.Msg.ExternalAsset.Equals(msg.ExternalAsset)) { pool := k.GetPool(ctx, msg.ExternalAsset.Symbol) requestUnits := ConvWBasisToUnits(lp.Units, request.WBasis) withdrawUnits := min(requestUnits, perRequestUnits) withdrawWBasisPoints := ConvUnitsToWBasisPoints(lp.Units, withdrawUnits) // Reuse removal logic using withdrawWBasisPoints k.ProcessRemoveLiqiduityMsg(ctx, types.MsgRemoveLiquidity{ ... WBasisPoints: withdrawWBasisPoints }) rowanValue = CalcWithdrawalRowanValue(lp.Units, withdrawWBasisPoints, ...) // Update the queued request k.SetProcessedRemovalRequest(ctx, request, withdrawWBasisPoints, rowanValue) } } } ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully