💻Technical
Technical Specifications
Last updated
Technical Specifications
Last updated
Borrowers and lenders can deposit and withdraw into the LiquidityWarehouse
contract by calling the relevant functions.
Borrowers can call depositBorrower
to deposit assets and withdrawBorrower
to withdraw assets
Lenders can call depositLender
to deposit assets and withdrawLender
to withdraw assets.
An automated Chainlink Automation contract named DebtCovenant
monitors the net asset values of both the borrower and lender deposits. This contract can call deactivate
to deactivate the LiquidityWarehouse
if the liquidation threshold has been breached as well as activate
to reactivate the LiquidityWarehouse
if the liquidation threshold is fulfilled. A full description on how the liquidation threshold is calculated is outlined here.
Assets in the LiquidityWarehouse
are periodically deployed to the borrower’s smart contracts. These assets are withdrawn to the LiquidityWarehouse
in two cases namely
When a lender calls withdrawLender
and there is insufficient liquidity in the LiquidityWarehouse
When deactivate
is called to automatically withdraw all deployed assets from the borrower’s smart contracts.
The exact terms of the LiquidityWarehouse
are stored in the Terms
struct.
These parameters can be updated by the DEFAULT_ADMIN
function by calling the relevant functions.
The total NAV (Net Asset Value) of a LiquidityWarehouse
comprises of the NAV of reserve liquidity on the Liquidity Warehouse as well as NAV from funds deployed to Whitelisted Target pools.
For accounting purpose, total NAV can also be expressed as summation of lenders NAV and borrowers NAV.
To ensure that lenders have seniority over borrower for the fixed interest, lender’s NAV is whichever is larger between total NAV and lenders balance, where lenders balance is the amount theoretically owed to the lenders compounded by fixed interest.
This also means, to be consistent with the above formula, borrowers NAV is the leftover of total NAV subtracted by the lenders balance with 0 as the floor.
Both lenders NAV and borrowers NAV have corresponding LP tokens representing their share of the NAV. These LP tokens are minted and burned as depositors deposit/withdraw from the LiquidityWarehouse
. The value of each of these LP tokens is given by dividing their respective total NAV values and the total LP token supply.
The deployed net asset value is calculated by iterating through the list of s_withdrawTargets
and determining the NAV for that target. The logic to calculate the NAV for each target is implemented in the _getDeployedAssetValue
function of the borrower specific LiquidityWarehouse
implementation.
The mechanism for updating the list of s_withdrawTargets
is outlined in the Access Control section below.
There are two thresholds that relates to the ratio between borrowers NAV and total NAV:
Capacity threshold: prevents deposits by lender and withdrawal by borrower if the resulting ratio between borrowers NAV and total NAV would be below the threshold
Liquidation threshold: allows for liquidation if the ratio between borrowers NAV and total NAV is below the threshold
LiquidationThreshold is enforced to always be lower then capacityThreshold
These thresholds can be configured by the contract admin by calling setLiquidationThreshold
and setCapacityThreshold
A LiquidityWarehouse
can be deactivated anytime the liquidationThreshold
is breached by calling deactivate
and reactivated by calling activate
. Both of these functions are not access controlled, which means that any address is allowed to call it whenever the correct conditions are met.
The liquidationThreshold
is considered to be breached whenever the following formula is true
What CAN be done when the LiquidationWarehouse
is ACTIVE
Borrowers can deposit and withdraw by calling depositBorrower
and withdrawBorrower
Lenders can deposit and withdraw by calling depositLender
and withdrawLender
Interest owed to the lenders continues to compound
What CANNOT be done when the LiquidationWarehouse
is ACTIVE
Borrowers cannot withdraw an amount that will breach the capacityThreshold
Addresses cannot call liquidate
to withdraw all deployed assets from whitelisted targets
What CAN be done when the LiquidationWarehouse
is ACTIVE
The contract admin can continue to call execute
to perform operational tasks
What CANNOT be done when the LiquidationWarehouse
is INACTIVE
Borrowers CANNOT deposit by calling depositBorrower
Interest stops accruing
Permissioned actors CANNOT execute actions by calling execute
Liquidity deployed to the whitelisted targets can be withdrawn by either
Passing in an array of whitelisted targets in the deactivate
function to deactivate and withdraw in the same transaction.
Passing in an array of whitelisted targets in the liquidate
function. This function exists in case the call to deactivate
could not withdraw the full deployed amount from all the whitelisted targets.
Internally both of these functions will loop through the list of whitelisted targets and try to withdraw as much liquidity as it can from each of the addresses passed in. The exact mechanism to withdraw funds will be implemented by the borrower specific implementation of the LiquidityWarehouse
in the _withdrawFromTarget
function.
DebtCovenant
In addition to the LiquidationWarehouse
, Copra will also implement a DebtCovenant
contract using Chainlink Automation to monitor the LiquidationWarehouse
's liquidation ratio and automatically call activate
or deactivate
when the liquidation threshold is breached.
Chainlink Automation compatible contracts require that the implementation contract implements a checkUpkeep
function to check whether or not an action needs to be performed and a performUpkeep
function to execute the required transactions.
The checkUpkeep
function will check to see if the liquidation threshold has been breached and determine whether or not a LiquidityWarehouse
needs to be activated/deactivated. In addition to this it will also determine a list of whitelisted target addresses that the LiquidityWarehouse
needs to withdraw liquidity from when deactivating.
The performUpkeep
function will take in the data returned from checkUpkeep
and call the relevant functions on the LiquidityWarehouse
.
Lenders and borrowers can both deposit into the LiquidityWarehouse
to earn a fixed yield on their assets. In order to start the LiquidityWarehouse
borrowers must first deposit an amount by calling depositBorrower
to deposit some assets into the LiquidityWarehouse
. Lenders can then call depositLender
to deposit assets as long as the ratio between the borrower’s NAV and lender’s NAV is not greater than the capacityThreshold
.
LiquidityWarehouse
has a capacityThreshold
of 10%
Borrower has deposited 10 USDC
Lender’s can deposit up to a maximum of 90 USDC
In return for depositing assets into the LiquidityWarehouse
, lenders and borrowers are both minted LP tokens that represent their share of their respective balances. These LP tokens both exist within the LiquidityWarehouse
contract, which implements ERC1155 in order to support multiple tokens. The lender’s LP token is reserved at ID 0 and the borrower’s LP token is reserved at ID 1. The amount of LP tokens minted for both lenders and borrowers are shown below.
Depositors can withdraw from the LiquidityWarehouse
by either calling withdrawLender
or withdrawBorrower
depending on whether they had deposited into the lender or borrower pools. Both of these functions takes in an amount
parameter, which represents the amount of shares that the depositor wishes to burn in exchange for receiving withdrawn assets. The amount of withdrawable assets are given below.
In addition to taking in an amount
parameter, the withdrawer may also specify a list of whitelistedTargets
to withdraw assets from whitelisted targets from if the amount of assets in the LiquidityWarehouse
is not enough to cover the withdrawable amount. The exact mechanism to facilitate withdrawals is left to the borrower specific implementation contract to implement in the _withdrawFromTarget
function. This function
The LiquidityWarehouse
contract can be externally controlled to call functions on another contract particularly for the purpose of deployments (and withdrawals) of funds from the LiquidityWarehouse
to whitelisted targets.
Callers can be whitelisted to perform certain function calls on specific target addresses by calling the toggleWhitelist
function, which takes in an array of LiquidityWarehouseAccessControl.Action
structs.
The whitelisted function calls, targets and callers are hashed together using keccak256
to generate an action ID, which are then mapped to a boolean
value to represent whether or not the call is whitelisted.
The LiquidtyWarehouse
needs to keep track of the list of whitelisted target addresses to calculate NAV and to withdraw liquidity from when the liquidity warehouse is deactivated. This list is stored as an EnumerablAddressSet
in the s_withdrawTargets
storage variable. Addresses can be added to this set by passing in an Action
struct with isWithdraw
and isWhitelisted
set to true. Conversely the address can be removed from s_withdrawTargets
by setting isWithdraw
to true and isWhitelisted
to false.
Whitelisted callers and the contract’s DEFAULT_ADMIN
can call execute
to perform any operational transactions as the LiquidityWarehouse
. Example operational transactions include
Calling approve
on an ERC20 token contract to approve an address to spend the assets in the LiquidityWarehouse
Calling functions on the borrower’s protocol to deploy assets from the LiquidityWarehouse
The execute
function takes in an array of ExecuteAction
structs
For each ExecuteAction
, the execute
function will first regenerate an actionId
using msg.sender
, [executeAction.target](<http://executeAction.target>)
and the function signature being called derived from bytes4(executeAction.data)
. The generated actionId
will then be compared with the boolean
entry in s_whitelistedActions
to determine whether or not the caller has been whitelisted to perform the given action. This step is skipped if the caller is the DEFAULT_ADMIN
.
There are two protocol fees
Interest fees: a percentage of interest amount added to lenders balance, practically implemented by minting appropriate amount of lender LP token to protocol treasury address
Withdrawal fees: a percentage of amount withdrawn by lender or borrower, practically implemented by transferring appropriate amount of lender or borrower LP token to protocol treasury address and subtracting withdrawal fees from total withdrawal amount
There are two privileged roles
PAUSER_ROLE
: this will be owned by a 2 out of 3 multisig contract, and has the ability to pause (and unpause) the Liquidity Warehouse contract
when the Liquidity Warehouse is paused, both lender and borrower deposit functions i.e depositLender
and depositBorrower
cannot be called
DEFAULT_ADMIN_ROLE
: The DEFAULT_ADMIN
will be able to update the terms of the LiquidityWarehouse
and interact with the underlying borrower protocol to deploy and withdraw assets. This introduces a degree of centralization risk as a malicious DEFAULT_ADMIN
will be able to steal user funds. In order to mitigate this risk, all LiquidityWarehouses
will be owned by a Timelock contract so that there is a delay period before sensitive transactions are executed. A Gnosis Multisig requiring 2 out of 3 signer addresses will be granted the PROPOSER
, EXECUTOR
and CANCELLER
roles on the Timelock contract so that the the multisig can propose, execute or cancel operations
List of functions callable by the DEFAULT_ADMIN
Action | Function Name |
---|---|
Updating the fixed interest rate
setInterestRate
Updating the liquidation threshold
setLiquidationThreshold
Updating the capacity threshold
setCapacityThreshold
Updating the interest fee
setInterestFee
Updating the withdrawal fee
setWithdrawalFee
Updating the fee recipient address
setFeeRecipient
Updating the list of whitelisted functions
toggleWhitelist
Call functions on behalf of the LiquidityWarehouse. Examples of this include deploying assets to borrower protocols and withdrawing. This action is centralized to the DEFAULT_ADMIN
but is mitigated by the fact that the Liquidity Warehouses will be owned by a Timelock contract so that lenders and borrowers have sufficient time to react to malicious activity
execute