Saturation
Authors: imi@1m1.io, Will duelingGalois@protonmail.com
Maintains saturation, or sat, which is the net borrow tracked for each
liquidation tranche.
Saturation is measured relative to active liquidity assets: deposited L less borrowed L.
In practice the tree stores the net borrow amount, while callers interpret it relative
to active liquidity.
Price movement is modeled with tick base and tranche base . A tranche groups
TICKS_PER_TRANCHE ticks:
For a tranche bounded by and , liquidity implies the following X and Y amounts:
For one tick, with integer ticks and where , saturation relative to L is:
Saturation is kept in two trees: one for net X borrows and one for net Y borrows. The
price is always the price of Y in units of X, and most calculations use sqrt price. A
net X borrow is a position whose liquidation would push price lower; a net Y borrow
pushes price higher.
Leaves split the uint112 saturation space into ordered intervals. Tranches and leaves
above the configured threshold are over-saturated and accrue penalties. Saturation is
cumulative in the tree, so updating a leaf also updates its parents. Penalties are path
sums in L assets.
To allocate saturation, a debt and its collateral are represented as a series of tranche
sized pieces. For liquidation start tick and end tick :
The tranche boundaries advance by TICKS_PER_TRANCHE. For net X debt,
; for net Y debt,
. Each subsequent tranche is
.
The X-side allocation can be rewritten as:
The left side is total saturation . The right side splits that total into each tranche's saturation , starting furthest from the current price and moving toward it:
The Y-side allocation is the same except that multiplies by rather
than dividing by it. During iteration, each tranche reduces the remaining saturation by
a factor of . The first tranche also applies a one-time adjustment for
the offset between the end of liquidation and the tranche boundary.
If saturation reaches minOrMaxTick, the position is already at the edge of the probable
price range and the calculation reverts.
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;
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_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_RESET_FOR_STRADDLE_SLOPE_BIPS
Slope for calculating premium when resetting saturation for straddle positions
where transitions to . Applied to
to produce premiumBips. Matches the
Desmos coefficient . At
the raw premium evaluates to
MAX_SAT_RESET_FOR_STRADDLE_PREMIUM_BIPS; past that point the cap engages.
uint256 internal constant SAT_RESET_FOR_STRADDLE_SLOPE_BIPS = 100_000;
MAX_SAT_RESET_FOR_STRADDLE_PREMIUM_BIPS
Maximum premium when resetting saturation for zero-to-positive straddle positions.
uint256 internal constant MAX_SAT_RESET_FOR_STRADDLE_PREMIUM_BIPS = 2000;
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
Number of leaves: .
uint256 internal constant LEAFS = 4096;
CHILDREN_PER_NODE
Number of children per node: .
uint256 internal constant CHILDREN_PER_NODE = 16;
CHILDREN_AT_THIRD_LEVEL
Number of children at the third level: .
uint256 private constant CHILDREN_AT_THIRD_LEVEL = 256;
TICKS_PER_TRANCHE
Number of ticks grouped into one saturation tranche. If is the tick base, then the tranche base is:
This is an int256 only to avoid casts below.
int256 internal 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;
TWO_Q72
, used in the saturation formula.
uint256 private constant TWO_Q72 = 0x2000000000000000000;
FOUR_Q144
, needed in the saturation quadratic formula.
uint256 private constant FOUR_Q144 = 0x4000000000000000000000000000000000000;
MAG4_TIMES_Q72
constant needed in the 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 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 |
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
) 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 |
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,