Search
K

Pooled Credit Lines

Pooled credit lines allow a single borrower to raise capital from multiple lenders in the form of a credit line. Pooled credit lines combine elements of credit lines and pools to create more generalized debt offerings.

Purpose

Functions of pooled credit lines have been spread across two contracts: PooledCreditLine.sol and LenderPool.sol. This divide has been roughly to ensure PooledCreditLine only deals with functions pertinent to the borrower, while LenderPool contains all the functions related to the lenders.
Wherever necessary, executing a function in one contract (PooledCreditLine/LenderPool) will trigger an accompanying function in the other contract to change the state of some of the variables stored in LenderPool.sol
Here's an example lifecycle of a pooled credit line:
Pooled credit line example
For a general overview of pooled credit lines please visit Pooled Credit Lines.

Functionality

Creating a pooled credit line request

The first step towards creating a pooled credit line request is calling the request() function in PooledCreditLine.sol. The function checks if the arguments supplied pass necessary checks. This function further calls an internal function (createRequest()) to store the parameters of the raise, and another function (_notifyRequest()) to pass on the details of the loan to the LenderPool contract.

Function

function request(Request calldata _request)
external returns (uint256)

Restrictions

  • Borrower must be verified by the borrowerVerifier selected by them
  • Arguments of the loan must pass range checks in the contract

Arguments

  • _request: A struct containing all the parameters necessary to create the loan request

Starting the pooled credit line

For a pooled credit line to go active, either:
  • the total amount lent must be equal to the borrowLimit
  • at the end of the collection period, the total amount lent (after subtracting the starting fee) must be greater than or equal to the minBorrowAmount
In the first case, the pooled credit line status is changed to the ACTIVE stage in the lend() call that leads to the total amount meeting borrowLimit. In the second case, the start() function needs to be called separately after the end of the collection period for the pool to turn to ACTIVE stage.

Relevant functions

1. LenderPool.start()

function start(uint256 _id, address _to) external nonReentrant

Restrictions

  • Can only be called after the end of the collection period

Arguments

  • _id: identifier of the pooled credit line for which start() is being called
  • _to: receiver address for the start fee

2. PooledCreditLine.accept()

function accept(uint256 _id, uint256 _amount)
external override onlyLenderPool

Restrictions

  • Can only be called by LenderPool contract

Arguments

  • _id: identifier of the pooled credit line which is being accepted
  • _amount: final amount of borrowAsset that will be available for the borrower to borrow

Supplying liquidity into a pooled credit line

Lenders can supply liquidity into a pooled credit line request by calling the lend() function. This can only be done during the collection stage, and requires the lender to be verified by the lenderVerifier chosen by the borrower.
function lend(uint256 _id, uint256 _amount)
external nonReentrant

Arguments

  • _id: identifier of the pooled credit line which is being accepted
  • _amount: amount that the lender wishes to lend

Cancelling a pooled credit line

A pooled credit line can be cancelled by the borrower as long as it is still in the collection stage.

Relevant Functions

1. PooledCreditLine.cancelRequest

function cancelRequest(uint256 _id) external onlyCreditLineBorrower(_id)

Restrictions

  • Requires the pooled credit line to be in the REQUESTED state
  • Requires that the pooled credit line still be in the collection stage
  • Can only be called by the borrower of the pooled credit line

Arguments

  • _id: identifier of the credit line to be cancelled

2. LenderPool.requestCancelled

The requestCancelled function is called by cancelRequest function to delete the startTime variable so that lending into the pooled credit line is effectively paused. Existing lenders can then start withdrawing their deposits since the pooled credit line status is changed to CANCELLED.
function requestCancelled(uint256 _id) external onlyPooledCreditLine

Restrictions

  • Can only be called by the PooledCreditLine contract

Arguments

  • _id: identifier for the pooled credit line

Borrowing from pooled credit line

The borrower of the pooled credit line can begin borrowing once the status is changed to ACTIVE and the current timestamp has passed the start time of the loan. Borrowing a given amount requires the borrower to have sufficient collateral such that their new collateral ratio (after the amount is borrowed) must be greater than or equal to the loan's ideal collateral ratio. In case the above condition does not hold true the operation would fail
function borrow(uint256 _id, uint256 _amount)
external nonReentrant onlyCreditLineBorrower(_id)

Restrictions

  • Only the borrower of the pooled credit line can call this function
  • The pooled credit line must be in the ACTIVE state
  • The timestamp must be grater than or equal to the start time of the loan

Arguments

  • _id: identifier of the pooled credit line
  • _amount: amount that the borrower wishes to borrow

Repaying debt

