Skip to main content

PartialLiquidations

Git Source

We allow liquidations to happen in parts when the position is spread across more than one tranche. These partial liquidations allow the liquidator to specify how many tranches they are liquidating based on how much they are repaying. Smaller slices start with the tranche range closest to the current price and move outward, so the slice reaches expected LTV before the full position on the same debt side does. In most cases, one tranche will be liquidated at one time, but in some cases, a sliver of liquidity in the first tranche may not be worth the cost to liquidate until it and the next tranche have become profitable to liquidate. Once we determine the number of tranches to liquidate, we build the liquidated slice from the debt side first. L uses a weight because both deposit and borrow L can be included together; the opposing side is then solved directly so the slice lands on the expected liquidation LTV. A partial liquidation splits the signed position into the liquidated slice and the remaining position:

P0=PΔ+P1,P0=[L0,X0,Y0],PΔ=[LΔ,XΔ,YΔ]P_0=P_\Delta+P_1, \qquad P_0=[L_0,X_0,Y_0], \qquad P_\Delta=[L_\Delta,X_\Delta,Y_\Delta]

The slice must preserve component signs, must not exceed the original component magnitudes, and must have exact expected liquidation LTV at the slice square-root price s_delta. The whole position and the remaining position do not need to have the same LTV as the liquidated slice. Signed components are net deposits minus borrows:

L0=depositLborrowLX0=depositXborrowXY0=depositYborrowY\begin{align} L_0 &= depositL - borrowL \\ X_0 &= depositX - borrowX \\ Y_0 &= depositY - borrowY \end{align}

The slice is constructed from the debt-side component first. Throughout this library, s is a square-root price, not a price. At square-root price s, X is measured in L as X / s and Y is measured in L as Y * s. Let k be the unscaled expected liquidation LTV:

k=EXPECTED_SATURATION_LTVk=EXPECTED\_SATURATION\_LTV

In code, k is represented by EXPECTED_SATURATION_LTV_MAG2 / MAG2. In signed slice notation, repaid debt components are negative and seized collateral components are positive. For net debt X, the exact-LTV slice condition is:

k=XΔ/sΔLΔLΔ+YΔsΔk=\dfrac{-X_\Delta/s_\Delta-L_\Delta}{L_\Delta+Y_\Delta s_\Delta}

The net debt Y equations are symmetric:

k=YΔsΔLΔLΔ+XΔ/sΔk=\dfrac{-Y_\Delta s_\Delta-L_\Delta}{L_\Delta+X_\Delta/s_\Delta}

State Variables

MAG2_INT

int256 internal constant MAG2_INT = int256(MAG2);

Functions

calculatePartialLiquidation

Calculates the partial liquidation slice for the tranches covered by the repayment.

Returns the full user asset array when every tranche is included. Returns the zero array when no L-denominated debt is repaid. The tranche count is chosen from absolute satInLAssets, while the slice geometry uses the corresponding relative saturation values.

function calculatePartialLiquidation(
Saturation.SaturationPair[] memory satPairPerTranche,
int16 lastTranche,
uint256[6] memory userAssets,
uint256 activeLiquidityAssets,
uint256 netRepaidLAssets,
uint256 netSeizedLAssets,
bool netDebtX
) internal pure returns (uint256[6] memory liquidation);

Parameters

NameTypeDescription
satPairPerTrancheSaturation.SaturationPair[]Saturation entries for the borrower, ordered from the end of liquidation toward the start of liquidation.
lastTrancheint16Last tranche occupied by the borrower in the liquidation direction.
userAssetsuint256[6]Borrower assets in [depositL, depositX, depositY, borrowL, borrowX, borrowY] order.
activeLiquidityAssetsuint256Active liquidity used to scale tranche saturation.
netRepaidLAssetsuint256Net debt repaid by the liquidator, denominated in L.
netSeizedLAssetsuint256Net collateral seized by the liquidator, denominated in L.
netDebtXboolWhether the liquidation repays net X debt. If false, it repays net Y debt.

Returns

NameTypeDescription
liquidationuint256[6]Component-bounded asset amounts to remove from the borrower.

calcMutation

Builds a liquidation slice from raw tranche-boundary and saturation inputs. It derives the target square-root price and asset weights from the same saturation window, applies L through a shared weight, then solves the deposit-side delta directly from the exact-LTV equation. The target slice square-root price is the selected tranche boundary moved by the start weight from the same saturation walk:

sΔ=bTwss_\Delta=b^T\sqrt{w_s}

In signed notation, the debt-side slice then fixes one raw borrow amount:

XΔ=borrowXwXfor net debt XX_\Delta=-borrowX\cdot w_X \qquad \text{for net debt X} YΔ=borrowYwYfor net debt YY_\Delta=-borrowY\cdot w_Y \qquad \text{for net debt Y}

