Notional V2 Technical Deep Dive Part 3: Valuation Framework

Jeff Wu
Jeff Wu

In this series of posts, I will walk you through the Notional V2 smart contracts. The goal of these posts is to help technically minded users and developers better understand a large and complex codebase (~12,000+ lines of Solidity!). In parts 1 and 2, we described fCash markets and trading on Notional V2. In this post, we will discuss Notional V2's valuation framework and how collateralization works for borrowers.

The math behind Notional V2's valuation framework is covered in depth in the documentation on Interest Rate Oracles and the fCash Valuation Curve. We won't rehash what's discussed in the documentation here, but this post will serve as a guide to how those concepts are implemented.

Every borrower in Notional needs to hold collateral against their debt. Notional's mission is to build the most capital efficient borrowing platform in DeFi and that means our valuation framework has to give borrowers credit for as much collateral as we can safely allow.

Oracle Rates and Discount Rates

fCash maturing at different points in time are not fungible with each other. In order to aggregate and compare multiple fCash assets we discount the notional (i.e. future value) of each asset to present value. The interest rate used to discount fCash assets is determined by rates set by Notional V2's fCash markets.

Notional V2 uses an internal interest rate oracle to ensure that users cannot manipulate the interest rate in their favor, calculated here. It's important to note that oracleRate is calculated whenever the market is loaded from storage and the previousTradeTime and oracleRate are set whenever a trade is completed. The oracleRate will also only update in storage if a lend or borrow trade has occurred. Adding or removing liquidity does not change the lastImpliedRate and therefore should not update the oracleRate.

As a side note, we never let lastImpliedRate reach exactly zero. If the lastImpliedRate or oracleRate are zero that means we must initialize the fCash markets using the nToken. This is described in a subsequent blog post.

Risk Adjustments

Throughout the valuation framework we calculate two versions of asset values: risk adjusted and non risk adjusted. Risk adjusted values reduce the value of collateral assets and increase the value of debt assets relative to their actual market value to give liquidators an opportunity to earn a profit if they have to liquidate. Larger risk adjustments reduce the risk of insolvency but also decreases the capital efficiency of the protocol. Setting sensible risk parameters is the job of Notional governance.

Terminology-wise, reducing the value of a positively valued asset is called a haircut. Increasing the value of a negatively valued asset is called a buffer. There are haircuts and buffers applied to fCash, liquidity tokens, nTokens, and cash balances.

fCash Valuation

Calculating the present value is pretty simple. We calculate the discount factor using continuous compounding and multiply that with the notional. Risk adjusted fCash values are calculated in this method.

\(discountFactor = e^(-oracleRate \cdot timeToMaturity)\)

If an fCash value is positive (lending), we increase the oracleRate by the fCashHaircut and discount by a higher interest rate than market. This puts the risk adjusted fCash value below the market value.

If an fCash value is negative (borrowing), we decrease the oracleRate by the debtBuffer. A lower interest rate increases the debt value of the fCash. It's important to note that Notional V2 allows users to hold less collateral against their debt than they owe at maturity! Although the effect is small for short term borrowing, it can be a sizable benefit for longer term borrowing. As the fCash debt gets closer to maturity, the size of this discount will decrease and an undercollateralized borrower may face liquidation.

If the oracleRate will become negative as a result of applying the debtBuffer we floor the oracleRate at zero and simply return the fCash notional value. This means the borrower must hold sufficient collateral for the entire debt payment at maturity.

Finally, Notional V2 also has a "bitmap portfolio" type which uses the same fCash valuation methods called via a different method. The bitmap portfolio is discussed in a separate post about Advanced Trading in Notional V2.

Liquidity Token Valuation

Liquidity tokens represent a claim on both asset cash and fCash in a market. Calculating these claims is straightforward. The value of a liquidity token will change as its claims shift between asset cash and fCash as a result of trading. Remember, the present value of fCash will also change as a result of changes to the oracleRate. Therefore, we apply a haircut to the asset cash and fCash claims during risk adjustment. Generally speaking, the longer dated the tenor the larger the haircut will be. Longer dated fCash markets will have higher slippage, potentially less liquidity and therefore more volatile interest rates.

Calculating the liquidity token value is a little tricky because we have to first net off fCash claims with fCash in the portfolio before discounting to present value. When a liquidity provider mints liquidity tokens, they also receive an offsetting negative fCash asset to their positive fCash claim. These two fCash values are fungible and offsetting so we need to net them off before discounting to present value (if we did not during a risk adjusted valuation a liquidity provider would have to hold additional, unnecessary collateral).

To do this with minimal looping, Notional V2 always sorts a user's assets such that the corresponding fCash asset is immediately before the liquidity token. If the matching fCash asset is present, we add the two values together before proceeding. In order to avoid double counting, we process all liquidity token values before fCash values. We will see this entire process in the last section of this post.

nToken Valuation

