Functions¶
Note
Some of the parameters require adapter
and data
to be set.
- If the adapter is an ERC20, the data
parameter is the amount.
- For ERC721, the data
parameter is the token ID.
Liquidity Provision¶
deposit
¶
deposit(uint256 amount)
Summary
Deposits the reserve asset (crvUSD) into the pool and mints RToken 1:1 for the caller, minus any deposit fee.
Guarded Method
Callable by any address (EOA or contract). Subject to pause.
Parameters
Name | Type | Description |
---|---|---|
amount | uint256 | Deposit amount in crvUSD (18 decimals). |
Emits
Deposit(address indexed user, uint256 amount, uint256 rTokenMinted)
Reverts
SupplyCapExceeded()
— ifLendingPoolSupplyCap
would be exceededAmountZero()
— ifamount == 0
Requirements
- Deposit fee (
LendingPoolDepositFee
) goes toFeeCollector
before minting RToken. - Withdrawals may be paused but deposits remain open unless explicitly disabled.
Source code
function deposit(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) notBlacklisted(msg.sender) {
ReserveLibrary.updateReserveState(reserve, rateData);
uint256 depositFeeAmount = (feeCollector == address(0) || msg.sender == stabilityPool) ? 0 : amount.percentMul(parameters.depositFee);
uint256 depositAmount = amount - depositFeeAmount;
_validateDepositSupplyCap(depositAmount);
uint256 mintedAmount = ReserveLibrary.deposit(reserve, rateData, depositAmount, msg.sender);
_rebalanceLiquidity();
if(depositFeeAmount > 0) _collectFee(depositFeeAmount, keccak256("LENDING_FEE"));
depositBlock[msg.sender] = block.number;
emit Deposit(msg.sender, depositAmount, mintedAmount);
}
withdraw
¶
withdraw(uint256 amount)
Summary
Burns the caller’s RToken balance to redeem reserve assets (crvUSD).
Updates reserve state, ensures liquidity, and rebalances after the withdrawal.
Guarded Method
- Callable by any user with RToken balance.
- Restricted by nonReentrant
, whenNotPaused
, and blacklist checks.
- Withdrawals can be globally paused via governance parameters, except for StabilityPool.
Full Withdrawal Tip
Pass uint256.max
as the amount
parameter to withdraw your entire RToken balance.
This ensures that no residual "dust" remains due to rounding or index scaling.
Parameters
Name | Type | Description |
---|---|---|
amount | uint256 | Amount of reserve assets to withdraw. Use uint256.max to withdraw full balance. |
Emits
- Withdraw(address indexed user, uint256 amountWithdrawn)
Reverts
WithdrawalsArePaused()
— if governance has disabled withdrawals (non-StabilityPool users).InsufficientBalance()
— if caller’s RToken balance < requested amount.LiquidityShortfall()
— if pool liquidity is insufficient to honor the withdrawal.
Requirements
- Caller must hold at least
amount
RToken. - Pool liquidity must be sufficient after rebalancing.
- Reserve state updated via
ReserveLibrary.updateReserveState
before execution.
Example (TypeScript / ethers)
Source code
function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) notBlacklisted(msg.sender) {
if (msg.sender != stabilityPool && parameters.withdrawalsPaused) revert WithdrawalsArePaused();
ReserveLibrary.updateReserveState(reserve, rateData);
uint256 scaledAmount = (amount == type(uint256).max)
? IERC20(reserve.reserveRTokenAddress).balanceOf(msg.sender)
: amount;
if (scaledAmount > IERC20(reserve.reserveRTokenAddress).balanceOf(msg.sender)) revert InsufficientBalance();
_ensureLiquidity(scaledAmount);
(uint256 amountWithdrawn,,) = ReserveLibrary.withdraw(
reserve,
rateData,
scaledAmount,
msg.sender
);
_rebalanceLiquidity();
emit Withdraw(msg.sender, amountWithdrawn);
}
Collateral management¶
openVaultPosition
¶
openVaultPosition()
Summary
Initializes a user’s vault for collateral management.
Must be called once before using depositAsset
or borrow
.
Collects a one-time fee if configured.
Guarded Method
- Callable by any user not blacklisted.
- No
nonReentrant
guard since it is a simple state change.
Parameters - None
Emits
- VaultOpeningFeePaid(address indexed user, uint256 fee)
Reverts
- VaultPositionAlreadyOpened()
— if vault already initialized.
Requirements
- One-time action per user.
- If
vaultOpeningFee > 0
, fee is transferred toFeeCollector
.
Source code
function openVaultPosition() external notBlacklisted(msg.sender) {
if (vaultOpened[msg.sender]) revert VaultPositionAlreadyOpened();
vaultOpened[msg.sender] = true;
if (parameters.vaultOpeningFee > 0) _collectFee(parameters.vaultOpeningFee, keccak256("LENDING_FEE"));
emit VaultOpeningFeePaid(msg.sender, parameters.vaultOpeningFee);
}
depositAsset
¶
depositAsset(address adapter, bytes calldata data)
Summary
Supplies collateral into the pool through a registered adapter.
Updates reserve state, verifies that the vault is open, and delegates the deposit logic to the adapter.
Guarded Method
- Callable by any user with an open vault.
- Protected by
nonReentrant
,whenNotPaused
,onlySupportedAdapter
, and blacklist checks.
Parameters
Name | Type | Description |
---|---|---|
adapter | address | Registered IAssetAdapter to route deposit |
data | bytes | Adapter-specific calldata |
Emits
- May emit adapter-specific events via IAssetAdapter.deposit
.
Reverts
CannotDepositWhenUnderLiquidation()
— if the position is flagged under liquidation.VaultPositionNotOpened()
— ifopenVaultPosition()
has not been called.UnsupportedAdapter()
— if adapter not registered.
Requirements
- User must have paid the
VaultOpeningFee
. - Collateral type must be whitelisted.
- Reserve state updated via
ReserveLibrary.updateReserveState
.
Example (TypeScript / ethers)
Source code
function depositAsset(address adapter, bytes calldata data) external nonReentrant whenNotPaused onlySupportedAdapter(adapter) notBlacklisted(msg.sender) {
CollateralPosition memory position = getPosition(adapter, msg.sender, data);
if (position.isUnderLiquidation) revert CannotDepositWhenUnderLiquidation();
if (!vaultOpened[msg.sender]) revert VaultPositionNotOpened();
ReserveLibrary.updateReserveState(reserve, rateData);
IAssetAdapter(adapter).deposit(msg.sender, data);
}
withdrawAsset
¶
withdrawAsset(address adapter, bytes calldata data)
Summary
Withdraws collateral from the pool via a registered adapter.
Ensures that health factor remains above liquidation threshold after withdrawal.
Guarded Method
- Callable by any user with sufficient collateral.
- Protected by nonReentrant
, whenNotPaused
, onlySupportedAdapter
, and blacklist checks.
Parameters
Name | Type | Description |
---|---|---|
adapter | address | Registered IAssetAdapter for the collateral |
data | bytes | Adapter-specific calldata |
Emits
- May emit adapter-specific events via IAssetAdapter.withdraw
.
Reverts
- CannotWithdrawUnderLiquidation()
— if position is flagged under liquidation.
- WithdrawalWouldLeaveUserUnderCollateralized()
— if withdrawal reduces health factor below HEALTH_FACTOR_LIQUIDATION_THRESHOLD
.
Requirements
- Health factor after withdrawal must remain ≥ threshold.
- Reserve state updated via ReserveLibrary.updateReserveState
.
- Collateral must be whitelisted.
Example (TypeScript / ethers)
Source code
function withdrawAsset(address adapter, bytes calldata data) external nonReentrant whenNotPaused onlySupportedAdapter(adapter) notBlacklisted(msg.sender) {
CollateralPosition memory position = getPosition(adapter, msg.sender, data);
if (position.isUnderLiquidation) revert CannotWithdrawUnderLiquidation();
ReserveLibrary.updateReserveState(reserve, rateData);
uint256 positionDebt = getPositionScaledDebt(adapter, msg.sender, data);
if (positionDebt > 0) {
uint256 depositedValue = IAssetAdapter(adapter).getAssetValue(msg.sender, data);
uint256 withdrawalValue = IAssetAdapter(adapter).getWithdrawValue(msg.sender, data);
uint256 newCollateralValue = depositedValue - withdrawalValue;
uint256 newHealthFactor;
if (positionDebt < 1) newHealthFactor = WadRayMath.RAY;
else {
uint256 collateralThreshold = newCollateralValue.percentMul(parameters.borrowThreshold);
newHealthFactor = (collateralThreshold * 1e18) / positionDebt;
}
if (newHealthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
}
IAssetAdapter(adapter).withdraw(msg.sender, data);
}
Borrow and repayments¶
borrow
¶
borrow(address adapter, bytes data, uint256 amount)
Summary
Borrows the reserve asset (crvUSD) against the caller’s collateral managed by adapter
.
Internally validates collateral, ensures pool liquidity (vault pull if needed), mints scaled DebtToken, transfers the borrowed amount to the user, updates indices/rates, and rebalances liquidity.
If the position had insurance, it is cleared after a successful borrow.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral position |
data | bytes |
Adapter-specific calldata identifying the position |
amount | uint256 |
Amount of reserve asset to borrow (18 decimals) |
Returns
- None
Emits
- Borrow(address indexed user, uint256 amount)
Reverts
CannotBorrowUnderLiquidation()
— if the position is flagged under liquidation.- Any error from
_validateBorrow
— insufficient collateral vs.BorrowThreshold
/ caps. - Liquidity errors from
_ensureLiquidity
— if the pool cannot source funds (after vault pull). - Adapter/role/amount guards listed above.
Side Effects
- Clears insurance state on the position after a successful borrow:
position.isInsured = false
position.rawInsuredBalance = 0
Requirements
- Adapter must be registered and support the asset.
- Post-borrow health factor must remain ≥ liquidation threshold (enforced by
_validateBorrow
). - Sufficient pool liquidity must be obtainable (
_ensureLiquidity
). - Indices updated before state changes to preserve accrual correctness.
Notes
- Insurance is intentionally cleared on borrow. To remain insured after borrowing again, the user must re-purchase insurance according to the protocol rules.
- Debt accounting uses scaled amounts against
reserve.usageIndex
.
Example (TypeScript / ethers)
import { parseUnits } from "ethers";
// Adapter + data identify the collateral position (e.g., NFT vault, ERC20 vault)
const adapter = "0xAdapter...";
const data = adapterSpecificCalldata; // bytes encoded off-chain per adapter ABI
const amount = parseUnits("5000", 18); // 5,000 crvUSD
const tx = await lendingPool.borrow(adapter, data, amount);
await tx.wait();
Source code (public wrapper and internal implementation)
function borrow(address adapter, bytes calldata data, uint256 amount)
external
nonReentrant
whenNotPaused
onlyValidAmount(amount)
onlySupportedAdapter(adapter)
notBlacklisted(msg.sender)
{
_borrow(adapter, data, amount);
CollateralPosition storage position = positions[msg.sender][IAssetAdapter(adapter).getPositionKey(data)];
if (position.isInsured) {
position.isInsured = false;
position.rawInsuredBalance = 0;
}
}
function _borrow(address adapter, bytes calldata data, uint256 amount) internal {
CollateralPosition storage position = positions[msg.sender][IAssetAdapter(adapter).getPositionKey(data)];
if (position.isUnderLiquidation) revert CannotBorrowUnderLiquidation();
ReserveLibrary.updateReserveState(reserve, rateData);
_validateBorrow(adapter, data, amount);
_ensureLiquidity(amount);
(, uint256 underlyingAmount, uint256 userIncrease, ) =
IDebtToken(reserve.reserveDebtTokenAddress).mint(
msg.sender,
msg.sender,
amount,
reserve.usageIndex,
abi.encode(adapter, data)
);
position.positionIndex = reserve.usageIndex;
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
position.rawDebtBalance += underlyingAmount + userIncrease;
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, 0, amount);
_rebalanceLiquidity();
emit Borrow(msg.sender, amount);
}
borrowWithInsurance
¶
borrowWithInsurance(address adapter, bytes data, uint256 amount)
Summary
Executes a borrow and simultaneously insures the resulting debt.
The insurance fee is calculated and collected upfront before the state changes.
Post-borrow, the user’s position is marked insured for both the newly borrowed amount and any previously uninsured balance.
Differences from borrow
- Requires the user to have approved and hold sufficient reserve asset to cover the insuranceFeeAmount
.
- Calls _collectFee(..., INSURANCE_FEE)
immediately after the borrow.
- Sets position.isInsured = true
and increments rawInsuredBalance
by uninsuredBalance + amount
.
- Emits InsuredAsset(...)
in addition to the normal Borrow
event.
Extra Reverts
- InsufficientAllowanceForFees()
— if ERC20 allowance < insuranceFeeAmount
.
- InsufficientBalance()
— if ERC20 balance < insuranceFeeAmount
.
Notes
- Borrowing again without this function clears existing insurance.
- To stay insured across multiple borrows, always use borrowWithInsurance
.
Example (TypeScript / ethers)
Source code
function borrowWithInsurance(address adapter, bytes calldata data, uint256 amount)
external
nonReentrant
whenNotPaused
onlyValidAmount(amount)
onlySupportedAdapter(adapter)
notBlacklisted(msg.sender)
{
CollateralPosition storage position =
positions[msg.sender][IAssetAdapter(adapter).getPositionKey(data)];
uint256 uninsuredBalance = getUninsuredBalance(adapter, msg.sender, data);
uint256 insuranceFeeAmount = calculateInsuranceFee(adapter, msg.sender, data, amount);
if (IERC20(reserve.reserveAssetAddress).allowance(msg.sender, address(this)) < insuranceFeeAmount)
revert InsufficientAllowanceForFees();
if (IERC20(reserve.reserveAssetAddress).balanceOf(msg.sender) < insuranceFeeAmount)
revert InsufficientBalance();
_borrow(adapter, data, amount);
_collectFee(insuranceFeeAmount, keccak256("INSURANCE_FEE"));
position.isInsured = true;
position.rawInsuredBalance += uninsuredBalance + amount;
emit InsuredAsset(IAssetAdapter(adapter).getAssetToken(), msg.sender, data, uninsuredBalance);
}
getUninsuredBalance
¶
getUninsuredBalance(address adapter, address user, bytes data) → uint256
Summary
Returns the portion of a user’s debt that is not covered by insurance.
Computed as rawDebtBalance - rawInsuredBalance
for the given collateral position.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral |
user | address |
Address of the borrower |
data | bytes |
Adapter-specific calldata identifying the position |
Returns
| Type | Description |
|----------|-----------------------------------|
| uint256 | Uninsured portion of the debt |
Example (TypeScript / ethers)
calculateInsuranceFee
¶
calculateInsuranceFee(address adapter, address user, bytes data, uint256 amount) → uint256
Summary
Computes the insurance fee owed for a user’s position if they borrow with insurance.
The fee covers both the new borrow amount and any previously uninsured balance.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral |
user | address |
Address of the borrower |
data | bytes |
Adapter-specific calldata identifying the position |
amount | uint256 |
Amount of reserve asset intended to be borrowed |
Returns
Type | Description |
---|---|
uint256 | Insurance fee in crvUSD |
Formula
uninsuredBalance
= fromgetUninsuredBalance
insuranceFeeRate
=parameters.insuranceFee
(basis points)
Notes
- If the user borrows without insurance initially, then later decides to insure, they must pay on their entire outstanding debt, not just the new amount.
- Pure view function: no state changes, no events.
Example (TypeScript / ethers)
repay
¶
repay(address adapter, bytes data, uint256 amount)
Summary
Repays the caller’s own debt for a specific collateral position and updates indices, debt balances, insurance status, rates, and liquidity.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral |
data | bytes |
Adapter-specific calldata identifying the position |
amount | uint256 |
Repayment amount (pass type(uint256).max to repay full scaled debt) |
Emits
Repay(address payer, address onBehalfOf, uint256 actualRepayAmount)
Reverts (selected)
InvalidAmount()
—amount == 0
CannotRepayUnderLiquidationWithoutInsurance()
— under liquidation and not insuredGracePeriodExpired()
— liquidation grace window elapsedResidualCannotBeRepaid()
— remaining scaled debt <MIN_BORROW_AMOUNT
but still > 0- ERC20 allowance/balance failures on transfer
- Adapter validation failures
Notes
- To repay the entire outstanding scaled debt and avoid dust, pass
type(uint256).max
asamount
. - After successful repayment,
withdrawAsset
can be called for an ERC-721 adapter to retrieve the NFT. - For ERC-20 adapters, you can continue repaying until your position approaches the borrow threshold; ensure collateralization remains sufficient.
- If a small residual remains below
MIN_BORROW_AMOUNT
, the system reverts; repay in full to avoid this.
Example (TypeScript / ethers)
// Repay a fixed amount
await reserveAsset.approve(lendingPool.target, repayAmt);
await (await lendingPool.repay(adapter, data, repayAmt)).wait();
// Repay in full (no dust)
await reserveAsset.approve(lendingPool.target, maxExpected);
await (await lendingPool.repay(adapter, data, ethers.constants.MaxUint256)).wait();
Source code (public + internal)
function repay(address adapter, bytes calldata data, uint256 amount)
external
nonReentrant
whenNotPaused
onlyValidAmount(amount)
onlySupportedAdapter(adapter)
notBlacklisted(msg.sender)
{
_repay(adapter, data, amount, msg.sender);
}
function _repay(address adapter, bytes calldata data, uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
IAssetAdapter(adapter).validate(onBehalfOf, data);
// Update reserve state before repayment
ReserveLibrary.updateReserveState(reserve, rateData);
CollateralPosition storage position = positions[onBehalfOf][IAssetAdapter(adapter).getPositionKey(data)];
// If the user is under liquidation and position is not insured, they cannot repay.
if (position.isUnderLiquidation && !position.isInsured) revert CannotRepayUnderLiquidationWithoutInsurance();
// If the position is under liquidation and the grace period has expired, the user cannot repay.
if (position.isUnderLiquidation && block.timestamp > position.liquidationStartTime + parameters.liquidationGracePeriod) revert GracePeriodExpired();
// Calculate the position's debt including any accrued interest
uint256 positionScaledDebt = getPositionScaledDebt(adapter, onBehalfOf, data);
// If amount is greater than positionDebt, cap it at positionDebt
uint256 actualRepayAmount = (amount == type(uint256).max) ? positionScaledDebt : amount;
// Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
(, , uint256 normalizedAmountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(
onBehalfOf,
actualRepayAmount,
this.getNormalizedDebt(),
abi.encode(adapter, data)
);
// Update the position index of the user
position.positionIndex = reserve.usageIndex;
// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, actualRepayAmount);
// Update position's debt balance
if (balanceIncrease > 0) {
position.rawDebtBalance += balanceIncrease;
}
position.rawDebtBalance = position.rawDebtBalance - normalizedAmountBurned;
// Update the insurance
if (position.rawInsuredBalance > position.rawDebtBalance) {
position.rawInsuredBalance = position.rawDebtBalance;
}
if (position.rawDebtBalance == 0) {
position.rawInsuredBalance = 0;
position.isInsured = false;
}
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, actualRepayAmount, 0);
// If a residual remains below MIN_BORROW_AMOUNT, revert
if (position.rawDebtBalance > 0 && getPositionScaledDebt(adapter, onBehalfOf, data) < MIN_BORROW_AMOUNT) {
revert ResidualCannotBeRepaid();
}
_rebalanceLiquidity();
emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}
repayOnBehalf
¶
repayOnBehalf(address adapter, bytes data, uint256 amount, address onBehalfOf)
Summary
Third-party repayment of a borrower’s debt for a specific collateral position. Forwards to the internal _repay
flow after performing feature-flag and minimum-amount checks.
Minimum Repayment Requirement
When repaying on behalf of another user, at least 1% of the borrower's scaled debt must be repaid in a single transaction.
Attempting to repay less than 1% will revert with RepaymentBelowMinimumThreshold()
.
To repay the full debt (and avoid dust), pass type(uint256).max
as the amount
parameter.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral position |
data | bytes |
Adapter-specific calldata identifying the position |
amount | uint256 |
Repayment amount (use type(uint256).max to repay full scaled debt; see Notes) |
onBehalfOf | address |
Borrower whose debt is being repaid |
Emits
Repay(address payer, address onBehalfOf, uint256 actualRepayAmount)
(emitted inside _repay
)
Reverts (selected)
PaybackDebtDisabled()
— feature flag disabledAddressCannotBeZero()
—onBehalfOf
is zeroRepaymentBelowMinimumThreshold()
— whenmsg.sender != onBehalfOf
andamount < 1%
of scaled debt- Plus all
_repay
errors (e.g., liquidation/grace-period rules, allowance/balance, residual belowMIN_BORROW_AMOUNT
)
Notes
- To repay the entire outstanding scaled debt and avoid dust, pass
type(uint256).max
asamount
. - When the payer is not the borrower, the repayment must be ≥ 1% of the borrower’s scaled debt (anti-griefing).
- If a small residual remains below
MIN_BORROW_AMOUNT
, the call will revert; repay in full to avoid this. - Ensure the payer has approved and holds enough reserve asset for the transfer.
Example (TypeScript / ethers)
// Third-party repay >= 1% of scaled debt
const scaled = await lendingPool.getPositionScaledDebt(adapter, borrower, data);
const onePercent = scaled.mul(1).div(100);
await reserveAsset.approve(lendingPool.target, onePercent);
await (await lendingPool.repayOnBehalf(adapter, data, onePercent, borrower)).wait();
// Full payoff (no dust)
await reserveAsset.approve(lendingPool.target, maxExpected);
await (await lendingPool.repayOnBehalf(
adapter, data, ethers.constants.MaxUint256, borrower
)).wait();
Source code
function repayOnBehalf(address adapter, bytes calldata data, uint256 amount, address onBehalfOf)
external
nonReentrant
whenNotPaused
onlyValidAmount(amount)
onlySupportedAdapter(adapter)
notBlacklisted(msg.sender)
notBlacklisted(onBehalfOf)
{
if (!parameters.canPaybackDebt) revert PaybackDebtDisabled();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
uint256 positionScaledDebt = getPositionScaledDebt(adapter, onBehalfOf, data);
if (positionScaledDebt != 0 && msg.sender != onBehalfOf) {
uint256 minRepaymentAmount = positionScaledDebt.percentMul(100); // 1%
if (amount < minRepaymentAmount) revert RepaymentBelowMinimumThreshold();
}
_repay(adapter, data, amount, onBehalfOf);
}
Liquidation Management¶
initiateLiquidation
¶
initiateLiquidation(address adapter, address user, bytes data)
Summary
Starts liquidation for a position whose health factor is below the liquidation threshold.
Validates the position, accrues indices, checks HF, then flags the position under liquidation and records the start time.
Note
The contract implementing this function is a proxy and resides in LiquidationProxy.sol
.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral |
user | address |
Position owner |
data | bytes |
Adapter-specific calldata identifying the position |
Emits
- LiquidationInitiated(address indexed sender, address indexed user, address adapter, bytes data)
Reverts (selected)
UserAlreadyUnderLiquidation()
— position already flaggedHealthFactorTooHigh()
— health factor ≥HEALTH_FACTOR_LIQUIDATION_THRESHOLD
- Adapter validation failures
Notes
- Health factor is computed via
calculateHealthFactorProxy
(delegates to LendingPool’scalculateHealthFactor
). - Index accrual occurs before checks via
ReserveLibrary.updateReserveState
.
Example (TypeScript / ethers)
const adapterAddr = "0xAdapter...";
const borrower = "0xBorrower...";
const adapterData = ethers.AbiCoder.defaultAbiCoder().encode(
["uint256"],
[tokenId] // e.g. for an ERC721 collateral adapter
);
const tx = await lendingPool.initiateLiquidation(adapterAddr, borrower, adapterData);
await tx.wait();
Source code
function initiateLiquidation(address adapter, address user, bytes calldata data) external onlyProxy {
bytes32 positionKey = IAssetAdapter(adapter).getPositionKey(data);
CollateralPosition storage position = positions[user][positionKey];
if (position.isUnderLiquidation) revert UserAlreadyUnderLiquidation();
IAssetAdapter(adapter).validate(user, data);
// update state
ReserveLibrary.updateReserveState(reserve, rateData);
uint256 healthFactor = calculateHealthFactorProxy(adapter, user, data);
if (healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD) revert HealthFactorTooHigh();
position.isUnderLiquidation = true;
position.liquidationStartTime = block.timestamp;
emit LiquidationInitiated(msg.sender, user, adapter, data);
}
closeLiquidation
¶
closeLiquidation(address adapter, bytes data)
Summary
Closes an active liquidation for the caller’s position.
Validates the position, enforces grace-period and insurance rules, accrues indices, confirms debt is fully repaid, then clears the liquidation flags and insurance state.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral |
data | bytes |
Adapter-specific calldata identifying the position |
Emits
- LiquidationClosed(address indexed user, address adapter, bytes data)
Reverts (selected)
NotUnderLiquidation()
— position isn’t flagged under liquidationGracePeriodExpired()
— grace period elapsed and debt is still > 0NotInsured()
— attempting to close with nonzero debt and no insuranceDebtNotZero()
— after accrual, outstanding debt is not fully repaid- Adapter validation failures
Notes
- If insured, the borrower must fully repay within
liquidationGracePeriod
to close. - The proxy permits closing after the grace period only when debt is already zero.
- On success, liquidation flags are cleared and insurance is reset.
Example (TypeScript / ethers)
const adapterAddr = "0xAdapter..."; const data = ethers.AbiCoder.defaultAbiCoder().encode(["uint256"], [tokenId]);
const tx = await lendingPool.closeLiquidation(adapterAddr, data); await tx.wait(); ```
Source code
function closeLiquidation(address adapter, bytes calldata data) external onlyProxy {
address userAddress = msg.sender;
bytes32 positionKey = IAssetAdapter(adapter).getPositionKey(data);
CollateralPosition storage position = positions[userAddress][positionKey];
if (!position.isUnderLiquidation) revert NotUnderLiquidation();
IAssetAdapter(adapter).validate(msg.sender, data);
uint256 positionDebt = getPositionScaledDebtProxy(adapter, userAddress, data);
if (block.timestamp > position.liquidationStartTime + parameters.liquidationGracePeriod && positionDebt > 0) {
revert GracePeriodExpired();
}
// Closing is only allowed with insurance if debt is nonzero
if (!position.isInsured && positionDebt > 0) revert NotInsured();
// Accrue indices; after accrual, debt must be zero
ReserveLibrary.updateReserveState(reserve, rateData);
if (positionDebt > 0) revert DebtNotZero();
position.isUnderLiquidation = false;
position.liquidationStartTime = 0;
position.rawInsuredBalance = 0;
position.isInsured = false;
emit LiquidationClosed(userAddress, adapter, data);
}
finalizeLiquidation
¶
finalizeLiquidation(address adapter, address user, bytes data)
Summary
Completes the liquidation process by transferring collateral to the Stability Pool and burning the user’s debt.
Enforces that the grace period has expired (for insured positions) and ensures there is still outstanding debt.
Restricted Access
This method is only callable by the Stability Pool through the proxy when a position must be liquidated.
If the debt is already zero, the liquidation cannot be finalized here. In that case, the borrower (owner) should close the liquidation instead.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter for the collateral |
user | address |
The borrower whose position is being liquidated |
data | bytes |
Adapter-specific calldata identifying the position |
Emits
- LiquidationFinalized(address stabilityPool, address user, address adapter, bytes data, uint256 debtCleared, uint256 collateralValue)
Reverts (selected)
NotUnderLiquidation()
— position isn’t marked for liquidationGracePeriodNotExpired()
— insured position still within grace period- Adapter validation failures
- Stability Pool checks debt > 0 before execution
Notes
- Debt must exist for liquidation to be finalized.
- After execution, the user’s collateral is transferred to the Stability Pool and the debt is fully cleared.
- Insurance flags and balances are reset to zero.
Example (TypeScript / ethers)
Source code
function finalizeLiquidation(address adapter, address user, bytes calldata data) external onlyProxy {
bytes32 positionKey = IAssetAdapter(adapter).getPositionKey(data);
CollateralPosition storage position = positions[user][positionKey];
if (!position.isUnderLiquidation) revert NotUnderLiquidation();
IAssetAdapter(adapter).validate(user, data);
// update state
ReserveLibrary.updateReserveState(reserve, rateData);
if (position.isInsured && block.timestamp <= position.liquidationStartTime + parameters.liquidationGracePeriod) {
revert GracePeriodNotExpired();
}
uint256 positionDebt = getPositionScaledDebtProxy(adapter, user, data);
position.isUnderLiquidation = false;
position.liquidationStartTime = 0;
position.isInsured = false;
IAssetAdapter(adapter).transferAsset(user, data, stabilityPool);
IDebtToken(reserve.reserveDebtTokenAddress).burn(
user,
positionDebt,
reserve.usageIndex,
abi.encode(adapter, data)
);
position.positionIndex = reserve.usageIndex;
position.rawDebtBalance = 0;
position.rawInsuredBalance = 0;
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, positionDebt, 0);
emit LiquidationFinalized(
stabilityPool,
user,
adapter,
data,
positionDebt,
IAssetAdapter(adapter).getAssetValue(user, data)
);
}
Positions¶
calculateHealthFactor
¶
calculateHealthFactor(address adapter, address user, bytes data) → uint256
Summary
Returns the position’s health factor (RAY). Uses current collateral value and scaled debt with the protocol’s liquidationThreshold
.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter |
user | address |
Position owner |
data | bytes |
Adapter-specific identifier |
Returns
uint256
— Health factor (RAY, 1e27). If scaled debt is negligible, returns WadRayMath.RAY
.
Notes
- HF ≥
1e18
implies safe; HF <1e18
implies liquidatable per thresholding logic (exact threshold governed by parameters). - Collateral value from
IAssetAdapter.getAssetValue(user, data)
.
getUserCollateralValue
¶
getUserCollateralValue(address user) → uint256
Summary
Sums the user’s total collateral value across all registered adapters.
Parameters
Name | Type | Description |
---|---|---|
user | address |
User address |
Returns
uint256
— Total collateral value.
Notes
- Iterates
adapters[]
and callsIAssetAdapter(adapter).getTotalAssetValue(user)
per adapter.
getPositionDebt
¶
getPositionDebt(address adapter, address user, bytes data) → uint256
Summary
Returns raw debt for a position without interest accrual (stored balance).
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter |
user | address |
Position owner |
data | bytes |
Position identifier |
Returns
uint256
— Raw debt balance.
Notes
- For interest-inclusive debt, use
getPositionScaledDebt
.
getPositionScaledDebt
¶
getPositionScaledDebt(address adapter, address user, bytes data) → uint256
Summary
Returns scaled debt (includes interest via index) for a specific position.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter |
user | address |
Position owner |
data | bytes |
Position identifier |
Returns
uint256
— Scaled debt (interest-aware).
Notes
- Internally derives from
_positionScaledDebt(position)
.
getPositionsScaledDebt
¶
getPositionsScaledDebt(address user) → uint256
Summary
Aggregates the user’s total scaled debt across all positions on all adapters.
Parameters
Name | Type | Description |
---|---|---|
user | address |
User address |
Returns
uint256
— Total scaled debt.
Notes
- Iterates
adapters[]
, fetchesgetPositionKeys(user)
, sums_positionScaledDebt
for each key.
getPosition
¶
getPosition(address adapter, address user, bytes data) → CollateralPosition
Summary
Returns the stored CollateralPosition
struct for the specified position.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter |
user | address |
Position owner |
data | bytes |
Position identifier |
Returns
CollateralPosition
— Current stored fields for the position.
Notes
- Keyed by
positions[user][IAssetAdapter(adapter).getPositionKey(data)]
.
getPositionData
¶
getPositionData(address adapter, address user, bytes data) → (uint256 positionIndex, uint256 rawDebtBalance)
Summary
Returns a compact view of two key fields of a position.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter |
user | address |
Position owner |
data | bytes |
Position identifier |
Returns
uint256 positionIndex
uint256 rawDebtBalance
Notes
- For a fuller struct, use
getPosition
orgetPositionView
.
getPositionView¶
getPositionView(address adapter, address user, bytes data) → CollateralPositionView
Summary
Returns a synthesized view struct for a position, including flags, balances, indices, scaled debt, health factor, adapter and asset addresses.
Parameters
Name | Type | Description |
---|---|---|
adapter | address |
Registered IAssetAdapter |
user | address |
Position owner |
data | bytes |
Position identifier |
Returns
CollateralPositionView
with:
isUnderLiquidation
,liquidationStartTime
,rawDebtBalance
,isInsured
scaledDebtBalance
(viagetPositionScaledDebt
)healthFactor
(viacalculateHealthFactor
)positionIndex
,adapter
,asset
Notes
- Useful for UIs needing a single call to render the full state.
Indexes¶
getNormalizedIncome
¶
getNormalizedIncome() → uint256
Summary
Returns the reserve’s normalized income (liquidity index).
Returns
uint256
— Liquidity index.
Notes
- Read-only wrapper over
ReserveLibrary.getLiquidityIndex(reserve, rateData)
.
getNormalizedDebt
¶
getNormalizedDebt() → uint256
Summary
Returns the reserve’s normalized debt (usage index).
Returns
uint256
— Usage index.
Notes
- Read-only wrapper over
ReserveLibrary.getUsageIndex(reserve, rateData)
.
getReserveState
¶
getReserveState() → ReserveData
Summary
Returns the reserve’s data that includes the indexes and usages.
Returns
ReserveData
with:
reserveRTokenAddress
,reserveAssetAddress
,reserveDebtTokenAddress
totalLiquidity
,totalUsage
,liquidityIndex
,usageIndex
lastUpdateTimestamp
,pendingProtocolFeeAmount
Important: Correct Source for Total Usage (Total Debt)
To have an up-to-date value for total usage (i.e., the total debt in the system including accrued interest), do not use reserve.totalUsage
.
Instead, use DebtToken.totalSupply
.
The reserve.totalUsage
field only reflects the total supply of the debt token in circulation at the last update and does not include accrued interest since then.