L is selected next. The remaining side is solved last from the exact-LTV equation and may be either collateral seized or same-side borrow repaid, depending on the sign of the solved delta.

function calcMutation(
uint256[6] memory userAssets,
uint256 trancheBoundarySqrtPriceQ72,
uint256 partialSaturation,
uint256 totalSaturation,
uint256 activeLiquidityAssets,
bool netDebtX
) internal pure returns (uint256[6] memory liquidation);

Parameters

NameTypeDescription
userAssetsuint256[6]Borrower assets in [depositL, depositX, depositY, borrowL, borrowX, borrowY] order.
trancheBoundarySqrtPriceQ72uint256Boundary sqrt price for the tranche adjacent to the included slice.
partialSaturationuint256Relative saturation included in the partial liquidation.
totalSaturationuint256Total relative borrower saturation across all tranches.
activeLiquidityAssetsuint256Active liquidity used to scale tranche saturation.
netDebtXboolWhether the debt side is X. If false, the debt side is Y.

Returns

NameTypeDescription
liquidationuint256[6]Component-bounded asset amounts to remove from the borrower.

applyLMutation

Applies the L weight to both L legs and returns the resulting signed L delta. The raw L legs are both included at the same weight:

depositLΔ=depositLwL,borrowLΔ=borrowLwLdepositL_\Delta=depositL\cdot w_L,\qquad borrowL_\Delta=borrowL\cdot w_L

The signed L contribution is:

LΔ=depositLΔborrowLΔL_\Delta=depositL_\Delta-borrowL_\Delta
function applyLMutation(
uint256[6] memory liquidation,
uint256[6] memory userAssets,
uint256 lWeightQ72
) private pure returns (int256 lDelta);

Parameters

NameTypeDescription
liquidationuint256[6]Liquidation array being built.
userAssetsuint256[6]Borrower assets in [depositL, depositX, depositY, borrowL, borrowX, borrowY] order.
lWeightQ72uint256Q72 weight to apply to deposit L and borrow L.

Returns

NameTypeDescription
lDeltaint256Signed L contribution of the liquidated slice.

calcDebtSideWeightQ72

Calculates the saturation-derived weight for the debt-side borrow asset. The selected debt-side weight is:

