Liquidation¶
Overview¶
The Liquidation module extends the Stability Pool by handling the safe liquidation of under-collateralised positions.
It is deployed as an immutable implementation and is executed only via delegatecall from the StabilityPool (or an authorised proxy).
Warning
Direct interaction with the implementation contract is blocked by onlyProxy.
This guarantees state changes persist in the StabilityPool and prevents standalone abuse.
No Deposit or Minting Fees on Liquidation
When a liquidation occurs, no deposit or minting fees are charged by the LendingPool or the RWA Vault.
The full value of the liquidated collateral is used to restore the Stability Pool and protocol health, ensuring that liquidations are not penalized by protocol fees at any stage.
Purpose¶
- Check borrower health; abort if the position is still safe.
- Transfer borrower collateral (ERC-20 / ERC-721) to the RWA Vault and mint iREET.
- Split minted iREET per protocol parameters:
- Minting fee → Treasury
- Burn → permanent removal
- Swap → convert to pmUSD
- Liquidity rewards → deposit to Curve Gauge
- Restore the Stability Pool’s rToken by re-depositing swapped pmUSD into the LendingPool.
Note
Keeping liquidation logic in a delegate-callable module keeps the Stability Pool compact and upgradeable without storage clashes.
Philosophy & Incentives¶
Manager-Driven Liquidations¶
- RAAC does not incentivize external liquidators.
- Only protocol managers trigger liquidations; this matches the real-estate risk profile.
Conservative Parameters¶
- Borrow Threshold: 50%
- Liquidation Threshold: 60% (10% buffer)
Risk Mitigation¶
- Real-estate volatility is low (typical drawdowns are gradual).
- Oracle updates weekly.
- Focus on Section 8 housing (government-subsidised).
- Diversified across low-risk states.
User Incentives¶
Users deposit rToken → receive deToken → may stake for rental yield and external liquidity incentives, concentrating rewards via gauges.
Liquidation Mechanics¶
- The entire position is taken (debt + collateral).
- The LendingPool clears the user’s debt when
finalizeLiquidationis executed; collateral is transferred to the Stability Pool. - Collateral is then routed to the RWA Vault to mint iREET, which is split across Treasury / Burn / Swap / Liquidity.
- The Swap leg (iREET → pmUSD) is re-deposited into the LendingPool to restore rToken used by the Stability Pool.
Excess pmUSD handling
Any excess pmUSD after liquidation is deposited back into the LendingPool, minting rToken to the Stability Pool without minting deToken.
In a bad-debt scenario that surplus therefore repairs the pool's backing before it becomes withdrawable value for anyone.
Operating a Liquidation¶
Liquidations in v1 are operated, not automated. The manager role is held by a multisig, which lets the protocol review each liquidation and submit the parameter changes, any Treasury funding and the liquidateBorrower call together as one batch. Before each liquidation the operator sizes it to current market conditions using the parameters below, so that it clears in a single transaction.
Parameters¶
| Parameter | Set via | Who | What it controls |
|---|---|---|---|
mintBps (Treasury fee) |
setLiquidationVaultTokenSplit |
Manager or owner | Share of the minted iREET sent to the Treasury |
burnBps |
setLiquidationVaultTokenSplit |
Manager or owner | Share of the minted iREET burned |
swapBps |
setLiquidationVaultTokenSplit |
Manager or owner | Share of the minted iREET sold for pmUSD through LiquidationSwap |
liquidityBps |
setLiquidationVaultTokenSplit |
Manager or owner | Share of the minted iREET sent to liquidity rewards |
slippageProtectionBps |
LiquidationSwap.setLiquidityPool |
Owner | Maximum discount to NAV the swap leg will accept |
enableSlippageProtection |
LiquidationSwap.setEnableSlippageProtection |
Owner | Whether the NAV floor is applied at all |
The four split values are updated atomically in one call and must sum to 10000. The split is not a fixed protocol constant: the operator sets it before each liquidation, mainly to choose how much iREET the swap leg sells.
The swap floor is derived from NAV, by design¶
LiquidationSwap computes its minimum output from the RWA Vault's pricePerShare() (NAV) and the pmUSD/USD oracle, not from the AMM's own quote:
expectedDy = pricePerShare * amount / pmUSDPrice
minDy = expectedDy * (10000 - slippageProtectionBps) / 10000
If the pool trades iREET more than slippageProtectionBps below NAV, whether because the swap is large relative to pool depth or because the market is at a discount, the exchange reverts with SlippageExceeded and the whole liquidation reverts. This is intentional. A floor derived from the pool's own quote would accept whatever price a depressed or manipulated pool offers; the NAV floor stops the Stability Pool from selling collateral below fair value unless an operator explicitly allows it.
The value set at deployment is an initial value, not a permanent bound. When a liquidation is blocked by the floor, the operator has two levers:
- Reduce
swapBps. Sell only what the pool can absorb within tolerance, with a buffer, and route the remainder to the Treasury share (mintBps) or the other legs. The Treasury can then sell its iREET gradually, at normal market pace, instead of in one large trade. - Widen
slippageProtectionBps(owner), when selling at the discount is the right decision, for example under a sustained market discount where waiting costs more than the discount.
A smaller swap does not by itself make the liquidation revert: any amount the swap does not raise is a shortfall, handled by one of the paths below.
Covering a shortfall¶
liquidateBorrower compares the Stability Pool's own pmUSD balance (swap proceeds plus anything already held) with the position's debt. The difference, rTokenAmountRequired, is the only amount the pool must raise by redeeming its rToken through lendingPool.withdraw(). That call needs idle pmUSD in the LendingPool, so under high utilisation it can revert even when the pool holds enough rToken. Depending on conditions, the operator chooses one of the following:
| Path | What happens | Needs idle pmUSD in the LendingPool? | Stability Pool backing afterwards |
|---|---|---|---|
| A. Treasury pre-funds the Stability Pool | The Treasury transfers pmUSD to the Stability Pool before the liquidation. Swap proceeds plus the top-up cover the debt, so rTokenAmountRequired is zero and lendingPool.withdraw() is never called. Any excess is re-deposited as rToken to the pool. |
No | Whole |
| B. Stability Pool absorbs the gap | The pool redeems its own rToken for the gap through lendingPool.withdraw(). |
Yes | Under-backed by the gap until the Treasury restores it |
| B + liquidity injection | If idle cash is too low for path B, the Treasury first injects pmUSD into the LendingPool (a direct transfer to the rToken contract) so the withdrawal can clear. If the pool's own rToken is short (InsufficientBalance), the Treasury transfers rpmUSD it holds to the Stability Pool. |
Provided by the Treasury | Under-backed by the gap until the Treasury restores it |
| C. Redirect to Treasury, repay later | The operator lowers swapBps and sends the unsold iREET to the Treasury via mintBps. The Stability Pool covers the gap (path B), taking a temporary cut. The Treasury sells the iREET over time at normal market pace and transfers rToken to the Stability Pool to make it whole. |
Yes, or injected as above | Restored as the Treasury sells |
Restoring backing after paths B and C works as described in Restoring backing from the Treasury: rToken is transferred to the pool directly, never through deposit(), so no deToken is minted against it.
Time under liquidation
Once a position is under liquidation it stays latched (isUnderLiquidation) so the borrower cannot exit mid-process, and its debt keeps accruing until liquidateBorrower succeeds. A reverted attempt changes no state. The interest accrued in that window is bounded by how quickly the operator adjusts parameters or arranges Treasury funding, which is why these adjustments are part of the standard liquidation procedure rather than an exceptional step.
Access Control & Safety¶
| Mechanism | Description |
|---|---|
onlyProxy |
Prevents direct calls; execution must come via Stability Pool. |
| Managers | Only managers/owner in Stability Pool can call liquidateBorrower and setLiquidationVaultTokenSplit. The manager role is held by a multisig. |
| Validation | • Adapters must be registered • Asset/vault adapter tokens must match • data must validate • Debt > 0 • Pool must hold enough liquidity |
Key Functions¶
| Function | Type | Description |
|---|---|---|
liquidateBorrower(poolAdapter, vaultAdapter, user, data, minSharesOut) |
external | Entry point from Stability Pool; runs the full liquidation cycle and emits BorrowerLiquidated. |
vaultAdapter is always the ERC-721 path
poolAdapter is the borrower-side IAssetAdapter; vaultAdapter is the vault-side IVaultAssetAdapter, and in practice it is always RAACNFTVaultAdapterV2. No ERC-20 vault adapter is deployed. The proxy still validates that both adapters report the same asset token before proceeding.
| _handleLiquidation(poolAdapter, vaultAdapter, data) | internal | Routes collateral → RWA Vault → iREET → split (treasury, burn, swap, liquidity). |
| _handleSwap(amount) | internal | Swaps iREET for pmUSD via the configured swap implementation. |
| _handleLiquidityRewards(amount) | internal | Deposits iREET into a Curve Gauge. |
| _handleTreasuryFee(amount) | internal | Transfers the minting-fee share to Treasury. |
Split struct
struct LiquidationVaultTokenSplit {
uint256 mintPercentage; // Treasury
uint256 burnPercentage; // Burn
uint256 swapPercentage; // Swap
uint256 liquidityPercentage; // Gauge
} // must sum to 10000 (100%)
Liquidation Flow¶
- Pre-checks: validate adapters, debt,
data, and pool liquidity. - Stability Pool → LendingPool: call
finalizeLiquidation(debt is cleared; collateral moves to Stability Pool). - Collateral → RWA Vault: deposit collateral; receive iREET.
- iREET split: Treasury / Burn / Swap / Liquidity per
LiquidationVaultTokenSplit. - Swap leg: iREET → pmUSD via configured pool/route.
- Restore rToken: deposit swapped pmUSD back into the LendingPool to regain rToken used by the Stability Pool.
- Shortfall branch: if the Stability Pool's pmUSD balance (swap proceeds plus any Treasury top-up made beforehand) is below the debt, the gap is covered by the pool's own
rTokenvialendingPool.withdraw(), which requires idle pmUSD in the LendingPool. The pool is then under-backed until the deficit is repaired. See Covering a shortfall for the available paths and Backing, Bad Debt and the Treasury Backstop. - Emit
BorrowerLiquidated.
Liquidation Process Diagram (detailed)¶

Notes¶
- All percentages use basis points (
10000 = 100%). - Slippage risk is expected; managers size the swap leg to available pool depth before each liquidation (see Operating a Liquidation).
- No external liquidation incentives; liquidations are manager-driven.