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.
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
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 request(Request calldata _request)
external returns (uint256)
- Borrower must be verified by the borrowerVerifier selected by them
- Arguments of the loan must pass range checks in the contract
_request
: A struct containing all the parameters necessary to create the loan request
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.function start(uint256 _id, address _to) external nonReentrant
- Can only be called after the end of the collection period
_id
: identifier of the pooled credit line for whichstart()
is being called_to
: receiver address for the start fee
function accept(uint256 _id, uint256 _amount)
external override onlyLenderPool
- Can only be called by LenderPool contract
_id
: identifier of the pooled credit line which is being accepted_amount
: final amount ofborrowAsset
that will be available for the borrower to borrow
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
_id
: identifier of the pooled credit line which is being accepted_amount
: amount that the lender wishes to lend
A pooled credit line can be cancelled by the borrower as long as it is still in the collection stage.
function cancelRequest(uint256 _id) external onlyCreditLineBorrower(_id)
- 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
_id
: identifier of the credit line to be cancelled
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
- Can only be called by the PooledCreditLine contract
_id
: identifier for the 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)
- 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
_id
: identifier of the pooled credit line_amount
: amount that the borrower wishes to borrow
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
.function repay(uint256 _id, uint256 _amount)
external nonReentrant
_id
: identifier for the pooled credit line for which amount is being repaid_amount
: amount that the msg.sender wishes to repay
Function called by
PooledCreditLine.repay()
to update relevant state vars in the LenderPool contractfunction repaid(
uint256 _id,
uint256 _sharesRepaid,
uint256 _interestShares
) external onlyPooledCreditLine nonReentrant
- Can only be called by the PooledCreditLine contract
_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
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
_id
: identifier for the pooled credit line_lender
: address of lender whose interests are being withdrawn
This function is used to remove the entire debt accumulated in the contract
function withdrawLiquidity(uint256 _id) external nonReentrant
_id
: identifier for the pooled credit line
Sufficient collateral must be deposited for the borrow() to be active
function depositCollateral(
uint256 _id,
uint256 _amount,
bool _fromSavingsAccount
) external nonReentrant
`_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
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.
function withdrawCollateral(
uint256 _id,
uint256 _amount,
bool _toSavingsAccount
) external nonReentrant onlyCreditLineBorrower(_id)
- Can only be called by the poolcreditline borrower
_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
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)
- Only pooled credit line borrower can call
_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
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
function liquidate(uint256 _id, bool _withdraw) external nonReentrant
- msg.sender must be a lender in the credit line
_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 callingwithdrawLiquidation
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)
- Can only be called by the LenderPool contract
_id
: Pooled credit line identifier
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 close(uint256 _id) external onlyCreditLineBorrower(_id)
- Only the borrower of the pooled credit line can call this function
_id
: identifier of the pooled credit line
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 |
Last modified 1yr ago