wdebt={wXif net debt XwYif net debt Yw_{debt}= \begin{cases} w_X & \text{if net debt X} \\ w_Y & \text{if net debt Y} \end{cases}
function calcDebtSideWeightQ72(
uint256 sqrtStartWeightQ72,
uint256 sqrtEndWeightQ72,
bool netDebtX
) private pure returns (uint256 debtSideWeightQ72);

Parameters

NameTypeDescription
sqrtStartWeightQ72uint256Start sqrt weight of the included saturation window.
sqrtEndWeightQ72uint256End sqrt weight of the included saturation window.
netDebtXboolWhether the debt side is X. If false, the debt side is Y.

Returns

NameTypeDescription
debtSideWeightQ72uint256Q72 weight applied to the debt-side borrow amount.

applyRemainingSideDelta

Applies the signed remaining-side delta to the matching deposit or borrow leg. Positive deltas seize remaining-side collateral. Negative deltas repay borrow on that side. Partial borrow repayment is valid as long as it is not greater than the starting borrow.

RΔ>0depositΔ=min(RΔ,deposit)R_\Delta>0 \Rightarrow deposit_\Delta=\min(R_\Delta, deposit) RΔ<0borrowΔ=min(RΔ,borrow)R_\Delta<0 \Rightarrow borrow_\Delta=\min(-R_\Delta, borrow)
function applyRemainingSideDelta(
uint256[6] memory liquidation,
uint256[6] memory userAssets,
int256 remainingSideDelta,
uint256 depositIndex,
uint256 borrowIndex
) private pure;

Parameters

NameTypeDescription
liquidationuint256[6]Liquidation array being built.
userAssetsuint256[6]Borrower assets in [depositL, depositX, depositY, borrowL, borrowX, borrowY] order.
remainingSideDeltaint256Signed amount for the side opposite the fixed debt-side borrow.
depositIndexuint256Deposit token index for the remaining side.
borrowIndexuint256Borrow token index for the remaining side.

calcRemainingSideDelta

Solves the signed remaining-side delta for the fixed debt-side repayment. The returned delta is positive when the slice must seize deposit-side collateral and negative when it must repay borrow on that side to land on expected LTV. For net debt X, define the fixed X-side debt value in L-units:

debtSideValueInL=XΔsΔ=borrowXΔsΔdebtSideValueInL=-\frac{X_\Delta}{s_\Delta}=\frac{borrowX_\Delta}{s_\Delta}

Then:

YΔ=debtSideValueInL(1+k)LΔksΔY_\Delta=\dfrac{debtSideValueInL-(1+k)L_\Delta}{ks_\Delta}

For net debt Y, define the fixed Y-side debt value in L-units:

debtSideValueInL=YΔsΔ=borrowYΔsΔdebtSideValueInL=-Y_\Delta s_\Delta=borrowY_\Delta s_\Delta

Then:

XΔ=sΔ(debtSideValueInL(1+k)LΔ)kX_\Delta=\dfrac{s_\Delta(debtSideValueInL-(1+k)L_\Delta)}{k}
function calcRemainingSideDelta(
uint256 borrowDelta,
int256 lDelta,
uint256 sqrtPriceQ72,
bool netDebtX
) private pure returns (int256 remainingSideDelta);

Parameters

NameTypeDescription
borrowDeltauint256Final debt-side borrow amount repaid by the liquidation.
lDeltaint256Signed L contribution already included in the liquidation.
sqrtPriceQ72uint256Target liquidation sqrt price for the slice.
netDebtXboolWhether the debt side is X. If false, the debt side is Y.

Returns

NameTypeDescription
remainingSideDeltaint256Signed delta for the remaining side.

calcXWeightQ72

Calculates the partial liquidation weight for asset X. Formula for w_X:

w_X &= \large \frac{ 1- \sqrt{w_s} }{ \sqrt{w_e}-\sqrt{w_s} }
function calcXWeightQ72(
uint256 sqrtStartWeightQ72,
uint256 sqrtEndWeightQ72
) private pure returns (uint256 xWeightQ72);

Parameters

NameTypeDescription
sqrtStartWeightQ72uint256Q72 start sqrt weight for the included saturation window.
sqrtEndWeightQ72uint256Q72 end sqrt weight for the included saturation window.

Returns

NameTypeDescription
xWeightQ72uint256Q72 weight for asset X.

calcYWeightQ72

Calculates the partial liquidation weight for asset Y. Formula for w_Y:

wY=wewX\begin{equation} w_Y = \sqrt{w_e} \cdot w_X \end{equation}
function calcYWeightQ72(uint256 weightXQ72, uint256 sqrtEndWeightQ72) private pure returns (uint256 yWeightQ72);

Parameters

NameTypeDescription
weightXQ72uint256Q72 weight for asset X.
sqrtEndWeightQ72uint256Q72 end sqrt weight for the included saturation window.

Returns

NameTypeDescription
yWeightQ72uint256Q72 weight for asset Y.

calcLWeightQ72

Calculates the partial liquidation weight for asset L from the debt-side slice. Net-zero L is omitted. Net-borrow L uses the debt-side weight. Net-deposit L chooses the lower feasible L endpoint implied by the fixed debt-side repayment. For net-borrow L:

LΔ=L0wdebtL_\Delta=L_0\cdot w_{debt}

For net-deposit L, L is the free collateral-side variable:

0LΔL00 \le L_\Delta \le L_0

Let debtSideValueInL be the fixed debt-side value and remainingCollateralInL be the remaining positive deposit-side collateral, both in L-units:

debtSideValueInL={borrowXΔsΔif net debt XborrowYΔsΔif net debt YdebtSideValueInL= \begin{cases} \dfrac{borrowX_\Delta}{s_\Delta} & \text{if net debt X} \\ borrowY_\Delta s_\Delta & \text{if net debt Y} \end{cases} remainingCollateralInL={(depositYborrowY)sΔif net debt X and depositY>borrowYdepositXborrowXsΔif net debt Y and depositX>borrowX0otherwiseremainingCollateralInL= \begin{cases} (depositY-borrowY)s_\Delta & \text{if net debt X and } depositY>borrowY \\ \dfrac{depositX-borrowX}{s_\Delta} & \text{if net debt Y and } depositX>borrowX \\ 0 & \text{otherwise} \end{cases}

Exact LTV gives the lower feasible endpoint. Only positive remaining-side collateral changes that endpoint; zero or net-borrow remaining-side value uses the same lower bound:

LΔ=max(0,debtSideValueInLkremainingCollateralInL1+k)when remainingCollateralInL>0L_\Delta=\max\left(0,\frac{debtSideValueInL-k\cdot remainingCollateralInL}{1+k}\right) \qquad \text{when } remainingCollateralInL>0 LΔ=debtSideValueInL1+kwhen remainingCollateralInL=0L_\Delta=\frac{debtSideValueInL}{1+k} \qquad \text{when } remainingCollateralInL=0
function calcLWeightQ72(
uint256[6] memory userAssets,
uint256 debtSideWeightQ72,
uint256 debtSideDelta,
uint256 targetLiquidationSqrtPriceQ72,
bool netDebtX
) private pure returns (uint256 lWeightQ72);

Parameters

NameTypeDescription
userAssetsuint256[6]Borrower assets in [depositL, depositX, depositY, borrowL, borrowX, borrowY] order.
debtSideWeightQ72uint256Q72 weight applied to the debt-side borrow amount.
debtSideDeltauint256Final debt-side borrow amount repaid by the liquidation.
targetLiquidationSqrtPriceQ72uint256Target liquidation sqrt price for the slice.
netDebtXboolWhether the debt side is X. If false, the debt side is Y.

Returns

NameTypeDescription
lWeightQ72uint256Q72 weight applied to deposit L and borrow L.

calcSqrtStartWeightQ72

Calculates the start sqrt weight for the included saturation window. The saturation is normalized by the active liquidity scaled by the maximum allowed saturation ratio. Define:

rmax=MAX_SATURATION_RATIO_IN_MAG2MAG2r_{max}=\frac{MAX\_SATURATION\_RATIO\_IN\_MAG2}{MAG2}

We round up the sqrt to avoid sqrtStartWeightQ72 == Q72, which would return 0 from calcXWeightQ72. Normalize the included saturation by active liquidity:

as=satrmaxLa_s=\frac{sat}{r_{max}L}

The underlying non-sqrt start weight is:

ws={1+as(B1) if net debt of X 11+as(B1) if net debt of Y\begin{equation} w_s = \begin{cases} 1+a_s(B-1) & \text{ if net debt of X } \\ \frac{1}{1+a_s(B-1)} & \text{ if net debt of Y} \end{cases} \end{equation}
function calcSqrtStartWeightQ72(
uint256 partialSaturation,
uint256 activeLiquidityAssets,
bool netDebtX
) internal pure returns (uint256 sqrtStartWeightQ72);

Parameters

NameTypeDescription
partialSaturationuint256Saturation included in the partial liquidation.
activeLiquidityAssetsuint256Active liquidity used to scale tranche saturation.
netDebtXboolWhether the debt side is X. If false, the reciprocal weight is used.

Returns

NameTypeDescription
sqrtStartWeightQ72uint256Q72 start sqrt weight, adjusted for the debt side.

calcSqrtEndWeightQ72

Calculates the end sqrt weight for the included saturation window. Normalize the remaining saturation by active liquidity:

rmax=MAX_SATURATION_RATIO_IN_MAG2MAG2,ae=sattotalsatrmaxLr_{max}=\frac{MAX\_SATURATION\_RATIO\_IN\_MAG2}{MAG2}, \qquad a_e=\frac{sat_{total}-sat}{r_{max}L}

The underlying non-sqrt end weight is:

we={1ae(B1) if net debt of X 11ae(B1) if net debt of Y\begin{equation} w_e = \begin{cases} 1-a_e(B-1) & \text{ if net debt of X } \\ \frac{1}{1-a_e(B-1)} & \text{ if net debt of Y} \end{cases} \end{equation}
function calcSqrtEndWeightQ72(
uint256 partialSaturation,
uint256 totalSaturation,
uint256 activeLiquidityAssets,
bool netDebtX
) private pure returns (uint256 sqrtEndWeightQ72);

Parameters

NameTypeDescription
partialSaturationuint256Saturation included in the partial liquidation.
totalSaturationuint256Total borrower saturation across all tranches.
activeLiquidityAssetsuint256Active liquidity used to scale tranche saturation.
netDebtXboolWhether the debt side is X. If false, the reciprocal weight is used.

Returns

NameTypeDescription
sqrtEndWeightQ72uint256Q72 end sqrt weight, adjusted for the debt side.

weightInNumeratorOrDenominator

Adjusts a sqrt weight into numerator form for X debt or denominator form for Y debt. Net debt Y walks the same tranche geometry in inverted square-root-price space:

w1w\sqrt{w}\mapsto\frac{1}{\sqrt{w}}
function weightInNumeratorOrDenominator(
uint256 sqrtWeight,
bool netDebtX
) private pure returns (uint256 adjustedSqrtWeight);

Parameters

NameTypeDescription
sqrtWeightuint256Q72 sqrt weight before debt-side orientation.
netDebtXboolWhether the debt side is X. If false, the reciprocal weight is returned.

Returns

NameTypeDescription
adjustedSqrtWeightuint256Q72 sqrt weight oriented for the debt side.

Structs

LoopMemoryState

struct LoopMemoryState {
uint256 satArrayLength;
uint256 partialSatInLAssets;
uint256 includedTranches;
uint256 partialSatLimit;
}