Skip to content

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

  1. Check borrower health; abort if the position is still safe.
  2. Transfer borrower collateral (ERC-20 / ERC-721) to the RWA Vault and mint iREET.
  3. Split minted iREET per protocol parameters:
  4. Minting fee → Treasury
  5. Burn → permanent removal
  6. Swap → convert to pmUSD
  7. Liquidity rewards → deposit to Curve Gauge
  8. 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 finalizeLiquidation is 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:

  1. 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.
  2. 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.

STAGE 1 · SIZING · MANAGER MULTISIG STAGE 2 · SWAP AGAINST THE NAV FLOOR STAGE 3 · SHORTFALL COVERAGE STAGE 4 · RESTORE · TERMINAL Position under liquidation isUnderLiquidation · debt accruing setLiquidationVaultTokenSplit() swapBps sized to pool depth · batched by multisig swap clears minDy ? Adjust and retry lower swapBps · route the rest to Treasury or owner widens slippageProtectionBps proceeds >= debt ? Debt repaid from proceeds surplus re-deposited as rToken to the pool Treasury pre-funds ? pmUSD sent to Stability Pool rTokenAmountRequired = 0 withdraw() never called withdraw() can clear ? Treasury injects liquidity pmUSD into the LendingPool and/or rpmUSD to the Stability Pool Pool rToken covers the gap withdraw() · pool under-backed by the gap Treasury restores backing sells its iREET · sends rToken to the pool Liquidation complete debt cleared · Stability Pool backing whole no · SlippageExceeded retry yes yes no · shortfall yes · path A no · path B / C no · low idle cash or rToken yes

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

  1. Pre-checks: validate adapters, debt, data, and pool liquidity.
  2. Stability Pool → LendingPool: call finalizeLiquidation (debt is cleared; collateral moves to Stability Pool).
  3. Collateral → RWA Vault: deposit collateral; receive iREET.
  4. iREET split: Treasury / Burn / Swap / Liquidity per LiquidationVaultTokenSplit.
  5. Swap leg: iREET → pmUSD via configured pool/route.
  6. Restore rToken: deposit swapped pmUSD back into the LendingPool to regain rToken used by the Stability Pool.
  7. 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 rToken via lendingPool.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.
  8. Emit BorrowerLiquidated.

Liquidation Process Diagram (detailed)

Liquidation Process Diagram


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.