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¶
- User calls
deposit(amount)
sending lending asset - A deposit fee (
LendingPoolDepositFee
) is transferred to theFeeCollector
- 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
:
- Contract attempts to withdraw full amount from old vault through
withdrawAll
- Calculates liquidity vs yield split
- Distributes funds accordingly to the new vault
- 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:
baseRate
→optimalRate
→maxRate
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.