See GitHub for full documentation:
Additionally, see below for overview:
Credit Vaults are multi-vault portfolios allowing different types of investors to deposit into one bucket of funds managed by the portfolio manager, while having different risk-return profiles. There are three tranches:
A / "Senior" (fixed rate)
B / "Junior" (fixed rate)
C / "Equity" (variable rate)
Vault (or "Portfolio") can be in one of three states - Capital Formation, Live and Closed.
Portfolio starts its life cycle in the Capital Formation state. From Capital Formation state, Portfolio can be transitioned into Live state or Closed state.
Portfolio can be launched at any time, from Capital Formation - into Live state. While in Live state, the portfolio can only be transitioned into Closed state. Closed state is the final state of the portfolio.
Portfolio Manager can decide to Close the Portfolio before it hits end of Capital Formation.
Length of the Capital Formation phase prevents the portfolio from staying in the Capital Formation state forever. If the deadline timestamp is met, the portfolio can be transitioned from Capital Formation state to Closed using a public function. It is a safety mechanism ensuring that funds will not remain stuck in the portfolio i.e. in case of PM losing his private key and not being able to manage the portfolio anymore.
Public transition from Live to Closed should be possible as well. If all loans get repaid or default and the portfolio is past the end date, any address can close the portfolio.
Manager can close portfolio anytime, when portfolio is Live. From this time on, essentially anyone can start withdrawing, manager stops earning fees and cannot disburse any more loans. Additionally the total portfolio value has to consist of 100% cash if it is being closed before maturity date.
Portfolio consists of four main smart contracts - Master Portfolio and three ERC4626 Vaults. Master Portfolio is a central contract responsible for most of the calculations. It also serves as a main capital pool and a terminal for portfolio managers to manage capital deployments. ERC4626 Vaults serve as ports for investors to deposit into or withdraw from a particular tranche. They store the capital when the portfolio is NOT in the Live state (so in the Capital Formation state and in the Closed state). In the Live state they only handle interactions between the investors and the portfolio. Additionally each Vault issues a token which represents shares of a particular investor in a particular tranche. These main four smart contracts are most likely going to be supported by a set of auxiliary smart contracts like Portfolio Factory or Protocol Config.
There are three main actors in the system:
Portfolio Manager (PM) - an actor responsible for portfolio configuration and capital deployments. They run an asset management business and earn fees as returns. It’s highly probable that they will also be lenders in the equity tranche.
Borrower - an actor that is a target of Portfolio Managers’ capital deployment operations. Receives capital on loan disbursement and is obligated to return it in the schedule defined in the used debt instrument.
Lender - an actor that invests their funds into a portfolio seeking returns. Lenders might have different risk appetite and different return requirements. For the purpose of this document they are divided into three main categories:
Senior Lender - is a lender whose priority is safety and high returns are a secondary goal.
Junior Lender - is a lender who wants to balance exposure to various risks and opportunity to earn an attractive yield.
Equity Lender - is a lender who is comfortable with exposure to highly volatile assets that may present different performance in different market conditions. Their capital will serve as first loss capital in case of default or portfolio underperformance.
DAO - an entity that oversees all portfolio operations, provides infrastructure and earns fees.
Depositing is allowed when a portfolio is in the Capital Formation state or in the Live state.
As the deposits in the Capital Formation state happen only before any capital deployments are made, or any other types of risks applied, for each deposit lender receives a number of LP tokens exactly equal to the number of tokens deposited. Portfolio token has the same number of decimals as the portfolio asset token (important note: there is always only one underlying asset token for each portfolio). For 1 wei of the underlying token deposited, a lender will receive 1 wei of the LP token. By default there are no restrictions on the sizes of the deposits. The only requirement that needs to be met is not depositing more into a particular tranche than is defined in the tranche ceiling parameter.
Withdrawals in the Capital Formation state are allowed. LP tokens are burned 1:1 to assets (at least in the vanilla, default strategy)
The default controller should require the withdrawal flag to be on, and funds to not drop below tranche floor level (controller setting).
When deposits happen in the Live state, the value of a LP token is dynamic. It changes because of the interest accrual mechanisms, portfolio value fluctuations etc. In order to make a deposit in the Live state the total value of the portfolio and the value of a particular LP token needs to be calculated in the depositing transaction. Because initially the value of one underlying asset wei was set to one LP token wei, very small deposits (deposits worth way below $0.01) may be mispriced. Additionally when making deposits in the Live state, both tranche ceilings and tranche ratios have to be respected.
When withdrawals happen in the Live state, the total value of the portfolio is dynamic. The total value of the portfolio, and then the value of a particular LP token tranche needs to be calculated in the withdrawing transaction. When withdrawals are made in the Live state, tranche ratios and tranche floors have to be respected.
When withdrawals happen in the Closed state, the total value of the portfolio is not subject to any changes anymore, LP tokens have a stable value. This value is calculated when the PM closes the portfolio and might change in case of recovery of any defaulted assets. When withdrawals are made in the closed state, neither tranche ratios, nor tranche floors need to be respected.
A PM creates a portfolio by calling a proper method on the Portfolio Factory. In order to create a portfolio a potential PM needs to be on the factory whitelist. In order to be whitelisted a PM needs to go through a process defined by the DAO. DAO is a final decision maker and executor when it comes to the whitelisting logistics.
If the PM wishes to use only 1, or 2 out of 3 available tranches - it is possible. There has to be a way to deploy only the necessary number of contracts. If PM launches a portfolio with only 2 (or 1) tranches, only 2 (or 1) of them should be deployed. This can be solved with a proper parametrisation of factory, different function for portfolio creation in factory or having multiple factories using the same factory whitelist. If portfolio is once deployed as a 1 or 2 tranche portfolio, more tranches cannot be added later on. If PM ultimately wants to run 3 tranches, but wants to start with 1, they still need to deploy all 3 of them, and using controllers block access to the ones that are not needed.
Each tranche has two flags a.k.a levers ****(they should be inside proper default Controllers) and determine whether deposits or withdrawals are allowed. By default, portfolio is created with the Deposit Lever set to allowed
and Withdraw Lever set to disallowed
. Then manager can set the levers as they please. When portfolio exits Capital Formation state and enters Live state, both levers are automatically set to disallowed
. When portfolio enters Closed state, Deposit Lever is automatically set to disallowed
and Withdraw Lever is automatically set to allowed
and manager can no longer change them.
At any point of time when the current value of any subsequent tranche needs to be calculated a waterfall calculation needs to be performed. Waterfall calculation first evaluates the total value of the portfolio. Sums up the value of the cash sitting in the portfolio and then assigns chunks of that value to particular tranches in the seniority order.
Structured Credit Vaults support only one type of Waterfall: learn more here.
Once whitelisted, a PM can create a portfolio. In order to create a portfolio they need to pass the following parameters. Some of them are editable (E) during the portfolio lifecycle or fixed (F) and cannot be changed after the portfolio creation.
A / Senior tranche parameters
Senior tranche name (F)
Senior tranche symbol (F)
Senior tranche manager fee (F)
Target senior interest rate (F)
First Loss Capital buffer for Senior (F)
B / Junior/Mezzanine tranche parameters
Mezzanine tranche name (F)
Mezzanine tranche symbol (F)
Mezzanine tranche manager fee (F)
Target senior interest rate (F)
First Loss Capital buffer for Mezzanine (F)
C / Equity tranche parameters
Equity tranche name (F)
Equity tranche symbol (F)
Equity tranche manager fee (F)
Minimum expected Equity interest rate (E)
Maximum expected Equity interest rate (E)
Administrative parameters
Portfolio name (F)
Portfolio duration (E)
Capital formation duration (E)
Minimum portfolio size (F)
There might be a need to pass additional parameters that were not mentioned here.
Once a portfolio is created it enters the Capital Formation state. In the Capital Formation state deposits of new capital by investors are available, but withdrawals (by default - they can be allowed) and loan disbursements are blocked. Particular deposits do not need to be larger than tranche floor, but they cannot overflow above tranche ceiling. PM is free to change tranche sizes during that period.
In order to start the portfolio, or in other words change its state from Capital Formation to Live, the only action that needs to be taken is calling a proper method. This method would only be callable by the PM and would require sum of all tranche sizes to be above defined total minimum portfolio size. Additionally all the tranches need to fulfil the tranche ratio requirements (so the Junior tranche cannot be too large compared to Equity tranche and Senior tranche cannot be too large compared to Junior tranche and Equity tranche combined). Calling this method will enable loan disbursements, start fixed interest accruing.
Mechanism of loan disbursement will not be discussed in this doc. Tranched portfolio should use the current TrueFi state of the art loan mechanism. Most certainly it's going to be based on Fixed Interest Only Loans, which can be configured either in a way that they will force the borrower to repay interest periodically and then principal at the loan end date, or as bullet loans. All issued debt instruments need to have their end date before the end date of the portfolio. Though this is only an UI-enforced requirement. We need to remove this requirement from smart contracts.
In order to close a portfolio before the maturity date, the portfolio cannot have any outstanding assets. Portfolio can always be closed, if it’s after the maturity date. When closing the portfolio a waterfall needs to be calculated and the value of each of the particular tranches set. After the portfolio is closed, deposits are blocked and capital deployments are blocked, but withdrawals are allowed. Withdrawals are made at the LP token price preset in the waterfall calculation. Tranches’ floors are not blocking withdrawals. Default controller flags indicating whether deposits and withdrawals are allowed are set, so deposits are not allowed anymore and withdrawals are allowed - this is being set automatically and cannot be changed by the manager anymore.
It is possible that some repayments from overdue instruments are gonna be made after the portfolio is officially closed. If this happens, Waterfall needs to be recalculated as the overall value of the portfolio changes.
For more on the concept of controllers, read more here.
In order to allow utilisation of different lender restriction strategies, without breaking the ERC4626 interface used for tranche vaults, a special mechanism needs to be put in place. Whenever a user would be about to create portfolio LP tokens, a special auxiliary smart contract is asked about the results of a particular deposit or mint. This contract should take all the parameters provided in the deposit()
or mint()
call, plus the message sender of the call and return a pair of values - amount of LP tokens that will be minted on deposit, or amount of asset tokens that need to be sent in order to mint desired amount of LP tokens, plus the amount of asset tokens that need to be sent as a fee to the management fee beneficiary address. In the simplest case, this contract might be just a whitelist that only checks its internal state and facilitates standard, proportional deposits or mints, but this interface allows implementation of more sophisticated strategies. If there are any other pieces of data that need to be passed, they would need to be passed directly to the controller contract, before interacting with the vault. So for example the whitelist doesn’t necessarily need to be managed manually by the PM, but might automatically add anyone that provides a signature of some particular piece of data. Each vault has to have its own Deposit Controller.
✅ Controlled Functions
deposit(assets, receiver)
onDeposit(msg.sender, assets, receiver)
Returns:
shares
- how many shares are gonna be minted to the receiver
depositFee
- how much is going to be subtracted from assets
and sent to the manager beneficiary address
mint(shares, receiver)
onMint(msg.sender, shares, receiver)
Returns:
assets
- how many assets are going to be sent from receiver
to the portfolio
mintFee
- how many assets are going to be sent from receiver
to the manager fee beneficiary
Additionally Deposit Controller needs to handle all the necessary view functions. Their interfaces are identical to the original ERC-4626 interfaces.
previewDeposit(assets)
- returns how many LP will the depositor get for the assets
maxDeposit(receiver)
- returns max amount of assets that can be put into deposit (before fee subtraction)
previewMint(shares)
- returns how many assets does user need to send to mint particular number of shares (actually deposited + fee)
maxMint(receiver)
- returns max amount of shares that can be minted
Analogically to the deposits and mints there is a controller, responsible for handling withdrawals and redeems. Withdrawal controller, on each withdrawal or redeem attempt, will receive all call arguments, plus the message sender and determine the amount of assets that is being taken out of the vault, or amount of LP tokens burned, plus fee for the manager.
✅ Controlled Functions
withdraw(assets, receiver, owner)
onWithdraw(msg.sender, assets, receiver, owner)
Returns:
shares
- how many shares are gonna be burned from the owner
withdrawFee
- how much is going to withdrawn from the vault on top of the assets
and sent to the manager beneficiary address
**redeem(shares, receiver, owner)**
onRedeem(msg.sender, shares, receiver, owner)
Returns:
assets
- how many assets are going to be sent from the vault to the receiver
mintFee
- how many assets are going to be taken from the vault on top of the assets
and sent to the manager fee beneficiary
Additionally Withdrawal Controller needs to handle all the necessary view functions. Their interfaces are identical to the original ERC-4626 interfaces.
previewWithdraw(assets)
- returns how many LP will one need to burn in order to get desired amount of assets
maxWithdraw(owner)
- returns max amount of assets that can be withdrawn (after paying fee)
previewRedeem(shares)
- returns how many assets will user get (after paying fee) for a particular redeem
maxRedeem(receiver)
- returns max amount of shares that can be burned to redeem
Transfer controller determines whether particular ERC-20 vault LP token can be transferred. Transfer controller does not reimplement the whole logic of the transfer, but in this case simply returns a boolean determining, whether a particular transfer is allowed or not. In order to return this value transfer controller would receive all parameters of the transfer - sender, recipient, amount and the message sender.
✅ Controlled Functions
transfer(to, value)
transferFrom(from, to, value)
onTransfer(msg.sender, from, to, value)
Returns:
isTransferAllowed
- boolean determining whether a particular transfer is allowed or not
Fees can be configured separately for each tranche of the vault. Below are all of the fee types that can be applied to vaults:
Block-By-Block continuous fees
Protocol fee (required, set by protocol)
Management fee (optional, set by PM)
Additional optional fees
LP Deposit fees (optional, set by PM)
LP Redemption fees (optional, set by PM)
Instrument origination and/or repayment fees (optional, set by PM)
Protocol fees accrue block-by-block during the Live state and in the Closed state of a vault. Management fees also accrue block-by-block, but only during the Live state of a vault.
Protocol fees and Management fees are paid on each interaction with the vault, including lender deposits, lender redemptions, disbursements made by the PM, repayments from borrowers, or updates to the vault value ("NAV updates").
Whenever an interaction happens, the following sequence is executed by the vault smart contract:
Check following parameters:
Current TVL (before the action is executed)
Time elapsed since previous interaction
Fee rate (basis points per annum)
Calculate fee accrued for the period between the current and previous interaction
If there are sufficient funds available in the vault, send fees (incl. any previously unpaid fees) directly to fee beneficiary addresses
Protocol fee is sent to TrueFi protocol treasury
Management fee is sent to an address set by the PM
If there are not sufficient funds available, save the value of fees that couldn’t be paid as unpaid fees
LP deposit fees and redemption fees are handled entirely by Deposit Controller and Withdrawal Controller contracts, respectively. Vaults can charge custom management fees upon deposit or redemptions if respective controllers implement proper logic and return non-zero fee values.
Additionally, instrument origination fees and/or instrument repayment fees can be implemented within the mechanism of an instrument itself. The vault smart contract itself does not implement any mechanism that facilitates charging such fees.
Value of each subsequent tranche can be fetched by calling a proper method on a proper ERC4626 Vault. It works differently for different tranches.
Value of Senior and Junior tranches is flat and remains unchanged when a portfolio is in an Capital Formation or Closed state. When it is in Live state, Senior and Junior tranches’ values increase linearly at the pace defined by the target interest rate parameters. This value should be capped by the value of the tranche assigned to it by the Optimistic Waterfall. So the target interest rate might not be met if the Optimistic Waterfall calculations turn out to assign a smaller value to the tranche.
Whenever the value of the LP token is repriced in the Live state (i.e. because of deposit or withdrawal), already accrued interest is accounted towards the total value of the portfolio and now is also accruing interest. So the interest is compounding with each “tick” of the portfolio. For portfolios that do not last very long or do not have very high interest rates, the compounding effect is insignificant. You can check this Compounding vs Simple doc to see example calculations comparing simple interest versus compounding interest.
Equity tranche, similarly to Junior and Senior tranches remains flat in the Capital Formation and Closed states. While a portfolio is in the Live state the value of the Equity tranche might be highly volatile. It is calculated as the remaining value of the total portfolio value after subtracting values of all of the other tranches. It might be the case that for some period of time Equity tranche is going to be losing value (i.e. while the portfolio is underutilized)
In order to make the portfolio more attractive for Senior lenders it is possible to first onboard equity capital (or Equity and Junior capital), deploy it and then, once the PM has proven that they will perform sensible capital deployments, onboard Senior capital. PM can set the ceiling of the Senior tranche to 0 at the beginning and only start gathering Junior and Equity capital. Once enough funds are collected they can switch the Portfolio state to Live, and disburse the first batch of loans. Once Senior lenders can clearly see what PM is investing in, PM opens Senior deposits, by increasing senior tranche ceiling. Now, each newly collected Senior deposit would be deployed into the same instruments PM already used. If implemented, PM can also use the “Warehousing Line” product to seed the Portfolio.
Credit Vaults are multi-tranche vaults allowing lenders to deposit into one or more specific buckets of funds ("tranches") managed by the portfolio manager. Credit Vaults can have up to 3 tranches, enabling each tranche to deliver unique risk-return profiles.
Below is an overview of Credit Vault contracts.
StructuredPortfolio
is a contract responsible for loan management and Tranche value calculations. It might hold any number of tranches (limited by gas) but at launch, credit vaults are intended to contain 1, 2, or 3 tranches.
StructuredPortfolio
extends LoansManager
contract.
StructuredPortfolio
has 3 states: CapitalFormation
, Live
and Closed
.
The credit vault goes into Live
state when the start()
method is called. It transfers all funds from tranches to the credit vault and enables the ability to create and fund loans. Each tranche value is now calculated using the debt waterfall algorithm.
Creating and funding loans is only possible in Live
state.
When the vault's end date passes, anyone can close the vault. Manager can close a vault prematurely if there are no ongoing loans. This will transfer funds back to tranches according to the waterfall algorithm.
StructuredPortfolioFactory
is a factory contract that creates StructuredPortfolio
contracts along with its TrancheVault
contracts and controllers.
TrancheVault
is the contract that allows users to deposit/withdraw funds and to manage the vault. TrancheVault
creates checkpoints on every action that changes total tranche value. This allows the vault to calculate linear growth since the last change in waterfall calculations.
TrancheVault
handles paying fees to the manager and to the protocol. Fees are calculated continuously but are transferred on every checkpoint update.
TrancheVault
also manages DepositController
, WithdrawController
and TransferController
. TrancheVault
supports ERC20, ERC165 and ERC4626 interfaces.
The ProtocolConfig
contract holds parameters necessary for fee accruals.
These parameters are:
defaultProtocolFeeRate
: the protocol fee (in basis points) that will be charged on each vault
protocolAdmin
: the address of the protocol owner
protocolTreasury
: the address where all fees are transferred
pauserAddress
: address of developer multisig for emergency pausing the protocol
customFeeRates
: custom fee model that can be defined by the manager
All of these parameters are settable by the protocol admin.
LoansManager
is an abstract contract that is an adapter to FixedInterestOnlyLoans
that allows to add/fund/cancel/repay them.
Controllers regulate different aspects of how TrueFi products work.
DepositController
checks whether a lender is allowed to deposit and checks maximum deposit amounts for each lender. Managers can choose to enable or disable deposits.
WithdrawController
manages whether a lender can withdraw and checks maximum withdrawal amounts for each lender. Managers can choose to enable or disable withdrawals prior to the vault's maturity date. This controller is similar to DepositController
but for withdrawals.
TransferController
has a single method onTransfer
that is called when a tranche's ERC20 transfer is made.
The basic implementation of this controller always returns true (all transfers enabled). This could be swapped for a different controller to add custom functionality (e.g. only transfers between specific addresses allowed, no transfers allowed, etc).
In credit vaults, the senior tranches are entitled to receive principal plus accrued interest (fixed rate target interest) in higher priority over the more junior tranches.
Thus in a three-tranche credit vault, the waterfall of repayments works as follows:
First, Tranche A receives principal plus target interest, then Tranche B receives principal plus target interest, and then Tranche C receives the remainder of the funds in the vault.
In summary, juniormost tranches absorb performance-based volatility within the vault, while more senior tranches feel losses only if losses exceed the size of subordinated junior tranches.
It is important to note that in CapitalFormation
and Closed
states, the vault consists only of idle funds. In CapitalFormation
and Closed
states, there are no assumptions about the future performance of deployed capital and thus the vault is only calculating how to split idle funds between different lenders.
First, the senior tranche would get its principal plus target interest, then junior would get its principal plus target interest and then equity would get the rest. So basically the equity tranche will absorb most of the performance-based volatility, while the junior tranche will be absorbing default losses if the total losses are going to be larger than the equity tranche.
Important to notice is that in the Open and the Closed states, the portfolio consists only of cash, which means that there are no assumptions about future performance of currently deployed capital, but it’s only a way of calculating how to split the cash between different lenders.
See a few examples below: