# Technical

## Overview

<figure><img src="https://3251301222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7IUb6hmEj08b42Hg3Nry%2Fuploads%2FDufVhGnTvuIOCZLQc7xW%2FScreenshot%202024-03-18%20at%203.14.37%E2%80%AFPM.png?alt=media&#x26;token=73beaf63-127a-4f7a-b331-c4f58de98a52" alt=""><figcaption></figcaption></figure>

1. Borrowers and lenders can deposit and withdraw into the `LiquidityWarehouse` contract by calling the relevant functions.
   1. Borrowers can call `depositBorrower` to deposit assets and `withdrawBorrower` to withdraw assets
   2. Lenders can call `depositLender` to deposit assets and `withdrawLender` to withdraw assets.
2. 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](https://www.notion.so/Tech-Docs-75c06bc77ee54b0389ef6f51b102c995?pvs=21).
3. Assets in the `LiquidityWarehouse` are periodically deployed to the borrower’s smart contracts. These assets are withdrawn to the `LiquidityWarehouse` in two cases namely
   1. When a lender calls `withdrawLender` and there is insufficient liquidity in the `LiquidityWarehouse`
   2. When `deactivate` is called to automatically withdraw all deployed assets from the borrower’s smart contracts.

## Terms

The exact terms of the `LiquidityWarehouse` are stored in the `Terms` struct.

```jsx
 struct Terms {
      /// @notice The asset the loans in the liquidity
      /// warehouse is denominated in
      IERC20 asset;
      /// @notice The address that will receive fees
      address feeRecipient;
      /// @notice The liquidation threshold of the liquidity warehouse
      uint64 liquidationThreshold;
      /// @notice The capacity threshold of the liquidity warehouse
      uint64 capacityThreshold;
      /// @notice The interest rate of the liquidity warehouse
      uint64 interestRate;
      /// @notice The fee taken from compounded interest of the liquidity warehouse
      uint64 interestFee;
      /// @notice The fee taken from withdrawals
      uint64 withdrawalFee;
  }
```

These parameters can be updated by the `DEFAULT_ADMIN` function by calling the [relevant functions](#roles).

## Net Asset Value

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.

$$
totalNAV = reserveNAV + deployedNAV
$$

For accounting purpose, total NAV can also be expressed as summation of lenders NAV and borrowers NAV.

$$
totalNAV = lenderNAV + borrower 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.

$$
lenderNAV = min(totalNAV, lenderBalance)
$$

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.

$$
borrowerNAV = max(totalNAV - lenderBalance, 0)
$$

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.

$$
lenderLPTokenValue = \frac{lenderNAV}{lenderLPTokenTotalSupply}
$$

$$
borrowerLPTokenValue = \frac{borrowerNAV}{borrowerLPTokenTotalSupply}
$$

#### Calculating Deployed NAV

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.

```jsx
uint256 deployedNAV;
/// We acknowledge that this may be gas intensive but we do not foresee there 
/// being a high number of withdraw targets
for (uint256 i; i < withdrawTargets.length; ++i) {
    deployedNAV += _getDeployedAssetValue(withdrawTargets[i]);
}
```

The mechanism for updating the list of `s_withdrawTargets` is outlined in the Access Control section below.

## Thresholds

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

$$
\frac{borrowerNAV}{totalNAV} < capacityThreshold
$$

* *Liquidation threshold*: allows for liquidation if the ratio between borrowers NAV and total NAV is below the threshold

$$
\frac{borrowerNAV}{totalNAV} < liquidationThreshold
$$

LiquidationThreshold is enforced to always be lower then capacityThreshold

$$
liquidationThreshold < capacityThreshold
$$

These thresholds can be configured by the contract admin by calling `setLiquidationThreshold` and `setCapacityThreshold`

## Liquidation

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

$$
\frac{borrowerNAV}{totalNAV}\<liquidationThreshold
$$

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`

#### Withdrawing Liquidity

Liquidity deployed to the whitelisted targets can be withdrawn by either

1. Passing in an array of whitelisted targets in the `deactivate` function to deactivate and withdraw in the same transaction.
2. 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`.

## Deposits

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`.

#### **Example**

* `LiquidityWarehouse` has a `capacityThreshold` of 10%
* Borrower has deposited 10 USDC
* Lender’s can deposit up to a maximum of 90 USDC

<figure><img src="https://3251301222-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7IUb6hmEj08b42Hg3Nry%2Fuploads%2FLBnPO9CcMjBE0TRiQmjM%2FScreenshot%202024-03-18%20at%205.51.45%E2%80%AFPM.png?alt=media&#x26;token=f6eba8b7-a850-4d93-80fe-ef92d8f1c4de" alt="" width="375"><figcaption></figcaption></figure>

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.

$$
mintedLenderLPTokenAmount = \frac{lenderDepositAmount}{lenderLPTokenValue}
$$

$$
mintedBorrowerLPTokenAmount = \frac{borrowerDepositAmount}{borrowerLPTokenValue}
$$

## Withdrawals

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.

$$
lenderWithdrawalAmount= amountLenderLPToken \*lenderLPTokenValue
$$

$$
borrowerWithdrawalAmount= amountBorrowerLPToken \*borrowerLPTokenValue
$$

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

## Access Control and Operations

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.

#### Whitelisting Callers

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.

```jsx
struct Action {
      /// @notice The caller of the action
      address caller;
      /// @notice The target of the action
      address target;
      /// @notice The function that the action
      /// will call
      bytes4 fnSelector;
      /// @notice True if the action is whitelisted
      bool isWhitelisted;
      /// @notice True if this is the withdraw function
      bool isWithdraw;
  }
```

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.

```jsx
 bytes32 actionId = keccak256(abi.encode(target, caller, fnSelector));
 s_whitelistedActions[actionId] = action.isWhitelisted;
```

#### Withdraw Targets

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.

#### Execution

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

```jsx
struct ExecuteAction {
      /// @notice The target address the action will call
      address target;
      /// @notice The action's calldata
      bytes data;
      /// @notice The amount of wei to send when executing
      /// the action
      uint256 value;
  }
```

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`.

## Fees

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

$$
interestFees=interestFees%\*(lenderBalanceAfterInterest - lenderBalanceBeforeInterest)
$$

$$
lenderLPTokenMintToTreasury=lenderLPTokenTotalSupply\*interestFees/(lenderBalanceAfterInterest-interestFees)
$$

* *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

$$
LPTokenTransferToTreasury = withdrawalFees%\*LPTokenRedeemed
$$

$$
withdrawalAmountAfterFees = withdrawalAmount\*(1-withdrawalFees%)
$$

## Roles

There are two privileged roles

1. `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
   1. when the Liquidity Warehouse is paused, both lender and borrower deposit functions i.e `depositLender` and `depositBorrower` cannot be called
2. `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`                 |