At this point it is worth mentioning the nToken a bit. nTokens are a new asset class introduced by Notional V2; they represent a bundle of liquidity tokens across all fCash markets for a given currency. Every currency in Notional V2 that has fCash (i.e. can be lent or borrowed) will have a single corresponding nToken (i.e. nDAI, nUSDC, nETH, etc). An nToken holder will receive yield in the form of underlying money market return (i.e. cDAI), fCash market liquidity fees, fCash fixed interest, and NOTE token incentives. Even better, nToken holders will have a fully passive experience, the nToken holdings will be automatically rolled forward upon the quarterly expiration of fCash markets.

We will dive into nToken dynamics in more depth in subsequent posts (there are a lot of dynamics!) so we'll just touch on how we value nToken balances for a user. The first step is to calculate the present value of all the nToken's holdings. To simplify setting the nToken haircut for governors, the value of individual assets is not risk adjusted during valuation but an overall haircut is applied at the end instead.

Because we don't do risk adjustments on individual assets, calculating nToken value is a lot easier. Note that in this method, nToken value is denominated in "asset cash" (i.e. cTokens). We simply aggregate all the present values of the cash and fCash assets. The adjustment for a specific user's account is simple:

\(nTokenValue = \frac{totalNTokenValue \cdot nTokenBalance}{nTokenTotalSupply}\)

Note that although the nToken's assets themselves are not risk adjusted, a user's nTokenValue is haircut during the free collateral check to account for the riskiness of the asset and to allow for a profitable liquidation of nTokens held as collateral.

Account Context

Before we dive into the aggregate free collateral calculation we will discuss the Account Context a bit. The account context is a storage slot that Notional V2 uses to track what currencies an account is active in in order to calculate their aggregate free collateral. There are three fields that are relevant to valuation.

Bitmap Currency Id

If bitmap currency id is set to a non zero value, this means that the account is using a bitmap type portfolio. This portfolio only holds fCash assets (no liquidity tokens) and has a different storage scheme than an array type portfolio. The free collateral check uses this field to determine where to find fCash assets.

Has Debt

hasDebt determines if the free collateral check will be called or not. If an account has a negative cash balance or a negative fCash asset then the free collateral check must be called. Whenever cash balances or fCash balances become negative, hasDebt is set to true. Note that hasDebt is a bitmap with two bits: one for cash debts and one for fCash debts.

If an account no longer has debt, the free collateral check will turn off the flag. This is done differently depending on the scenario. Cash debts are only turned off during a free collateral check (when all cash balances are examined). Β 

The fCash (or HAS_ASSET_DEBT) flag is updated differently depending on the portfolio type. Bitmap portfolios have their HAS_ASSET_DEBT flag updated during a free collateral check when all fCash assets are examined. Array type portfolios have their HAS_ASSET_DEBT flag updated whenever the array is updated.

Active Currencies

Active currencies is a bytes18 value where each 2 bytes represent a currency that the account has an active portfolio asset, cash balance or nToken. The setActiveCurrency method is called whenever balances or array type portfolios are updated.

The setActiveCurrency method does a lot of bit shifting which is probably best understood by just reading the code. Note that if bitmapCurrencyId is non zero, it cannot ever be marked as an active currency otherwise we will double count balances during a free collateral check.

Free Collateral

FreeCollateral.sol is a pretty large file but it's mainly due to the duplication between getFreeCollateralStateful, getFreeCollateralView and getLiquidationFactors. Each of these methods does the same calculation but with slight variations.

  • getFreeCollateralStateful is called in checkFreeCollateralAndRevert at the end of every user action that requires a collateral check.
  • getFreeCollateralView is called in the same external library for a read only analysis of free collateral. It also returns an array of the net asset cash values in each active currency. We require a separate view function for free collateral because getting the most accurate interest rates for underlying cTokens requires updating storage via a transaction (or simulating via a static call off chain). This isn't always possible so therefore we have a view only version.
  • getLiquidationFactors is used during liquidation which is discussed in a subsequent blog post.

An important concept in free collateral is called local currency netting. If a user is holding USDC denominated cash against USDC denominated fCash debt they have no foreign exchange risk between the cash and fCash value. In other words, the cash balance can be applied directly to the fCash debt as a repayment. Therefore, before we convert any currency to ETH and apply currency haircuts and buffers we first aggregate all value in a single currency.

The general structure of each of these methods is as follows:

  • If bitmapCurrencyId is active, get the cash balance, nToken value (not the same as nToken balance!), and net portfolio value in asset cash denominations. Add these all together to get local currency netting before converting to ETH.
  • Then proceed through each active currency in the same manner, getting cash balance, nToken and portfolio values in asset cash denominations.

Notice a few things. First, netLocalAssetValue is denominated in "asset cash" terms, but the ETH exchange rate is denominated in underlying terms (i.e. ETH not CETH). In _updateNetETHValue we convert asset cash denomination to underlying before converting to ETH. The convertToETH method will also apply haircuts and buffers to the local currency value before converting to ETH.

An aggregate free collateral value less than zero means an account may be liquidated which is discussed in a subsequent post.

Wrapping Up

In this post we discuss Notional V2's valuation framework. The valuation framework is critical to ensuring that borrowers maintain safe collateral levels such that lenders will feel confident the protocol will not become insolvent. At the same time, the valuation framework also strives to ensure that borrowers get as much capital efficiency as the protocol can safely allow.

Deep DiveTechnical

Jeff Wu

Co-Founder and CTO