The borrower can start repaying their debt when they wish to. A fixed repayment schedule has not be implemented in the contracts to allow for sufficient flexibility in case the borrower cannot make a timely repayment for some reason. Typically we would expect borrowers to come up with a soft repayment schedule with the lenders off-chain which would include conditions for late repayments. While this allows for sufficient flexibility, failure to stick to this repayment schedule would hurt the borrower's future borrowing prospects, thus incentivizing borrowers to maintain a healthy repayment schedule.
Note that borrowers only accrue interests on the principal they're actively borrowing, not the entire pool. This is because any unused capital is redeployed onto the borrowAssetStrategy to earn passive yield. Repayments first count towards repayment of any accrued interest. Repayments count towards repayment of the principal only once all interests are paid.
While there are no fixed repayment schedules, it is enforced that all debt must be repaid by the end of credit line. Failure to do so puts the borrower in a grace period, during which they continue to accrue interest, at an interest rate equal to borrowRate + gracePenaltyRate .

Relevant Functions

1. PooledCreditLine.repay

function repay(uint256 _id, uint256 _amount)
external nonReentrant

Arguments

  • _id: identifier for the pooled credit line for which amount is being repaid
  • _amount: amount that the msg.sender wishes to repay

2. LenderPool.repaid

Function called by PooledCreditLine.repay() to update relevant state vars in the LenderPool contract
function repaid(
uint256 _id,
uint256 _sharesRepaid,
uint256 _interestShares
) external onlyPooledCreditLine nonReentrant

Restrictions

  • Can only be called by the PooledCreditLine contract

Arguments

  • _id: identifier for the pooled credit line for which amount is being repaid
  • _amount: amount that was repaid in terms of the LP tokens of the savings strategy chosen
  • _interestShares: Number of shares that counted towards repaying the interest

Withdrawing interest

Lenders have the power to withdraw interests throughout the period of the loan. Interests are generated by two sources:
  • Borrower repays interest accrued
  • Deposits on the yield strategy (borrowAssetStrategy) earns interest
Note that interests repaid by borrower are discrete in time, while deposits in the borrowAssetStrategy earn interest continuously.
function withdrawInterest(uint256 _id, address _lender)
external nonReentrant

Arguments

  • _id: identifier for the pooled credit line
  • _lender: address of lender whose interests are being withdrawn

Withdrawing principal as lender

This function is used to remove the entire debt accumulated in the contract
function withdrawLiquidity(uint256 _id) external nonReentrant

Arguments

  • _id: identifier for the pooled credit line

Adding collateral

Sufficient collateral must be deposited for the borrow() to be active
function depositCollateral(
uint256 _id,
uint256 _amount,
bool _fromSavingsAccount
) external nonReentrant

Arguments:

  • `_id`: identifier for the pooled credit line
  • _amount: the amount that the borrower wants to supply into the pooled credit line
  • _fromtSavingsAccount: if true, funds are transferred from the msg.sender's savings account. If false, tokens are directly transferred from the user's wallet accrea

Withdrawing collateral

Collateral can be withdrawn by the borrower as long as the new collateral ratio (collateral ratio if _amount is withdrawn), is greater than or equal to the ideal collateral ratio.
Note that withdawCollateral() does not allow for lenders to withdraw excess collateral during the EXPIRED period.

Funcitions

function withdrawCollateral(
uint256 _id,
uint256 _amount,
bool _toSavingsAccount
) external nonReentrant onlyCreditLineBorrower(_id)

Restrictions

  • Can only be called by the poolcreditline borrower

Arguments

  • _id: identifier for the pooled credit line
  • _amount: amount of collateral that the msg.sender wants to withdaw
  • _to_SavingsAccount: if true, funds are transferred to the borrower's SavingsAccount account. If false, funds are directly transferred to the lender's wallet

2. PooledCreditLine.withdawCollateral

The below withdrawCollateral() function is fairly similar to the previous one, except it transfers the entire collateral that the msg.sender can withdraw rather than a part of it.
function withdrawCollateral(uint256 _id, bool _toSavingsAccount)
external nonReentrant onlyCreditLineBorrower(_id)

Restrictions

  • Only pooled credit line borrower can call

Arguments

  • _id: identifier of the pooled ctredit line
  • _to_SavingsAccount: if true, funds are transferred to the borrower's SavingsAccount account. If false, funds are directly transferred to the lender's wallet

Liquidating a pooled credit line

Liquidation can occur under two conditions:
  • Borrower fails to repay debt by the end of the grace period
  • The credit line's collateral ratio falls below the ideal collateral ratio

Relevant Functions

1. LenderPool.liquidate

function liquidate(uint256 _id, bool _withdraw) external nonReentrant

Restrictions

  • msg.sender must be a lender in the credit line

Arguments

  • _id: identifier of the pooled credit line
  • _withdraw: if true, the collateral amount that can be repossessed by the msg.sender is transferred to them. If false, they can claim it separately by calling withdrawLiquidation

2. PooledCreditLine.liquidate

Performs the checks necessary to ensure that the credit line can actually be liquidated. It also transfer the collateral to the LenderPool contract so that lenders can start withdrawing their share of the collateral.
function liquidate(uint256 _id)
external override nonReentrant
onlyLenderPool returns (address, uint256)

