Skip to content

LendingPool

Overview

The LendingPool is the core contract of the RAAC system.
It manages deposits, withdrawals, borrowing, repayment and liquidation while outsourcing collateral-specific logic to Adapters.
Any token or NFT can be used as collateral as long as a compliant adapter is registered.

Interest rate

If you want to consult more regarding the interest and yield, please consult the user documentation at (link here).

Purpose

  • Hold the main liquidity of the protocol and mint yield-bearing RToken receipts against deposits
  • Abstract collateral management via adapters so the pool logic stays asset agnostic
  • Accrue interest using an index-based model and a prime-rate driven interest curve. Interest is pegged to \(1/2\) US Prime rate.
  • Enforce protocol and risk parameters (caps, thresholds, buffer ratio, etc.)
  • Provide optional insurance with a grace period for under-collateralised positions
  • Integrate with an external vault to harvest yield while keeping a liquidity buffer

Parameters

All mutable parameters are changed through setParameter(OwnerParameter param, uint256 newValue) and are grouped in two buckets:

Protocol (risk) parameters — PROTOCOL_ROLE

Early Beta

These parameters can be adjusted by the protocol to optimize system performance and risk management.

Enum Description
LendingPoolBorrowCap Maximum total debt the pool can create
LendingPoolSupplyCap Maximum total deposits the pool can accept
BorrowThreshold % of collateral value that can be borrowed (in bp)
LiquidationThreshold % at which a position becomes liquidatable (in bp)
LiquidationGracePeriod Seconds given to insured users to repay (in sec)
LiquidityBufferRatio Portion of liquidity kept on-chain vs vault (currently 20%/80% split)
WithdrawalStatus 1 pauses user withdrawals, 0 un-pauses
CanPaybackDebt Allows third-party repayments. (1/0)
MaxRate Max interest at 100% utilization (in RAY)

Governance parameters — GOVERNANCE_ROLE

Enum Description
InsuranceFee % fee (bp) charged when a user buys insurance
ProtocolFeeRate Interest spread captured by the protocol (RAY)
LendingPoolDepositFee % fee (bp) on deposits
VaultOpeningFee Fixed fee (reserve asset) to open a collateral vault

An event ParameterChanged(param, oldValue, newValue) is emitted on every update.

Collateral Adapters

To ensure flexibility and support for future RWA assets as collateral, RAAC uses a system of adapters. New assets can either use existing adapters or have new ones created to suit their specific requirements (e.g., ERC1155, ERC4626).
Each collateral type is integrated via an Adapter that implements the IAssetAdapter interface.
Adapters handle the translation of generic pool calls (depositAsset, withdrawAsset, borrow, repay, etc.) into asset-specific logic.
Only adapters whitelisted by the owner through registerAdapter or removed via unregisterAdapter can be used.

Core Mechanics

Deposits & RToken minting

  1. User calls deposit(amount) sending lending asset
  2. A deposit fee (LendingPoolDepositFee) is transferred to the FeeCollector
  3. Net amount is supplied and the user receives RToken

Implementation Notes

  • Depositors receive RToken at a 1:1 ratio for each crvUSD supplied (18 decimals).
  • The deposit fee is determined by the LendingPoolDepositFee governance parameter.

Vault Position & Collateral

Before interacting with any adapter the user must pay the one-off VaultOpeningFee via openVaultPosition(). This is one time opening fee.

Vault Fee (Early Beta)

At the early beta, there is no vault fee. Although this may change in the future—consider checking the protocol parameters for the most up-to-date information.

Collateral is then managed with:

Function Description
depositAsset(adapter, data) Supplies collateral through the adapter
withdrawAsset(adapter, data) Withdraws collateral; reverts if health factor would fall below liquidation threshold

Borrowing

Function Description
borrow(adapter, data, amount) Standard borrow; reverts if resulting health factor < threshold
borrowWithInsurance(adapter, data, amount) Borrows and immediately insures the entire outstanding debt. Fee = (uninsuredDebt + amount) * InsuranceFee

Insurance Fee Application & Forfeiture

  • The insurance fee is a percentage-based fee applied to the entire borrowed capital at the time of insuring.
  • If you perform a new borrow and do not insure it, any existing insurance on your position is forfeited. To regain insurance, you must pay the insurance fee on the entire outstanding debt (not just the new amount).
  • Repaid borrows lose insurance: Once a borrow is repaid, any insurance associated with that debt is voided and does not carry over to future borrows.
  • Always ensure you understand the insurance status of your position before borrowing or repaying to avoid unexpected forfeiture or additional fees.

Validation

  • Pool utilization must remain below LendingPoolBorrowCap.
  • Sufficient collateral must be provided, as determined by the BorrowThreshold parameter.

Insurance Flow

  • The default liquidation grace period is 72 hours (LiquidationGracePeriod).
  • During the grace period, the borrower can fully repay their debt and call closeLiquidation to prevent collateral seizure.
  • Any new borrowing must also be insured; if not, any existing insurance on the position is forfeited.
  • If you opt out of insurance and later wish to re-enable it, you must pay the insurance fee on the entire outstanding debt.

Forgot to Close Liquidation After Repayment?

If you fully repay your debt but forget to call closeLiquidation, your collateral cannot be seized.
You may close your position and reclaim your collateral at any time by calling closeLiquidation, as long as the debt remains fully repaid.

Repayment

Repayment Paused During Protocol Pause

