Saturation
Authors: imi@1m1.io, Will duelingGalois@protonmail.com
Saturation, or sat, is defined as the net borrow. In theory, we would want to divide net borrow by the total liquidity; in practice, we keep the net borrow only in the tree. The unit of sat is relative to active liquidity assets, or the amount of L deposited less the amount borrowed. When we determine how much a swap moves the price, or square root price, we can define our equation using ticks, or tranches (25 ticks), where for some base , the square root price is for some tick . Alternatively for a larger base we can define the square root price as for some tranche . Using the square root price, we can define the amount of x or y in each tranche as:
where liquidity is . If we want to know how much debt of x or y can be liquidated within one tick, we can solve these equations for L and then the amount of x and y are considered the debt we would like to see if it could be liquidated in one tick. If saturation with respect to our starting is smaller, that amount of debt can be liquidated in one swap in the given ticks. Otherwise it is too big and can not. Note that we assume and . Then our definition of saturation relative to L is as follows,
Saturation is kept in a tree, starting with a root, levels and leafs. We keep 2 trees, one for net X borrows, another for net Y borrows. The price is always the price of Y in units of X. Mostly, the code works with the sqrt of price. A net X borrow refers to a position that if liquidated would cause the price to become smaller; the opposite for net Y positions. Ticks are along the price dimension and int16. Tranches are 25 ticks, stored as int16. Leafs (uint16) split the sat, which is uint112, into intervals. From left to right, the leafs of the tree cover the sat space in increasing order. Each account with a position has a price at which its LTV would reach LTVMAX, which is its liquidation (=liq) price. To place a debt into the appropriate tranche, we think of each debt and its respective collateral as a series of sums, where each item in the series fits in one tranche. Using formulas above, we determine the number of ticks a debt would cross if liquidated. This is considered the span of the liquidation. Using this value we then determine the start and end points of the liquidation, where the start would be closer to the prices, on the right of the end for net debt of x and on the left of the end for net debt of Y. Once we have the liquidation start, end, and span, we begin to place the debt, one tranche at a time moving towards the price. In this process we compare the prior recorded saturation and allow the insertion up to some max, set at 90% or the configuration set by the user. A Tranche contains multiple accounts and thus a total sat. The tranche's sat assigns it to a leaf. Each leaf can contain multiple tranches and thus has a total actual sat whilst representing a specific sat per tranche range. Leafs and thus tranches and thus accounts above a certain sat threshold are considered over saturated. These accounts are penalized for being in an over saturated tranche. Each account, tranche and leaf has a total penalty that needs to be repaid to close the position fully. Sat is distributed over multiple tranches, in case a single tranche does not have enough available sat left. Sat is kept cumulatively in the tree, meaning a node contains the sum of the sat of its parents. Updating a sat at the bottom of the tree requires updating all parents. Penalty is kept as a path sum, in uints of LAssets, meaning the penalty of an account is the sum of the penalties of all its parents. Updating the penalty for a range of leafs only requires updating the appropriate parent. Position (=pos) refers to the relative index of a child within its parent. Index refers to the index of a node in within its level The formula for allocating saturation is derived from,
for the start and end of liquidation and respectively. When we consider our buckets of TICKS_PER_TRANCHE we can rewrite this as a series where each boundary of each tranche where for a net debt of X and for a net debt of Y and for each subsequent tranche and . Thus we can rewrite the equations as:
We then define the left side of this equation as total saturation or newSaturation as we call it in the parameter passed in. Saturation is relative to the saturation in one tranche. The right side of the equation defines the saturation in each tranche , starting at the furthest point from the tranche and moving forward.
When calculating the case for Y, the result is almost identical, except our definition for requires us to multiply by rather than divide. The above shows the logic applied in this function. We can allocate saturation across each tranche until the total remaining saturation is depleted. We allow less than the ideal saturation to be consumed if there is less available. Extra saturation is then carried forward to tranches closer to the price, requiring part of the position to be liquidated sooner as needed based on the available liquidity. Two critical nuances of this algorithm is that we reduce by a factor of after each iteration and we multiply one time by after we allocate one time. The reduction of each iteration reflects the increase in the size of each tranche relative to a unit of X or Y as you move from one tranche to the next towards the price. The one time multiplication of is an adjustment for the offset of the start of liquidation relative to the start of the second tranche to minimize the impact of the reduction by since the first portion of saturation does not use an entire tranche. If saturation reaches the minOrMaxTick, we revert as the position is already reaching the limit of our probable price range and may require immediate liquidation if opened.
State Variables
SATURATION_TIME_BUFFER_IN_MAG2
time budget added to sat before adding it to the tree; compensates for the fact that the liq price moves closer to the current price over time.
uint256 internal constant SATURATION_TIME_BUFFER_IN_MAG2 = 101;
MAX_SATURATION_RATIO_IN_MAG2
percentage of max sat per tranche considered healthy; max sat per tranche is with B the tranche basis, which is the max sat such that the liquidation would not cause a swap larger than a tranche
uint256 internal constant MAX_SATURATION_RATIO_IN_MAG2 = 95;
START_SATURATION_PENALTY_RATIO_IN_MAG2
percentage of max sat per tranche where penalization begins
uint256 internal constant START_SATURATION_PENALTY_RATIO_IN_MAG2 = 85;
MAX_INITIAL_SATURATION_MAG2
maximum initial saturation percentage when adding a new position
uint256 internal constant MAX_INITIAL_SATURATION_MAG2 = 90;
EXPECTED_SATURATION_LTV_MAG2
The amount of LTV we expect liquidations to occur at
uint256 internal constant EXPECTED_SATURATION_LTV_MAG2 = 85;
EXPECTED_SATURATION_LTV_MAG2_TIMES_SAT_BUFFER_SQUARED
, a constant used in calculations.
uint256 internal constant EXPECTED_SATURATION_LTV_MAG2_TIMES_SAT_BUFFER_SQUARED = 867_085;
EXPECTED_SATURATION_LTV_PLUS_ONE_MAG2
, a constant used in calculations.
uint256 internal constant EXPECTED_SATURATION_LTV_PLUS_ONE_MAG2 = 185;
SAT_CHANGE_OF_BASE_Q128
a constant used to change the log base from the tick math base to the saturation to leaf base.
uint256 private constant SAT_CHANGE_OF_BASE_Q128 = 0xa39713406ef781154a9e682c2331a7c03;
SAT_CHANGE_OF_BASE_TIMES_SHIFT
a constant used to shift when changing the base from tick math base to the saturation leaf base.
uint256 private constant SAT_CHANGE_OF_BASE_TIMES_SHIFT = 0xb3f2fb93ad437464387b0c308d1d05537;
TICK_OFFSET
tick offset added to ensure leaf calculation starts from 0 at the lowest leaf
int16 private constant TICK_OFFSET = 1112;
LOWEST_POSSIBLE_IN_PENALTY
the lowest possible saturation is always in penalty
uint256 internal constant LOWEST_POSSIBLE_IN_PENALTY = 0xd9999999999999999999999999999999;
MIN_LIQ_TO_REACH_PENALTY
the minimum liquidity to reach the possibility of being in penalty.
uint256 private constant MIN_LIQ_TO_REACH_PENALTY = 850;
INT_ONE
Constant number one as an int type. Used for rounding or iterating direction.
int256 private constant INT_ONE = 1;
INT_NEGATIVE_ONE
Constant number negative one. Used for rounding or iterating direction.
int256 private constant INT_NEGATIVE_ONE = -1;
INT_ZERO
Constant number zero as an int type. Used for rounding or iterating direction.
int256 private constant INT_ZERO = 0;
LEVELS_WITHOUT_LEAFS
Tree leafs are on level LEVELS_WITHOUT_LEAFS; root is level 0
uint256 internal constant LEVELS_WITHOUT_LEAFS = 3;
LOWEST_LEVEL_INDEX
for convenience, since used a lot, ==LEVELS_WITHOUT_LEAFS - 1
uint256 internal constant LOWEST_LEVEL_INDEX = 2;
LEAFS
uint256 internal constant LEAFS = 4096;
CHILDREN_PER_NODE
uint256 internal constant CHILDREN_PER_NODE = 16;
CHILDREN_AT_THIRD_LEVEL
uint256 private constant CHILDREN_AT_THIRD_LEVEL = 256;
TICKS_PER_TRANCHE
is the base for ticks, then the tranche base is , int only to not need casting below, equals TICKS_PER_TRANCHE
int256 private constant TICKS_PER_TRANCHE = 25;
TRANCHE_BASE_OVER_BASE_MINUS_ONE_Q72
for convenience, used to determine max sat per tranche to not cross in liq swap:
uint256 constant TRANCHE_BASE_OVER_BASE_MINUS_ONE_Q72 = 0x5a19b9039a07efd7b39;
MIN_TRANCHE
TickMath.MIN_TICK / TICKS_PER_TRANCHE - 1; // -1 to floor
int256 internal constant MIN_TRANCHE = -795;
MAX_TRANCHE
TickMath.MAX_TICK / TICKS_PER_TRANCHE;
int256 internal constant MAX_TRANCHE = 794;
FIELD_NODE_MASK
constants for bit reading and writing in nodes.
type(uint256).max >> (TOTAL_BITS - FIELD_BITS);
uint256 private constant FIELD_NODE_MASK = 0xffff;
SATURATION_MAX_BUFFER_TRANCHES
Buffer space (in tranches) allowed above the highest used tranche before hitting maxLeaf limit
uint8 internal constant SATURATION_MAX_BUFFER_TRANCHES = 3;
QUARTER_OF_MAG2
Twenty-five percent magnitude of two.
uint256 private constant QUARTER_OF_MAG2 = 25;
QUARTER_MINUS_ONE
Twenty-five percent minus one magnitude of two.
uint256 private constant QUARTER_MINUS_ONE = 24;
NUMBER_OF_QUARTERS
quarters per tranche.
uint256 private constant NUMBER_OF_QUARTERS = 4;
SATURATION_LIQUIDATION_SCALER
We make the penalty slightly larger to hit our desired premium for exceeding the time buffer.
uint256 private constant SATURATION_LIQUIDATION_SCALER = 10_020;
TWO_Q72
, used in saturation formula.
uint256 private constant TWO_Q72 = 0x2000000000000000000;
FOUR_Q144
, needed in quadratic formula is saturation.
uint256 private constant FOUR_Q144 = 0x4000000000000000000000000000000000000;
MAG4_TIMES_Q72
constant needed in formula.
uint256 private constant MAG4_TIMES_Q72 = 0x2710000000000000000000;
B_SQUARED_Q72_MINUS_ONE
used to round up results of TickMath.getTickAtPrice().
uint256 private constant B_SQUARED_Q72_MINUS_ONE = 0x10100c08050301c1008;
Q183
A large number that will not overflow when multiplied by B_SQUARED_Q72_MINUS_ONE
uint256 private constant Q183 = 0x8000000000000000000000000000000000000000000000;
TICKS_PER_TRANCHE_MAG2
used for calculating available liquidity.
uint256 private constant TICKS_PER_TRANCHE_MAG2 = 2500;
Functions
initializeSaturationStruct
initializes the satStruct, allocating storage for all nodes
initCheck can be removed once the tree structure is fixed
function initializeSaturationStruct(
SaturationStruct storage satStruct
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
satStruct | SaturationStruct | contains the entire sat data |
initTree
init the nodes of the tree
function initTree(
Tree storage tree
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
update
update the borrow position of an account and potentially check (and revert) if the resulting sat is too high
run accruePenalties before running this function
function update(
SaturationStruct storage satStruct,
Validation.InputParams memory inputParams,
address account,
uint256 fragileLiquidityAssets,
uint256 userSaturationRatioMAG2,
bool skipMinOrMaxTickCheck
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
satStruct | SaturationStruct | main data struct |
inputParams | Validation.InputParams | contains the position and pair params, like account borrows/deposits, current price and active liquidity |
account | address | for which is position is being updated |
fragileLiquidityAssets | uint256 | |
userSaturationRatioMAG2 | uint256 | |
skipMinOrMaxTickCheck | bool |
updateTreeGivenAccountTrancheAndSat
internal update that removes the account from the tree (if it exists) from its prev position and adds it to its new position
function updateTreeGivenAccountTrancheAndSat(
Tree storage tree,
SaturationPair memory newSaturation,
address account,
int256 newEndOfLiquidationInTicks,
uint256 activeLiquidityInLAssets,
int256 minOrMaxTick,
uint256 userSaturationRatioMAG2
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
newSaturation | SaturationPair | the new sat of the account, in units of LAssets (absolute) and relative to active liquidity |
account | address | whos position is being considered |
newEndOfLiquidationInTicks | int256 | the new tranche of the account in mag2. |
activeLiquidityInLAssets | uint256 | of the pair |
minOrMaxTick | int256 | |
userSaturationRatioMAG2 | uint256 |
removeSatFromTranche
remove sat from tree, for each tranche in a loop that could hold sat for the account
function removeSatFromTranche(Tree storage tree, address account) internal returns (bool highestSetLeafRemoved);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
account | address | whose position is being considered |
Returns
| Name | Type | Description |
|---|---|---|
highestSetLeafRemoved | bool | flag indicating whether we removed sat from the highest leaf xor not |
removeSatFromTrancheStateUpdates
depending on old and new leaf of the tranche, update the sats, fields and penalties of the tree
function removeSatFromTrancheStateUpdates(
Tree storage tree,
SaturationPair memory oldAccountSaturationInTranche,
int256 tranche,
uint256 oldLeaf,
address account,
uint256 trancheIndex
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
oldAccountSaturationInTranche | SaturationPair | account sat |
tranche | int256 | under consideration |
oldLeaf | uint256 | where tranche was located before this sat removal |
account | address | needed to accrue penalty |
trancheIndex | uint256 | which tranche of the account are we handling? |
addSatToTranche
add sat to tree, for each tranche in a loop as needed. we add to each tranche as much as it can bear. Saturation Distribution Logic This function distributes debt across multiple tranches, maintaining two types of saturation:
- satInLAssets: The absolute debt amount in L assets (should remain constant total)
- satRelativeToL: The relative saturation that depends on the tranche's price level As we move between tranches (different price levels), the same absolute debt translates to different relative saturations due to the price-dependent formula. conceptually satInLAssets should not be scaled as it represents actual debt that doesn't change with price. The formula applied here, derived in the introduction, is,
function addSatToTranche(
Tree storage tree,
address account,
int256 newEndOfLiquidationInTicks,
SaturationPair memory newSaturation,
uint256 activeLiquidityInLAssets,
uint256 userSaturationRatioMAG2,
int256 minOrMaxTick
) internal returns (bool highestSetLeafAdded);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
account | address | whose position is being considered |
newEndOfLiquidationInTicks | int256 | the new tranche of the account location in MAG2 |
newSaturation | SaturationPair | the new sat of the account, in units of LAssets (absolute) and relative to active liquidity |
activeLiquidityInLAssets | uint256 | of the pair |
userSaturationRatioMAG2 | uint256 | |
minOrMaxTick | int256 |
Returns
| Name | Type | Description |
|---|---|---|
highestSetLeafAdded | bool | flag indicating whether we removed sat from the highest leaf xor not |
getUsableTicksAndLastTranche
get the number of ticks in the tranche that can be used based on where the liquidation ends.
we approximate the this calculation using a percentage of ticks available.
function getUsableTicksAndLastTranche(
int256 endOfLiquidationTick,
bool netDebtX
) internal pure returns (uint256 usableTicks, int256 lastTranche);
restrictUsableTicksForMinOrMaxTick
when the min or max tick bounding our price estimate is reached while allocating saturation, we limit how much of that tranche can be used so that we don't exceed the liquidation capacity of the tranche closest to the price with the given position.
function restrictUsableTicksForMinOrMaxTick(
uint256 initialUsableTicks,
int256 nextTranche,
int256 minOrMaxTick,
bool netDebtX
) internal pure returns (uint256 usableTicks);
getAddSatToTrancheStateUpdatesParams
helper function for adding saturation to appropriate tranches for the given parameters.
function getAddSatToTrancheStateUpdatesParams(
Tree storage tree,
address account,
int256 tranche,
SaturationPair memory newSaturation,
uint256 activeLiquidityInLAssets,
uint256 userSaturationRatioMAG2,
uint256 usableTicks
) internal view returns (AddSatToTrancheStateUpdatesStruct memory addSatToTrancheStateUpdatesParams);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
account | address | whose position is being considered |
tranche | int256 | under consideration |
newSaturation | SaturationPair | the saturation values to add |
activeLiquidityInLAssets | uint256 | of the pair |
userSaturationRatioMAG2 | uint256 | user saturation ratio |
usableTicks | uint256 | number of ticks available to use |
Returns
| Name | Type | Description |
|---|---|---|
addSatToTrancheStateUpdatesParams | AddSatToTrancheStateUpdatesStruct | the struct with required params to |
addSatToTrancheStateUpdates
depending on old and new leaf of the tranche, update the sats, fields and penalties of the tree
function addSatToTrancheStateUpdates(
Tree storage tree,
AddSatToTrancheStateUpdatesStruct memory params
) internal returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
params | AddSatToTrancheStateUpdatesStruct | convenience struct holding params needed for these updates |
addSatToTrancheStateUpdatesHigherLeaf
Add sat to tranche state updates higher leaf
function addSatToTrancheStateUpdatesHigherLeaf(
Tree storage tree,
int256 tranche,
SaturationPair memory oldTrancheSaturation,
SaturationPair memory newTrancheSaturation,
uint256 oldLeaf,
uint256 newLeaf
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
tranche | int256 | the tranche that is being moved |
oldTrancheSaturation | SaturationPair | the old sat of the tranche |
newTrancheSaturation | SaturationPair | the new sat of the tranche |
oldLeaf | uint256 | the leaf that the tranche was located in before it was removed |
newLeaf | uint256 | the leaf that the tranche was located in after it was removed |
removeTrancheToLeaf
removing a tranche from a leaf, update the fields and sats up the tree
function removeTrancheToLeaf(
Tree storage tree,
SaturationPair memory trancheSaturation,
int256 tranche,
uint256 leaf
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
trancheSaturation | SaturationPair | the saturation of the tranche being moved |
tranche | int256 | that is being moved |
leaf | uint256 | the leaf |
addTrancheToLeaf
adding a tranche from a leaf, update the fields and sats up the tree
function addTrancheToLeaf(
Tree storage tree,
SaturationPair memory trancheSaturation,
int256 tranche,
uint256 leaf
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
trancheSaturation | SaturationPair | the saturation of the tranche being moved |
tranche | int256 | that is being moved |
leaf | uint256 | the leaf |
addSatUpTheTree
recursively add sat up the tree
function addSatUpTheTree(Tree storage tree, uint128 satInLAssets, bool add) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
satInLAssets | uint128 | sat to add to the current node, usually uint112, int to allow subtracting sat up the tree |
add | bool |
updatePenalties
update penalties in the tree given
function updatePenalties(
Tree storage tree,
uint256 thresholdLeaf,
uint256 addPenaltyInBorrowLSharesPerSatInQ72
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
thresholdLeaf | uint256 | from which leaf on the penalty needs to be added inclusive |
addPenaltyInBorrowLSharesPerSatInQ72 | uint256 | the penalty to be added |
getPenaltySharesPerSatFromLeaf
recursive function to sum penalties from leaf to root
function getPenaltySharesPerSatFromLeaf(
Tree storage tree,
uint256 leaf
) private view returns (uint256 penaltyInBorrowLSharesPerSatInQ72);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
leaf | uint256 | index (0 based) of the leaf |
Returns
| Name | Type | Description |
|---|---|---|
penaltyInBorrowLSharesPerSatInQ72 | uint256 | total penalty at the leaf, non-negative but returned as an int for recursion |
accrueAccountPenalty
calc penalty owed by account for repay, total over all the tranches that might contain this accounts' sat
function accrueAccountPenalty(Tree storage tree, address account) internal returns (uint256 penaltyInBorrowLShares);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
account | address | whose position is being considered |
Returns
| Name | Type | Description |
|---|---|---|
penaltyInBorrowLShares | uint256 | the penalty owed by the account |
trancheDirection
move in the appropriate direction when iterating.
function trancheDirection(
bool netDebtX
) private pure returns (int256);
Parameters
| Name | Type | Description |
|---|---|---|
netDebtX | bool | direction flag |
calcNewAccountPenalty
calc penalty owed by account for repay, total over all the tranches that might contain this accounts' sat
function calcNewAccountPenalty(
Tree storage tree,
uint256 leaf,
uint256 accountSatInTrancheInLAssets,
address account,
uint256 trancheIndex
) private view returns (uint256 penaltyInBorrowLShares, uint256 accountTreePenaltyInBorrowLSharesPerSatInQ72);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
leaf | uint256 | the leaf that the tranche belongs to |
accountSatInTrancheInLAssets | uint256 | the sat of the account in the tranche |
account | address | whose position is being considered |
trancheIndex | uint256 | the index of the tranche that is being added to |
Returns
| Name | Type | Description |
|---|---|---|
penaltyInBorrowLShares | uint256 | the penalty owed by the account |
accountTreePenaltyInBorrowLSharesPerSatInQ72 | uint256 | the penalty owed by the account in the tranche |
calcAndAccrueNewAccountPenalty
calc and accrue new account penalty
function calcAndAccrueNewAccountPenalty(
Tree storage tree,
SaturationPair memory oldAccountSaturationInTranche,
uint256 oldLeaf,
address account,
uint256 trancheIndex,
uint256 newTreePenaltyAtOnsetInBorrowLSharesPerSatInQ72PerTranche
) private;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
oldAccountSaturationInTranche | SaturationPair | the old sat of the account in the tranche |
oldLeaf | uint256 | the leaf that the tranche was located in before it was removed |
account | address | whose position is being considered |
trancheIndex | uint256 | the index of the tranche that is being added to |
newTreePenaltyAtOnsetInBorrowLSharesPerSatInQ72PerTranche | uint256 | the new penalty at onset in borrow l shares per sat in q72 per tranche |
accruePenalties
accrue penalties since last accrual based on all over saturated positions
function accruePenalties(
SaturationStruct storage satStruct,
address account,
uint256 externalLiquidity,
uint256 duration,
uint256 allAssetsDepositL,
uint256 allAssetsBorrowL,
uint256 allSharesBorrowL
) internal returns (uint112 penaltyInBorrowLShares, uint112 accountPenaltyInBorrowLShares);
Parameters
| Name | Type | Description |
|---|---|---|
satStruct | SaturationStruct | main data struct |
account | address | whose position is being considered |
externalLiquidity | uint256 | Swap liquidity outside this pool |
duration | uint256 | since last accrual of penalties |
allAssetsDepositL | uint256 | allAsset[DEPOSIT_L] |
allAssetsBorrowL | uint256 | allAsset[BORROW_L] |
allSharesBorrowL | uint256 | allShares[BORROW_L] |
Returns
| Name | Type | Description |
|---|---|---|
penaltyInBorrowLShares | uint112 | the penalty owed by the account |
accountPenaltyInBorrowLShares | uint112 | the penalty owed by the account |
calcNewPenalties
calc new penalties
function calcNewPenalties(
SaturationStruct storage satStruct,
uint256 externalLiquidity,
uint256 duration,
uint256 allAssetsDepositL,
uint256 allAssetsBorrowL,
uint256 allSharesBorrowL
)
private
view
returns (
uint256 penaltyNetXInBorrowLShares,
uint256 penaltyNetXInBorrowLSharesPerSatInQ72,
uint256 penaltyNetYInBorrowLShares,
uint256 penaltyNetYInBorrowLSharesPerSatInQ72,
uint256 thresholdLeaf
);
Parameters
| Name | Type | Description |
|---|---|---|
satStruct | SaturationStruct | main data struct |
externalLiquidity | uint256 | Swap liquidity outside this pool |
duration | uint256 | since last accrual of penalties |
allAssetsDepositL | uint256 | allAsset[DEPOSIT_L] |
allAssetsBorrowL | uint256 | allAsset[BORROW_L] |
allSharesBorrowL | uint256 | allShares[BORROW_L] |
Returns
| Name | Type | Description |
|---|---|---|
penaltyNetXInBorrowLShares | uint256 | the penalty net X in borrow l shares |
penaltyNetXInBorrowLSharesPerSatInQ72 | uint256 | the penalty net X in borrow l shares per sat in q72 |
penaltyNetYInBorrowLShares | uint256 | the penalty net Y in borrow l shares |
penaltyNetYInBorrowLSharesPerSatInQ72 | uint256 | the penalty net Y in borrow l shares per sat in q72 |
thresholdLeaf | uint256 | the threshold leaf |
calcNewPenaltiesGivenTree
calc new penalties given tree
function calcNewPenaltiesGivenTree(
Tree storage tree,
uint256 thresholdLeaf,
uint256 duration,
uint256 currentBorrowUtilizationInWad,
uint256 saturationUtilizationInWad,
uint256 allAssetsDepositL,
uint256 allAssetsBorrowL,
uint256 allSharesBorrowL
) private view returns (uint256 penaltyInBorrowLShares, uint256 penaltyInBorrowLSharesPerSatInQ72);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
thresholdLeaf | uint256 | the threshold leaf |
duration | uint256 | since last accrual of penalties |
currentBorrowUtilizationInWad | uint256 | current borrow utilization in WAD |
saturationUtilizationInWad | uint256 | saturation utilization in WAD |
allAssetsDepositL | uint256 | allAsset[DEPOSIT_L] |
allAssetsBorrowL | uint256 | allAsset[BORROW_L] |
allSharesBorrowL | uint256 | allShares[BORROW_L] |
Returns
| Name | Type | Description |
|---|---|---|
penaltyInBorrowLShares | uint256 | the penalty net X in borrow l shares |
penaltyInBorrowLSharesPerSatInQ72 | uint256 | the penalty net X in borrow l shares per sat in q72 |
accrueAndRemoveAccountPenalty
accrue and remove account penalty
function accrueAndRemoveAccountPenalty(
SaturationStruct storage satStruct,
address account
) internal returns (uint112 penaltyInBorrowLShares);
Parameters
| Name | Type | Description |
|---|---|---|
satStruct | SaturationStruct | main data struct |
account | address | whose position is being considered |
Returns
| Name | Type | Description |
|---|---|---|
penaltyInBorrowLShares | uint112 | the penalty owed by the account |
calculateHardLiquidationPremium
calculate the max liquidation premium in bips for a hard liquidation uses the tree * to determine to allow for partial liquidations as they occur.
notice that input params are mutated but then returned to their original state.
function calculateHardLiquidationPremium(
Account memory account,
uint256[6] memory userAssets,
uint256 sqrtPriceMinInQ72,
uint256 sqrtPriceMaxInQ72,
uint256 activeLiquidityScalerInQ72,
uint256 activeLiquidityAssets,
uint256 netBorrowRepaidLAssets,
uint256 netDepositSeizedLAssets
) internal pure returns (uint256 maxPremiumInBips, bool allAssetsSeized);
Parameters
| Name | Type | Description |
|---|---|---|
account | Account | The borrower's saturation account. |
userAssets | uint256[6] | The current assets of the borrower. |
sqrtPriceMinInQ72 | uint256 | The minimum sqrt price of the pair. |
sqrtPriceMaxInQ72 | uint256 | The maximum sqrt price of the pair. |
activeLiquidityScalerInQ72 | uint256 | The ratio between the square root of the product of the reserves and the active liquidity assets. |
activeLiquidityAssets | uint256 | The active liquidity assets of the pair. |
netBorrowRepaidLAssets | uint256 | net debt repaid in liquidity assets |
netDepositSeizedLAssets | uint256 | net collateral seized in liquidity assets |
Returns
| Name | Type | Description |
|---|---|---|
maxPremiumInBips | uint256 | the max premium in bips that |
allAssetsSeized | bool | whether all assets where seized or not |
mutateInputParamsForPartialLiquidation
mutate input params to only include the eligible debt and collateral for ltv calculation
function mutateInputParamsForPartialLiquidation(
Account memory account,
uint256[6] memory userAssets,
uint256 activeLiquidityScalerInQ72,
uint256 sqrtPriceMinInQ72,
uint256 sqrtPriceMaxInQ72,
uint256 netBorrowRepaidLAssets
) internal pure returns (uint256 netDepositInLAssets);
Parameters
| Name | Type | Description |
|---|---|---|
account | Account | borrowers saturation account |
userAssets | uint256[6] | |
activeLiquidityScalerInQ72 | uint256 | |
sqrtPriceMinInQ72 | uint256 | |
sqrtPriceMaxInQ72 | uint256 | |
netBorrowRepaidLAssets | uint256 | net debt in liquidity assets |
calcPortionsForPartialLiquidation
Calculate the percent of debt and collateral that is eligible for ltv calculation
note that we assume that the min and max sqrt price are switched prior to calling this.
function calcPortionsForPartialLiquidation(
Account memory account,
uint256 netBorrowRepaidLAssets
) internal pure returns (uint256 partialBorrow, uint256 totalBorrow, uint256 partialDeposit, uint256 totalDeposit);
setXorUnsetFieldBitUpTheTree
recursive function to unset the field when removing a tranche from a leaf
function setXorUnsetFieldBitUpTheTree(
Tree storage tree,
uint256 level,
uint256 nodeIndex,
uint256 lowerNodePos,
uint256 set
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
level | uint256 | level being updated |
nodeIndex | uint256 | index is the position (0 based) of the node in its level |
lowerNodePos | uint256 | pos is the relative position (0 based) of the node in its parent |
set | uint256 | 1 for set, 0 for unset |
findHighestSetLeafUpwards
recursive function to find the highest set leaf starting from a leaf, first upwards, until a set field is found, then downwards to find the best set leaf
function findHighestSetLeafUpwards(
Tree storage tree,
uint256 level,
uint256 nodeIndex
) private view returns (uint256 highestSetLeaf);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
level | uint256 | that we are checking |
nodeIndex | uint256 | corresponding to our leaf at our level |
Returns
| Name | Type | Description |
|---|---|---|
highestSetLeaf | uint256 | highest leaf that is set in the tree |
findHighestSetLeafDownwards
recursive function to find the highest set leaf starting from a node, downwards
internal for testing only
function findHighestSetLeafDownwards(
Tree storage tree,
uint256 level,
uint256 nodeIndex
) internal view returns (uint256 leaf);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
level | uint256 | that we are starting from |
nodeIndex | uint256 | that we are starting from |
Returns
| Name | Type | Description |
|---|---|---|
leaf | uint256 | highest leaf under the node that is set |
calcLiqSqrtPriceQ72
Calc sqrt price at which positions' LTV would reach LTV_MAX
Output guarantees (fuzz tested and logic)
Outside above range, outputs 0 (essentially no liq)
Does not revert if LTV_MAX < LTV, rather LTV_MAX < LTV causing liq points are
returned as 0, as if they do not exist, based on the assumption LTV \le LTV_MAX
function calcLiqSqrtPriceQ72(
uint256[6] memory userAssets
) internal pure returns (uint256 netDebtXLiqSqrtPriceXInQ72, uint256 netDebtYLiqSqrtPriceXInQ72);
Parameters
| Name | Type | Description |
|---|---|---|
userAssets | uint256[6] | The position |
Returns
| Name | Type | Description |
|---|---|---|
netDebtXLiqSqrtPriceXInQ72 | uint256 | 0 if no netX liq price exists |
netDebtYLiqSqrtPriceXInQ72 | uint256 | 0 if no netY liq price exists |
calcLiqSqrtPriceQ72HandleAllABCNonZero
calc liq price when the quadratic has all 3 terms, netY,netL,netX, i.e. X, Y, L are all significant
function calcLiqSqrtPriceQ72HandleAllABCNonZero(
CalcLiqSqrtPriceHandleAllABCNonZeroStruct memory input
) internal pure returns (uint256 netDebtXLiqSqrtPriceXInQ72, uint256 netDebtYLiqSqrtPriceXInQ72);
Parameters
| Name | Type | Description |
|---|---|---|
input | CalcLiqSqrtPriceHandleAllABCNonZeroStruct | the position |
Returns
| Name | Type | Description |
|---|---|---|
netDebtXLiqSqrtPriceXInQ72 | uint256 | 0 if no netX liq price exists |
netDebtYLiqSqrtPriceXInQ72 | uint256 | 0 if no netY liq price exists |
calcSatChangeRatioBips
Calculate the ratio by which the saturation has changed for account.
the algorithm here matches that of addSatToTranche(), but accumulates the total
saturation to compare it to what is needed. If the allocated total saturation is less than
what is needed, we return the ratio to help determine the saturation adjustment premium.
function calcSatChangeRatioBips(
SaturationStruct storage satStruct,
Validation.InputParams memory inputParams,
uint256 liqSqrtPriceInXInQ72,
uint256 liqSqrtPriceInYInQ72,
address account,
uint256 desiredSaturationMAG2
) internal view returns (uint256 ratioNetXBips, uint256 ratioNetYBips);
Parameters
| Name | Type | Description |
|---|---|---|
satStruct | SaturationStruct | The saturation struct containing both netX and netY trees. |
inputParams | Validation.InputParams | The params containing the position of account. |
liqSqrtPriceInXInQ72 | uint256 | The liquidation price for netX. |
liqSqrtPriceInYInQ72 | uint256 | The liquidation price for netY. |
account | address | The account for which we are calculating the saturation change ratio. |
desiredSaturationMAG2 | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
ratioNetXBips | uint256 | The ratio representing the change in netX saturation for account. |
ratioNetYBips | uint256 | The ratio representing the change in netY saturation for account. |
calculateEndOfLiquidationAdjustment
a helper function to calculate the one time adjustment for the offset of the end of the liquidation relative to the the boundary of the tranches.
This formula is described in the introduction.
function calculateEndOfLiquidationAdjustment(
int256 endOfLiquidationInTicks
) private pure returns (uint256 endOfLiquidationSqrtPriceAdjustment);
Parameters
| Name | Type | Description |
|---|---|---|
endOfLiquidationInTicks | int256 | the tick at which liquidation should end by. |
Returns
| Name | Type | Description |
|---|---|---|
endOfLiquidationSqrtPriceAdjustment | uint256 | the sqrt price adjustment required to be applied. |
calcTotalSatAfterLeafInclusive
calc total sat of all accounts/tranches/leafs higher (and same) as the threshold
iterate through leaves directly since penalty range is fixed (~8 leaves from 85% to 95% sat)
function calcTotalSatAfterLeafInclusive(
Tree storage tree,
uint256 thresholdLeaf
) internal view returns (uint128 satInPenaltyInLAssets);
Parameters
| Name | Type | Description |
|---|---|---|
tree | Tree | that is being read from or written to |
thresholdLeaf | uint256 | leaf to start adding sat from |
Returns
| Name | Type | Description |
|---|---|---|
satInPenaltyInLAssets | uint128 | total sat of all accounts with tranche in a leaf from at least thresholdLeaf (absolute saturation) |
getSatPercentageInWads
Get precalculated saturation percentage for a given delta (maxLeaf - highestLeaf)
function getSatPercentageInWads(
SaturationStruct storage satStruct
) internal view returns (uint256 saturationPercentage);
Parameters
| Name | Type | Description |
|---|---|---|
satStruct | SaturationStruct | The saturation struct |
Returns
| Name | Type | Description |
|---|---|---|
saturationPercentage | uint256 | The precalculated saturation percentage as uint256 |
satToLeaf
convert sat to leaf
function satToLeaf(
uint256 satLAssets
) internal pure returns (uint256 leaf);
Parameters
| Name | Type | Description |
|---|---|---|
satLAssets | uint256 | sat to convert |
Returns
| Name | Type | Description |
|---|---|---|
leaf | uint256 | resulting leaf from 0 to 2**12-1 |
calcSatAvailableToAddToTranche
calc how much sat can be added to a tranche such that it is healthy
*There are some important properties of the relationships between the returned values
and the input values, specifically the input newSaturationRelativeToLAssets and the
return values satAvailableToAddRelativeToLAssets and targetCapacityRelativeToLAssets.
For brevity we call these newSat, satAvailable, and target respectively in the
explanation below,
Three cases,
- ,
- if (1) then therefore if (2) then therefore since if (3) then therefore In all cases . Also, we know by the limiting that *
function calcSatAvailableToAddToTranche(
uint256 activeLiquidityInLAssets,
uint128 newSaturationRelativeToLAssets,
uint128 currentTrancheSatRelativeToLAssets,
uint256 userSaturationRatioMAG2,
uint256 usableTicks
) internal pure returns (uint128 satAvailableToAddRelativeToLAssets, uint256 targetCapacityRelativeToLAssets);
Parameters
| Name | Type | Description |
|---|---|---|
activeLiquidityInLAssets | uint256 | of the pair |
newSaturationRelativeToLAssets | uint128 | the sat that we want to add |
currentTrancheSatRelativeToLAssets | uint128 | the sat that the tranche already holds |
userSaturationRatioMAG2 | uint256 | the user's desired saturation ratio |
usableTicks | uint256 | the number of usable ticks within the tranche, restricted by either the end of liquidation or the min/max tick. |
Returns
| Name | Type | Description |
|---|---|---|
satAvailableToAddRelativeToLAssets | uint128 | considering the currentTrancheSatRelativeToLAssets and the max a tranche can have |
targetCapacityRelativeToLAssets | uint256 |
calcLastTickAndSaturation
calc the tick at which the best case liquidation would end and the saturation of the last tranche containing that tick. Not all the saturation may fit into that tranche, but we calculate it as if it will which means that adjustments to the saturation will need to be made if it doesn't fit when placing it into the tree.
function calcLastTickAndSaturation(
Validation.InputParams memory inputParams,
uint256 netXLiqSqrtPriceInXInQ72,
uint256 netYLiqSqrtPriceInXInQ72,
uint256 desiredThresholdMag2,
bool netDebtX,
bool skipMinOrMaxTickCheck
) internal pure returns (SaturationPair memory saturation, int256 endOfLiquidationInTicks, int256 currentTickLimit);
Parameters
| Name | Type | Description |
|---|---|---|
inputParams | Validation.InputParams | the input params |
netXLiqSqrtPriceInXInQ72 | uint256 | the midpoint of liquidation sqrt price of debt X in X/Y |
netYLiqSqrtPriceInXInQ72 | uint256 | the midpoint of liquidation sqrt price of debt Y in X/Y |
desiredThresholdMag2 | uint256 | the desired threshold |
netDebtX | bool | whether the net debt is X or Y |
skipMinOrMaxTickCheck | bool | when borrowing liquidity, the two liquidations will start facing opposite ways and the current price can only be on one side. When this happens, only one side's liquidation is valid, the other could not occur without the price moving through the valid liquidation. We also skip this check during calcSatChangeRatioBips() as we don't want to block liquidations. |
Returns
| Name | Type | Description |
|---|---|---|
saturation | SaturationPair | the saturation of the tranche |
endOfLiquidationInTicks | int256 | the point at which the liquidation would end. |
currentTickLimit | int256 | The point at which the liquidation can not start before due to the current price. |
calculateStartAndEndOfLiquidationPriceQ128
Convert from sqrtPrice by squaring, but we don't square the span because we only want to shift by half of the span to move from the middle of the liquidation to the end. Division prior to multiplication to avoid overflow since sqrtPriceQ72 is is 128 bits.
function calculateStartAndEndOfLiquidationPriceQ128(
uint256 liqSqrtPriceQ72,
uint256 sqrtPriceSpanQ72,
bool netDebtX
) internal pure returns (uint256 startOfLiquidationPriceQ128, uint256 endOfLiquidationPriceQ128);
liqPriceDividedBySpan
function liqPriceDividedBySpan(
uint256 liqPriceQ144,
uint256 sqrtPriceSpanQ72
) private pure returns (uint256 priceQ128);
liqPriceMultipliedBySpan
function liqPriceMultipliedBySpan(
uint256 liqPriceQ144,
uint256 sqrtPriceSpanQ72
) private pure returns (uint256 priceQ128);
calculateNetDebtAndSpan
calc net debt and span
function calculateNetDebtAndSpan(
Validation.InputParams memory inputParams,
uint256 liqSqrtPriceInXInQ72,
uint256 desiredThresholdMag2,
bool netDebtX
) internal pure returns (uint256 netDebtXorYAssets, uint256 netDebtLAssets, uint256 minSqrtPriceSpanQ72);
Parameters
| Name | Type | Description |
|---|---|---|
inputParams | Validation.InputParams | the input params |
liqSqrtPriceInXInQ72 | uint256 | |
desiredThresholdMag2 | uint256 | the desired threshold |
netDebtX | bool | whether the net debt is X or Y |
Returns
| Name | Type | Description |
|---|---|---|
netDebtXorYAssets | uint256 | the net debt |
netDebtLAssets | uint256 | the net debt in L assets |
minSqrtPriceSpanQ72 | uint256 | the tranche span in sqrtPrice |
calculateSaturation
calculate the relative saturation of the position at the end of liquidation. Since we place saturation in tranches starting at the tick where the liquidation would end and moving forward to the start of liquidation, this calculates the entire saturation as if it would fit in the last tranche, we then we will need to adjust the saturation each time we move forward a tranche to the next tranche by dividing by a factor of when we allocate the saturation later. The equation here is slightly different than the equation in our description since we multiply by a factor of for each tranche we move back from the start of liquidation tick. Thus here we use, where is the number of tranches we need to move back,
As we iterate through tranches, we divide by a factor of such that when we reach the
final tranche, our equation from the start applies.
Note that we also magnify the debt by the SATURATION_TIME_BUFFER_IN_MAG2 to account for
the potential growth that will occur over time due to interest. This allows for our
estimate of saturation to be static in spite of the dynamic impact of interest.
function calculateSaturation(
uint256 netDebtXOrYAssets,
uint256 endOfLiquidationSqrtPriceQ72,
bool netDebtX
) internal pure returns (uint128 saturation);
Parameters
| Name | Type | Description |
|---|---|---|
netDebtXOrYAssets | uint256 | the net debt in X or Y assets. |
endOfLiquidationSqrtPriceQ72 | uint256 | the tick at which the liquidation ends. |
netDebtX | bool | whether the debt is net in X or Y assets |
Returns
| Name | Type | Description |
|---|---|---|
saturation | uint128 | the saturation relative to active liquidity assets. |
calculateMinSqrtPriceSpanQ72
function calculateMinSqrtPriceSpanQ72(
uint256 collateral,
uint256 debt,
uint256 activeLiquidityAssets,
uint256 desiredThresholdMag2
) internal pure returns (uint256 sqrtPriceSpanQ72);
readFieldBitFromNode
read single bit value from the field of a node
function readFieldBitFromNode(uint256 node, uint256 bitPos) internal pure returns (uint256 bit);
Parameters
| Name | Type | Description |
|---|---|---|
node | uint256 | the full node |
bitPos | uint256 | position of the bit |
Returns
| Name | Type | Description |
|---|---|---|
bit | uint256 | the resulting bit, 0 xor 1, as a uint |
writeFlippedFieldBitToNode
write to node
function writeFlippedFieldBitToNode(uint256 nodeIn, uint256 bitPos) internal pure returns (uint256 nodeOut);
Parameters
| Name | Type | Description |
|---|---|---|
nodeIn | uint256 | node to read from |
bitPos | uint256 | position of the bit |
Returns
| Name | Type | Description |
|---|---|---|
nodeOut | uint256 | node with bit flipped |
readFieldFromNode
read field from node
function readFieldFromNode(
uint256 node
) internal pure returns (uint256 field);
Parameters
| Name | Type | Description |
|---|---|---|
node | uint256 | node to read from |
Returns
| Name | Type | Description |
|---|---|---|
field | uint256 | field of the node |
calcSaturationPenaltyRatePerSecondInWads
Calculates the penalty scaling factor based on current borrow utilization and saturation This implements the penalty rate function Formula:
Where,
function calcSaturationPenaltyRatePerSecondInWads(
uint256 currentBorrowUtilizationInWad,
uint256 saturationUtilizationInWad,
uint128 satInPenaltyInLAssets,
uint256 allAssetsDepositL
) internal pure returns (uint256 penaltyRatePerSecondInWads);
Parameters
| Name | Type | Description |
|---|---|---|
currentBorrowUtilizationInWad | uint256 | Current borrow utilization of L (u_0) |
saturationUtilizationInWad | uint256 | Current saturation utilization (u_s) |
satInPenaltyInLAssets | uint128 | The saturation in L assets in the penalty |
allAssetsDepositL | uint256 | The total assets deposited in L |
Returns
| Name | Type | Description |
|---|---|---|
penaltyRatePerSecondInWads | uint256 | The penalty rate per second in WADs |
Errors
MaxTrancheOverSaturated
if the largest sat in the trees is too large
error MaxTrancheOverSaturated();
NegativeSpan
raised if the , this shouldn't be possible.
error NegativeSpan();
LiquidationPassesMinOrMaxTick
raised if the start of liquidation would occur on the wrong side of the min or max tick price from the GeometricTWAP.
error LiquidationPassesMinOrMaxTick();
SaturationReachesMinOrMaxTick
raised if the the available saturation is not sufficient to keep the start of liquidation from reaching the wrong side of the min or max tick price from the GeometricTWAP.
error SaturationReachesMinOrMaxTick();
Structs
SaturationStruct
final structure containing all the storage data
struct SaturationStruct {
Tree netXTree;
Tree netYTree;
uint16 maxLeaf;
}
Tree
the main storage type of tree struct within the SaturationStruct.
struct Tree {
bool netX;
uint16 highestSetLeaf;
uint128 totalSatInLAssets;
uint256 tranchesWithSaturation;
uint256[][LEVELS_WITHOUT_LEAFS] nodes;
Leaf[LEAFS] leafs;
mapping(int16 => uint16) trancheToLeaf;
mapping(int16 => SaturationPair) trancheToSaturation;
mapping(address => Account) accountData;
}
Leaf
a leaf contains multiple tranches and contains the total sat and penalty for the leaf
struct Leaf {
Uint16Set.Set tranches;
SaturationPair leafSatPair;
uint256 penaltyInBorrowLSharesPerSatInQ72;
}
Account
basic data per account associated with an address stored in the Tree struct in a
map as the value associated with the owners address as the key.
struct Account {
bool exists;
int16 lastTranche;
uint112 penaltyInBorrowLShares;
SaturationPair[] satPairPerTranche;
uint256[] treePenaltyAtOnsetInBorrowLSharesPerSatInQ72PerTranche;
}
CalcLiqSqrtPriceHandleAllABCNonZeroStruct
used in memory to avoid stack overflow in calcLiqSqrtPriceQ72().
struct CalcLiqSqrtPriceHandleAllABCNonZeroStruct {
int256 netLInMAG2;
int256 netXInMAG2;
int256 netYInMAG2;
uint256 netYAbsInMAG2;
uint256 borrowedXAssets;
uint256 borrowedYAssets;
}
AddSatToTrancheStateUpdatesStruct
used in memory to avoid stack overflow in addSatToTranche().
struct AddSatToTrancheStateUpdatesStruct {
int256 tranche;
uint256 newLeaf;
SaturationPair oldTrancheSaturation;
SaturationPair newTrancheSaturation;
SaturationPair satAvailableToAdd;
uint256 targetCapacityRelativeToLAssets;
address account;
}
SaturationPair
a pair of saturation values used and stored throughout this library.
struct SaturationPair {
uint128 satInLAssets;
uint128 satRelativeToL;
}