Restrictions

  • Can only be called by the LenderPool contract

Arguments

  • _id: Pooled credit line identifier

Closing a pooled credit line

The borrower of the pooled credit line can close a loan at any time (given, the loan is ACTIVE) as long as the debt is 0. This is checked by ensuring that the active principal being borrowed is 0 (this is because principal is reduced only once all interests are repaid).

Function

function close(uint256 _id) external onlyCreditLineBorrower(_id)

Restrictions

  • Only the borrower of the pooled credit line can call this function

Arguments

  • _id: identifier of the pooled credit line

Error codes

Error code
Error message
Function/modifier name
OCLB1
Only pooled credit line borrower can call this function
onlyCreditLineBorrower
OLP1
Only LenderPool contract can call this function
onlyLenderPool
ILB1
Borrow limit is out of range
_limitBorrowedInUSD
ILB2
Minimum borrow amount should be less than borrow limit
_limitBorrowedInUSD
ILB3
Minimum borrow amount should be greater than/equal to minimum borrow limit
_limitBorrowedInUSD
UBLL1
min should be less than max or one of the limits must be 0
updateBorrowLimitLimits
UBLL2
New limits must be different from existing limits
updateBorrowLimitLimits
UICRL1
min should be less than max or one of the limits must be 0
updateIdealCollateralRatioLimits
UICRL2
New limits must be different from existing limits
updateIdealCollateralRatioLimits
UBRL1
min should be less than max or one of the limits must be 0
updateBorrowRateLimits
UBRL2
New limits must be different from existing limits
updateBorrowRateLimits
UCPL1
min should be less than max or one of the limits must be 0
updateCollectionPeriodLimits
UCPL2
New limits must be different from existing limits
updateCollectionPeriodLimits
UDL1
min should be less than max or one of the limits must be 0
updateDurationLimits
UDL2
New limits must be different from existing limits
updateDurationLimits
UDGPL1
min should be less than max or one of the limits must be 0
updateDefaultGracePeriodLimits
UDGPL2
New limits must be different from existing limits
updateDefaultGracePeriodLimits
UGPRL1
min should be less than max or one of the limits must be 0
updateGracePenaltyRateLimits
UGPRL2
New limits must be different from existing limits
updateGracePenaltyRateLimits
UPO1
New implementation cannot match the existing one
updatePriceOracle
IUPO1
Implementation address cannot be 0
_updatePriceOracle
USA1
New implementation cannot match the existing one
updateSavingsAccount
IUSA1
Implementation address cannot be 0
_updateSavingsAccount
UPFF1
New value cannot match existing value
updateProtocolFeeFraction
IUPFF1
Value cannot be greater than 1 (SCALING_FACTOR)
_updateProtocolFeeFraction
UPFC1
New implementation cannot match the existing one
updateProtocolFeeCollector
IUPFC1
Implementation address cannot be 0
_updateProtocolFeeCollector
USR1
New implementation cannot match the existing one
updateStrategyRegistry
IUSR1
Implementation address cannot be 0
_updateStrategyRegistry
UV1
New implementation cannot match the existing one
updateVerification
IUV1
Implementation address cannot be 0
_updateVerification
R1
Borrower is not verified by borrowerVerifier
request
R2
borrowAsset and collateralAsset cannot be the same
request
R3
Price feed for the chosen borrowAsset-collateralAsset pair does not exist
request
R4
borrowAsset and collateralAsset cannot be address(0)
request
R5
borrowAssetStrategy is not a valid strategy
request
R6
collateralAssetStrategy is not a valid strategy
request
R8
borrowRate is not within range
request
R9
collateralRatio is not within range
request
R10
collectionPeriod is not within range
request
R11
duration is not within range
request
R12
defaultGracePeriod is not within range
request
R13
gracePenaltyRate is not within range
request
R14
lenderVerifier is not valid
request
A1
status should be in REQUESTED state
accept
DC1
status should in ACTIVE or EXPIRED state
depositCollateral
WC1
withdrawal amount requested must be less than or equal to withdrawable amount
withdrawCollateral
B1
requested borrow amount cannot be 0
_borrow
B2
cannot borrow before starting time of the credit line
_borrow
B3
amount request must be less than/equal to max possible amount.
_borrow
R1
status should in ACTIVE or EXPIRED state
repay
R2
nothing to repay
repay
L1
nothing to liquidate
liquidate
L2
status should in ACTIVE or EXPIRED state
liquidate
C1
status should in ACTIVE
close
C2
actively borrowed principal must be 0
close
CR1
status must be REQUESTED
cancelRequest
CR2
current timestamp must be less than the starting timestamp
cancelRequest
CRLC1
status must be REQUESTED
cancelRequestOnLowCollection
CRLC2
current timestamp must be greater than or equal to the starting timestamp
cancelRequestOnLowCollection