If the protocol is paused and repayments are disabled, users who are under liquidation cannot repay their debt to exit liquidation.
In such cases, a governance or protocol proposal can be enacted to extend the liquidation grace period, ensuring users are treated fairly and are not penalized due to circumstances beyond their control.

Function Description
repay(adapter, data, amount) Self repayment
repayOnBehalf(adapter, data, amount, user) Third-party repayment; amount must be ≥ 1% of the user's scaled debt

Operation Restrictions During Liquidation

All repayment, deposit, withdrawal, or borrowing actions will revert if isUnderLiquidation is true, unless the position is insured and the action occurs within the liquidation grace period.

Minimum Repayment by Third Parties

When a third party uses repayOnBehalf, they must repay at least 1% of the user's current outstanding debt (including any accrued interest).
Attempts to repay less than this minimum will revert. This ensures meaningful repayments and prevents dust attacks.

Minimum Residual Debt on Repayment

When making a repayment, if the remaining debt after repayment would fall below the protocol's MIN_BORROW_AMOUNT, the transaction will revert with a ResidualCannotBeRepaid error.
In such cases, you must fully repay the outstanding debt in a single transaction to close your position.
This prevents leaving "dust" borrows below the minimum threshold and ensures protocol integrity.

Liquidation lifecycle

Stage Callable by Function Notes
Start Anyone initiateLiquidation (proxy) Marks position under liquidation when health factor < 1
Close Borrower closeLiquidation (proxy) Can be executed during grace period if the debt is fully repaid (requires insurance)
Finalize StabilityPool finalizeLiquidation (proxy) After grace period, transfers collateral & settles debt

All three functions are executed via delegate-call proxies (liquidationProxy) to keep LendingPool byte-code small.

Decision Flow

flowchart TD
    A{Is user health factor < 1e18?}
    A -- No --> Z[Position stays active]
    A -- Yes --> B[initiateLiquidation<br/>Callable by anyone]

    B --> C{Is user insured?}
    C -- No --> F[finalizeLiquidation<br/>StabilityPool seizes collateral]

    C -- Yes --> D{Within grace period?}
    D -- No --> F[finalizeLiquidation<br/>StabilityPool seizes collateral]
    D -- Yes --> E{Debt repaid in full?}
    E -- Yes --> G[closeLiquidation<br/>Borrower closes position]
    E -- No --> F[finalizeLiquidation<br/>StabilityPool seizes collateral]

    Z:::good
    G:::good
    F:::bad

classDef good fill:#d9f0d3,stroke:#333,stroke-width:1px;
classDef bad fill:#f4cccc,stroke:#333,stroke-width:1px;

Liquidity Buffer & Vault

The protocol integrates with a Curve SCRVUSD vault as the vault proxy for yield generation.
This vault has no deposit limits, withdraw limits, or custom strategies. Share price is expected to increase monotonously.

Liquidity Rebalancing

  • Target allocation: 20% pool liquidity, 80% vault liquidity. See protocols section for correct parameters.
  • Triggered after every deposit, withdraw, repay, borrow, or liquidation
  • Yield is harvested on deposit/withdraw, prioritising the withdraw amount
  • Full yield extraction attempted on every rebalance; any leftover is harvested later

Vault Migration Process

When the owner sets a new vault with setVault:

  1. Contract attempts to withdraw full amount from old vault through withdrawAll
  2. Calculates liquidity vs yield split
  3. Distributes funds accordingly to the new vault
  4. Failsafe: if some SCRVUSD shares remain, they are sent to Treasury

Key Functions

Function Description Access
deposit(amount) Deposit reserve asset → mint RToken Any
withdraw(amount) Redeem RToken for reserve asset Any
openVaultPosition() Pays the vault opening fee Any
depositAsset(adapter, data) Deposit collateral Any
withdrawAsset(adapter, data) Withdraw collateral Any
borrow(adapter, data, amount) Borrow reserve asset Any
borrowWithInsurance(adapter, data, amount) Borrow with insurance Any
repay(adapter, data, amount) Repay own debt Any
repayOnBehalf(adapter, data, amount, user) Third-party repayment (≥1%) Any
initiateLiquidation(adapter, user, data) Start liquidation Any
closeLiquidation(adapter, data) Close liquidation during grace Borrower
finalizeLiquidation(adapter, user, data) Finalize liquidation StabilityPool
setParameter(param, value) Update protocol/governance parameter Role-based
setPrimeRate(rate) Updates the base prime rate PrimeRateOracle
setVault(address) Change yield vault Owner

Implementation Details

  • Built with OpenZeppelin: ReentrancyGuard, Pausable, Ownable, AccessControl, SafeERC20, ERC721Holder
  • Interest accrues using liquidity and usage indices (ReserveLibrary)
  • Dynamic interest curve: baseRateoptimalRatemaxRate around utilisation and prime rate
  • Liquidity automatically rebalanced after each deposit, withdraw, borrow, or repay
  • Insurance state tracked per position (isInsured, rawInsuredBalance)
  • Vault and liquidation logic delegated via proxies for reduced byte-code size

Interactions

  • Adapters (IAssetAdapter) – wrapper for ERC-20, ERC-721, or RWA collateral
  • RToken – interest-bearing receipt token
  • DebtToken – tracks scaled debt per position
  • StabilityPool – backstop that executes liquidations
  • Curve crvUSD Vault – yield source for idle liquidity
  • FeeCollector – receives protocol, deposit, and insurance fees

Notes

  • Fee Collector is always set at time of deployment. It is not expected to be removed.

TODO: Add a list of refeences to other pages