Comment on page

# Notional Whitepaper

Fixed rate, fixed term lending is by far the most common type of lending in traditional financial markets. In 2018, there was $15.3 trillion dollars of debt outstanding in U.S. corporate debt and mortgage debt markets[1]. 88% of that debt was in terms of fixed rates; fixed rates are simply more desireable for consumers of the financial system (i.e. corporations and households) who do not want exposure to interest rate volatility. In this paper we describe Notional, an on-chain Ethereum protocol, that enables users to lend and borrow at fixed rates at predefined maturities. It is inspired by other successful Ethereum protocols such as Uniswap, Compound, and MakerDAO.

DeFi (Decentralized Finance) is an exciting and rapidly growing ecosystem of new financial products that live on the Ethereum blockchain. In 2019, the amount of funds locked up in DeFi products grew from $274M USD to $674M USD[4] and was continuing to grow into 2020.

The benefits of DeFi are clear: users are able to seamlessly lend, borrow and exchange tokens within a financial system that is secure, private, transparent, and globally accessible. Those without access to the traditional financial system are able to participate in the DeFi ecosystem with nothing but an internet connection and tokens; no financial intermediaries exist to create barriers to entry. Since DeFi products are nothing more than autonomous computer programs, they enable an ecosystem of programmable money where different protocols integrate in order to create entirely new categories of products and businesses.

A core, missing component of this ecosystem is fixed rate, fixed term financing. Notional proposes to fill this gap. Notional is heavily inspired by the constant-product market maker used by Uniswap as well as Compound's collaterlization mechanism. Our contributions include: the concept of fCash, periodic maturities, the portfolio, and cash settlement which we describe in the following sections. We also introduce a novel new liquidity curve that we have designed specifically for trading our fCash tokens.

**fCash**is a tokenized representation of a fCash flow. It represents the amount of tokens (i.e. Dai) that an account is either entitled to receive (

`CASH_RECEIVER`

) or obligated to pay (`CASH_PAYER`

) at its designated maturity. For example, if an account holds +100 fCash tokens for a maturity at timestamp 100, it is entitled to 100 Dai at any time greater than or equal to timestamp 100. Similarly, -100 fCash tokens for the same maturity means that the account is obligated to pay 100 Dai at timestamp 100. A detailed description of lending and borrowing mechanics will follow.fCash in one maturity (i.e. due to mature at timestamp 100) is fungible with other fCash tokens with the same maturity. However it is not directly fungible with fCash with different maturities. Also note that the entitlement to receive fCash (

`CASH_RECEIVER`

) is freely transferrable but the obligation to pay (`CASH_PAYER`

) is not.Finally, fCash tokens are not strictly ERC20 tokens because fCash tokens at different maturities are not fungible with each other. fCash tokens can be represented under the ERC1155 token standard which allows for interoperability.

Notional uses an internal accounting concept called

**current cash**to represent deposits and matured cash flows. Since cash flows may be positive (`CASH_RECEIVER`

) or negative (`CASH_PAYER`

), current cash is represented as a signed integer (i.e. positive or negative).When a fCash token matures, its positive or negative value is added to the account's total current cash balance. This is discussed in the settlement section.

Notional introduces the notion of periodic, rolling

**maturities**to simplify trading and pool liquidity. These maturities are defined by two governance parameters:`G_NUM_MATURITIES`

and `G_MATURITY_LENGTH`

. `G_MATURITY_LENGTH`

defines how long each periodic maturity will last in seconds. `G_NUM_MATURITIES`

defines how many of these maturities will be traded in the future. For example, if `G_MATURITY_LENGTH = 1000`

and `G_NUM_MATURITIES = 4`

and we are starting at time 0, there will be maturities at timestamps 1000, 2000, 3000, 4000. Each one of these maturities will have a pool of fCash tokens that can be bought and sold.Each maturity defined above creates a

**market**that functions similarly to a Uniswap exchange with an important distinction in how the rate curve functions. Each market is defined by the following variables:**maturity**: The timestamp where all the fCash tokens in this market will mature.**totalfCash**: The amount of fCash tokens available for purchase.**totalCurrentCash**: The amount of cash (i.e. Dai) available for purchase.**totalLiquidity**: The amount of liquidity tokens minted by liquidity providers in this market.**lastImpliedRate**: The implied period rate of the most recent trade.

Trading fCash tokens is done via a special liquidity curve that has been designed to minimize slippage when trading fCash tokens. Consider Uniswap's constant product liquidity curve; it is bounded at zero and positive infinity. The advantage of this curve is that a trade can always find a market price regardless of how much liquidity is in the pool. The disadvantage is that exchange rates must move large amounts in order to accomodate this; the amount of slippage incurred would be intolerable for trading fixed term cash flows. For example, if you were to trade 1% of the liquidity pool using the constant product curve for 1-month fCash, it could result in a 10% change in the interest rate. On an annualized basis, this means a 120% change in the interest rate. This is simply too volatile for a useful trading experience. Also note that this problem explodes exponentially as the token gets closer to maturity -- that 10% change in the interest rate becomes almost a 50% change when the fCash is one week from maturity (a

**600%**change on an annualized basis). This is why Notional has a special liquidity curve designed for trading fCash that we describe next.The liquidity curve in Notional is based on the logit function which allows us to greatly reduce slippage and create upper and lower bounds for interest rates. The detailed algorithm for calculating the liquidity curve along with proofs of its required properties are described in Appendix A. Here, we will just outline some of the important properties of the curve.

This curve has the properties we need. The center part of the curve is relatively flat and therefore there will be less slippage when trading under normal conditions. The edges of the curve change exponentially and will incentivize the market to trade the interest rate back to the flatter, middle area. There are three ways that we can control the shape of this curve:

`rateAnchor`

and `rateScalar`

are governance parameters that determine the offset from zero and the slope of the curve, respectively. The `proportion`

is determined by liquidity providers and trading in the market in general. Like Uniswap, the first liquidity provider will be able to set this proportion and therefore impact the initial rate. Importantly, however, the ratio of current cash and fCash the liquidity provider adds does not determine the interest rate in isolation (like it would in Uniswap), it is one factor in combination with the two governance parameters.The general formula for calculating interest rates is:

exchangeRate = ln(proportion / (1 - proportion)) / rateScalar + rateAnchor

â€‹

where:

daiAmount = fCashAmount / exchangeRate

proportion = totalfCash / (totalfCash + totalCurrentCash)

A trade of 1% of the pool under this liquidity curve would would move the proportion from 0.5 to 0.495. With a

`rateScalar`

value of 100, this trade would move the exchange rate 0.02%. That equates to a 0.24% move in the annualized interest rate -- a much more reasonable slippage.When discussing interest rates, we distinguish between three different types of rates for clarity:

**Exchange Rate**: the spot rate at which fCash is exchanged for current cash.**Implied Period Rate**: the interest rate over the period that is implied by the exchange rate (i.e.`impliedPeriodRate = (exchangeRate - 1) * MATURITY_LENGTH / timeToMaturity`

)**Implied Annual Rate**: the implied period rate, annualized

The

`takefCash`

function allows users to deposit cash (i.e. Dai) to the market in exchange for the right to receive a cash flow at maturity. This is the equivalent of **lending**; the user is depositing cash for the right to receive a (hopefully larger) cash flow at maturity. From an economic standpoint, the user would simply not deposit cash unless they were entitled to a greater amount of fCash at maturity; no one would trade 100 Dai today for 95 Dai in the future. If the exchange rate were to fall to that level, arbitrageurs could easily obtain risk-free profits by moving it back into line with the market's expectation for interest rates over the given period. The`getExchangeRate`

function is described in detail in Appendix A.def takefCash(maturity, fCashToReceive):

# 0 < G_LIQUIDITY_FEE < 1

fee = (G_LIQUIDITY_FEE * timeToMaturity) / MATURITY_LENGTH

tradeExchangeRate = getExchangeRate(maturity, fCashToReceive) - fee

daiToDeposit = fCashToReceive / tradeExchangeRate

â€‹

# Market variables are updated.

maturity.totalfCash = maturity.totalfCash - fCashToReceive

maturity.totalCurrentCash = maturity.totalCurrentCash + daiToDeposit

The

`takeCurrentCash`

function allows users to deposit a fCash obligation in order to receive cash. This is the equivalent of **borrowing**; the user is committing to a future obligation in exchange for an amount of Dai that they can withdraw from the contract and spend as they wish. This obligation will be collateralized by ETH,`CASH_RECEIVER`

or `LIQUIDITY_TOKEN`

. See the Portfolio section for more details on this. The `getExchangeRate`

function is described in detail in Appendix A.def takeCurrentCash(maturity, fCashObligation):

# 0 < G_LIQUIDITY_FEE < 1

fee = (G_LIQUIDITY_FEE * timeToMaturity) / MATURITY_LENGTH

tradeExchangeRate = getExchangeRate(maturity, fCashObligation) + fee

daiToDeposit = fCashObligation / tradeExchangeRate

â€‹

# Market variables are updated.

totalfCash = totalfCash + fCashObligation

totalCurrentCash = totalCurrentCash - daiToReceive

Note that the amount of Dai and fCash exchanged in the two trades described above (

`takeCurrentCash`

and `takefCash`

) are calculated when the trade is made. This means, in effect, the interest rate for the user is fixed from that point on. A lender will not have to deposit any more Dai for the cash flow that they are promised; the borrower will not have to pay more fCash for the Dai they received.For example, a lender deposits 100 Dai in exchange for 105 fCash tokens that mature in 1 year. Once this trade is made, the lender has entered into a fixed rate loan at a 5% annualized rate for a term of 1 year. From this point on, none of these terms will change.

This does not mean that the next lender to trade will also receive the same 5% annualized rate. Since the amount of

`totalCurrentCash`

and `totalfCash`

have changed after the previous trade, the next lender will receive a different fixed rate.This is how Notional provides

**fixed rates**at**fixed maturities**.Borrowers and lenders change the exchange rate by taking from one side of the market and depositing on the other. Without some amount of liquidity on both sides of the market these interactions would not be possible. This is where the concept of

**liquidity tokens**plays a role. This is heavily inspired by the Uniswap liquidity token model.Liquidity providers can provide liquidity to a market at a specfied maturity by calling the

`addLiquidity`

and `removeLiquidity`

functions. When a liquidity provider adds liquidity, they deposit Dai and fCash at the prevailing exchange rate to both sides of the market. The provider submits the amount of fCash to add and the corresponding amount of Dai is calculated using the current exchange rate. Liquidity tokens are minted to account for the provider's contribution. Since the liquidity provider is depositing fCash, we create a `CASH_PAYER`

token to represent the obligation a liquidity provider has to provide that fCash when the market matures. The net present value of the `LIQUIDITY_TOKEN`

will partially offset this obligation.# Implied Market Interest Rate = 5%

maturity.totalfCash = 1050

maturity.totalCurrentCash = 1000

maturity.totalLiquidity = 1050

â€‹

def addLiquidity(maturity, fCashToAdd, maxDaiToAdd):

# Both of these are in proportion to the fCash market.

tokensToMint = maturity.totalLiquidity * fCashToAdd / maturity.totalfCash

daiRequired = maturity.totalCurrentCash * fCashToAdd / maturity.totalfCash

â€‹

# Ensures that the provider can bail out if the rate has moved against them

assert(daiRequired <= maxDaiToAdd)

â€‹

# Update the markets

maturity.totalfCash += fCashToAdd

maturity.totalCurrentCash += daiRequired

maturity.totalLiquidity += tokensToMint

â€‹

# Remove the Dai balance as well as account for a fCash obligation

daiBalances[msg.sender] -= daiRequired

accountPortfolio[msg.sender].push(Asset(CASH_PAYER, maturity, fCashToAdd))

accountPortfolio[msg.sender].push(Asset(LIQUIDITY_TOKEN, maturity, tokensToMint))

When liquidity providers want to stop providing liquidity, they are able to remove their tokens and receive Dai and fCash in proportion to the market. Note that the

`CASH_RECEIVER`

here will offset the `CASH_PAYER`

that was added to the liquidity provider's portfolio when they deposited the tokens. The pseudocode is below:def removeLiquidity(maturity, tokensToRemove):

# Both of these are in proportion to the ownership stake in the liquidity pool

daiOwed = maturity.totalCurrentCash * tokensToRemove / maturity.totalLiquidity

fCashOwed = maturity.totalfCash * tokensToRemove / maturity.totalLiquidity

â€‹

# Update the markets

maturity.totalfCash -= fCashOwed

maturity.totalCurrentCash -= daiOwed

maturity.totalLiquidity -= tokensToRemove

â€‹

# Credit the balances back to the liquidity provider

daiBalances[msg.sender] += daiOwed

accountPortfolio[msg.sender].push(Asset(CASH_RECEIVER, maturity, fCashOwed))

accountPortfolio[msg.sender].push(Asset(LIQUIDITY_TOKEN, maturity, -tokensToRemove))

Liquidity providers are incentivized by a transaction fee (parameterized in

`G_LIQUIDITY_FEE`

) on every transaction. The advantage of this design is that liquidity providers can passively provide liquidity and earn transaction fees in the market.**A key consideration**for liquidity providers is the understanding that they are providing liquidity in

**a single maturity**. Once that maturity passes, their liquidity tokens will be converted to a Dai balance and a current cash balance. In order to provide liquidity in a new market, they will have to make a smart contract call to add liquidity to the new maturity.

Each user in Notional has an account represented by their Ethereum address.

Cash balances can either be positive or negative. Positive cash balances can be withdrawn from Notional after passing a free collateral check. These positive cash balances can only be withdrawn if Notional actually holds the corresponding tokens, this will not be the case without settling negative cash balances.

In a simplified scenario, there are only two accounts, a lender and a borrower. Their cash balances are below and the free collateral condition requires Barbara to have sufficient collateral to cover their obligations. Let's assume the DAI/ETH exchange rate is 100 Dai/ETH. For this example, let's also assume that the Notional contract holds no Dai balance.

Account | Maturity | Asset | Value | ETH Balance |
---|---|---|---|---|

Leonard | Time 100 | `CASH_RECEIVER` | +1000 | 0 |

Barbara | Time 100 | `CASH_PAYER` | -1000 | 15 |

**Notional Dai Balance: 0**

At maturity, both portfolios are settled to cash balances.

Account | Dai Balance | ETH Balance |
---|---|---|

Leonard | +1000 | 0 |

Barbara | -1000 | 15 |

**Notional Dai Balance: 0**

At this point, Barbara may elect to deposit 1000 Dai into her Dai balances in order to cover her obligation. This will net out her -1000 cash balance and add 1000 to Notional's Dai balance. Leonard will now be able to withdraw 1000 Dai from Notional.

Account | Dai Balance | ETH Balance |
---|---|---|

Leonard | +1000 | 0 |

Barbara | 0 | 15 |

**Notional Dai Balance: 1000**

However, imagine a scenario where Barbara does not deposit 1000 Dai to repay her debt. Leonard would like to withdraw his 1000 Dai. However, there is no Dai in the system - only a negative cash balance. Enter Sally the settler. Sally sees that Notional will give her a discount on ETH if she deposits 1000 Dai into Barbara's account in return for purchasing her ETH. After Sally settles out Barbara's debt (purchasing 11 ETH for 1000 Dai at an exchange rate of 90 Dai to 1 ETH).

Account | Dai Balance | ETH Balance |
---|---|---|

Leonard | +1000 | 0 |

Barbara | 0 | 4 |

**Notional Dai Balance: 1000**

Leonard can now withdraw his 1000 Dai from Notional.

Since

`LIQUIDITY_TOKEN`

positions have a claim on Dai, if there is insufficient Dai and ETH balances to settle a negative cash position, liquidity tokens will be withdrawn from the markets for their dai portion in order to settle the cash position. See portfolio assets for a full explanation.In addition to cash balances, each account has a

**portfolio**which is an array of its`CASH_PAYER`

, `CASH_RECEIVER`

and `LIQUIDITY_TOKEN`

balances. The reason for storing this in an array as opposed to mappings is so that the contract can iterate over a portfolio and calculate the net present value of all the assets that the account holds. We do this in order to calculate free collateral. It is important to note that each asset in the portfolio contains some amount of risk and therefore we apply a [currency buffer](#currency buffer-(collateralization-ratio)) to the value of each asset.An additional benefit of the portfolio construction is that it allows for Notional to net out opposing

`CASH_PAYER`

and `CASH_RECEIVER`

positions. If an account has an obligation of 100 fCash in 1 year (`CASH_PAYER`

), but then buys the right to receive 50 fCash (`CASH_RECEIVER`

) at the same maturity, its net position is now a `CASH_PAYER`

of 50 fCash in 1 year. The portfolio construction ensures that there is always only one entry per maturity that represents the net position of the account no matter how many trades they have made in that maturity.First we settle all matured fCash and liquidity tokens to an account's current cash balance. The formula for this is simple:

def settleMaturedAssets(portfolio):

for asset in portfolio if maturity < blockTime:

if asset is CASH_PAYER:

cashBalances -= asset.amount

else if asset is CASH_RECEIVER:

cashBalances += asset.amount

else if asset is LIQUIDITY_TOKEN:

cashBalances += asset.fCashOwed

# Note that liquidity tokens will take all the remaining Dai

# left in the market

daiBalances += asset.daiOwed

Liquidity tokens have a claim on current cash and

`CASH_RECEIVER`

tokens in the liquidity pool of a specified maturity. As trades occur, the claim the liquidity tokens have will shift between current cash and the cash receivers. Since `CASH_RECEIVER`

tokens hold no value in the free collateral calculation, the amount of current cash an account has available to collateralize other obligations will shift as trades occur.In order to give liquidators time to react to this while the account still has enough cash on had to cover their shortfall we will haircut both the current cash and cash receiver claims on the liquidity token, using the

`G_LIQUIDITY_HAIRCUT`

parameter. The calculation is as follows:G_LIQUIDITY_HAIRCUT = 0.95

â€‹

def getLiquidityTokenValue(maturity, tokenAmount):

daiClaim = maturity.totalCurrentCash * tokenAmount / market.totalLiquidity

fCashClaim = maturity.totalfCash * tokenAmount / market.totalLiquidity

â€‹

if maturity > blockTime:

# If trades can still occur at this maturity, the daiClaim and fCashClaim

# are not finalized. We discount them both to account for this risk.

daiClaim = daiClaim * G_LIQUIDITY_HAIRCUT

fCashClaim = fCashClaim * G_LIQUIDITY_HAIRCUT

â€‹

return (daiClaim, fCashClaim)

`CASH_RECEIVER`

represents cash an account is scheduled to receive at maturity. Any time before maturity, `CASH_RECEIVER`

tokens may be sold for current cash if there is sufficient liquidity in the relevant cash market. Since there is no guarantee that this liquidity will be available if a `CASH_RECEIVER`

token must be liquidated to pay off obligations, we apply a haircut to the value of these tokens. We haircut it by 50% on an annualied basis. Therefore 100 Dai `CASH_RECEIVER`

tokens maturing in 1 year will be valued at 50 Dai. 100 Dai `CASH_RECEIVER`

tokens maturing in 6 months will be valued at 75 Dai. We apply a max haircut value to ensure that strange behaviors do not occur as these assets get close to maturity. The haircut applied follows the formula:G_FCASH_HAIRCUT = 0.5

G_FCASH_MAX_HAIRCUT = 0.95

â€‹

def getCashReceiverValue(maturity, blockTime, amount):

annualizedHaircut = G_FCASH_HAIRCUT * (maturity - blockTime) / SECONDS_IN_YEAR

postHaircutValue = amount * annualizedHaircut

â€‹

maxPostHaircutValue = amount * G_FCASH_MAX_HAIRCUT

â€‹

return min(postHaircutValue, maxPostHaircutValue)

In the future, this haircut may be allowed to reflect the rate at which these tokens trade on the market.

Each portfolio has a cash ladder which represents the net amount that it is scheduled to pay or receive at each future maturity. The cash ladder is used to calculate the collateral requirement for the account. Take the example portfolio:

Maturity | Asset | Value |
---|---|---|

Block 100 | `CASH_RECEIVER` | +1000 |

Block 200 | `CASH_PAYER` | -500 |

Block 200 | `LIQUIDITY_TOKEN` | +500 |

Block 300 | `CASH_PAYER` | -400 |

The cash ladder would be computed as follows, with a 5% haircut given to liquidity token claims (assume a claim of 500 Dai and 500 fCash).

Maturity | Value | Post Haircut |
---|---|---|

Current | +500 | +475 |

Block 100 | +1000 | +1000 |

Block 200 | 0 | -25 |

Block 300 | -400 | -400 |

**Free collateral**represents the amount of excess collateral an account holds beyond what it needs to collateralize its obligations. It represents the buffer that the account has to withstand market volatility, the amount of collateral an account is allowed to withdraw from Notional, as well as the approximate maximum amount that the account is allowed to borrow. There are three sources of positive collateral: cash balances,

`LIQUIDITY_TOKEN`

and `CASH_RECEIVER`

. `CASH_PAYER`

tokens represent obligations and are the only source of negative collateral. `CASH_PAYER`

tokens convert to negative cash balances as maturity which will continue to be a source of negative collateral.The free collateral figure is derived from the net per currency requirements converted to ETH. When net per currency requirements are negative, we apply a currency buffer to account for the risk of collateralizing the debt with a foreign currency.

G_ETH_BUFFER = 0.30

â€‹

def freeCollateral():

netDaiBalance = daiCashBalance

+ sum(getLiquidityTokenValue(...)['daiClaim'])

+ sum([

getCashReceiverValue(...)

for asset in portfolio

if asset.assetType == "CASH_RECEIVER"

])

- sum([

asset.notional

for asset in portfolio

if asset.assetType == "CASH_PAYER"

])

â€‹

netEthBalance = netDaiBalance * daiEthExchangeRate

â€‹

if netEthBalance < 0:

netEthBalance = netEthBalance * (1 - G_ETH_BUFFER)

â€‹

return netEthBalance

When calculating the value of an accountâ€™s holdings for the purposes of collateralization checks, Notional will apply a discount to every collateral asset (i.e. ETH and liquidity tokens) that reflects the riskiness of that asset. An asset's riskiness is defined as the magnitude by which its value is likely to change. This discount is known as a

**currency buffer**. Currency buffers ensure that there is ample time for an account to be liquidated before a market move pushes it into insolvency. Currency buffers also incorporate the cost paid to liquidators for settling cash or liquidating accounts as described below.When an account's free collateral drops below zero it can be liquidated in order to ensure that it remains solvent. This can occur if the price of ETH/DAI drops. Liquidation is discussed in further depth in Appendix Bâ€‹

The first step in liquidation is to sell off assets in the portfolio in order to generate Dai to de-risk the portfolio. Notional looks at sources of positive collateral and converts them to Dai. There are potentially two sources of positive collateral: portfolio assets and cash balances. Notional first extracts collateral from portfolio assets before proceeding on to cash balances.

`CASH_RECEIVER`

and `LIQUIDITY_TOKEN`

assets are potential sources of positive collateral within an account's portfolio. Currently, Notional only uses the liquidity tokens' claim on Dai as an asset. In the event of liquidation, these tokens would be removed from the market and the account would be credited with its claim on both Dai and fDai.Note that the free collateral calculation already accounts for the

`daiClaim`

in the liquidity token. Therefore, only the haircut portion of this `daiClaim`

is actually available to recollateralize the account. For example, in the cash ladder section we describe an account that has a 475 post haircut Dai claim. The difference between this and the actual 500 Dai claim (25 Dai) is what is available to recollateralize the account.ETH can be held as collateral for a Dai loan. Take the following portfolio that has a 30% currency buffer on the ETH/DAI price:

Maturity | Asset | Dai Value | ETH Value |
---|---|---|---|

Block 100 | 1000 `CASH_PAYER` | -1000 | -13 (30% buffer) |

ETH Balance | ETH = 12; ETH/DAI = 100 | â€‹ | +12 |

Dai Balance | â€‹ | 0 | 0 |

Total | â€‹ | â€‹ | -1 |

Let's say this portfolio was collateralized when ETH/DAI was priced at 110, but it has since dropped to 100. The portfolio is at risk of becoming insolvent, we must liquidate it. Notional will allow a liquidator to purchase the ETH for Dai at a discount to the prevailing price and deposit Dai to offset the debt.

Liquidation Procedure | Dai Raised |
---|---|

Sell 10.52 ETH @ 95 (5% liquidation discount) | 1000 |

Now the portfolio looks as follows (note that the -1000 Dai debt is net out with the 1000 Dai balance)

Maturity | Asset | Dai Value | ETH Value |
---|---|---|---|

Block 100 | 1000 `CASH_PAYER` | -1000 | 0 |

ETH Balance | ETH = 1.48; ETH/DAI = 100 | 0 | +1.48 |

Dai Balance | â€‹ | +1000 | 0 |

Total | â€‹ | 0 | +1.48 |

A portfolio may be undercollateralized and only have

`CASH_RECEIVER`

assets as a source of positive collateral. We do not allow liquidation of until this condition is met -- meaning there are no liquidity tokens or positive cash balances in the portfolio. We do this because liquidating fCash is risky on multiple fronts. First we must sell the entire balance of the fCash asset into the market, we cannot calculate a portion of the `CASH_RECEIVER`

to sell (see Appendix A: Calculating Current Cash Requirement). Secondly, it is unclear if the market will have sufficient liquidity to purchase an arbitrary `CASH_RECEIVER`

asset. When market proportions reach 90% fCash the calculations will start to overflow and prevent trading. Finally, if this is the case then we allow the liquidator to purchase the fCash asset at a severe discount (see: Cash Receiver Value)In the event that

`CASH_RECEIVER`

assets will be liquidated, we first attempt to sell them into their respective market. If that fails then we allow the liquidator to purchase the assets.The algorithm for calculating the corresponding amount of current cash for a given fCash amount is as follows:

- 1.Find the new rate anchor for the current blocktime
- 2.Calculate the trade exchange rate for the given fCash amount
- 3.Add the trading fee to the trade exchange rate
- 4.Calculate the current cash amount from the exchange rate
- 5.Update the current cash and fCash pools accordingly
- 6.Calculate the final
**implied period rate**for the market and save it

Note the difference between exchange rates and implied rates. Exchange rates are the spot rate that fCash will be exchanged for current cash. Implied period rates are interest rates normalized to the length of the maturity. We are able to compare implied period rates across time periods; exchange rates can only be compared at a single point in time.

This is the Python psuedocode for calculating interest rates in Notional:

def getDaiAmount(maturity, fCashAmount):

# if fCashAmount > 0, we are borrowing

# if fCashAmount < 0, we are lending

timeToMaturity = maturity.blockTime - currentBlockNum

maturity.rateAnchor = getNewRateAnchor(maturity, timeToMaturity)

â€‹

# Assert that the implied rate we have now is exactly equal to the last implied rate we

# traded at. See: Interest Rate Continuity

assert(getImpliedRate(getExchangeRate(maturity, timeToMaturity, 0)) == maturity.lastImpliedRate)

tradeExchangeRate = getExchangeRate(maturity, timeToMaturity, fCashAmount)

â€‹

# The fee decreases as we get closer to maturity

fee = G_LIQUIDITY_FEE * timeToMaturity / MATURITY_LENGTH

tradeExchangeRate = tradeExchangeRate + fee

â€‹

# We do not allow negative interest rates

assert(tradeExchangeRate >= 1)

â€‹

daiAmount = fCashAmount / tradeExchangeRate

â€‹

# We update the liquidity pools to reflect the trade

maturity.totalfCash += fCashAmount

maturity.totalCurrentCash -= daiAmount

â€‹

# We update the lastImpliedRate, see: Interest Rate Continuity

finalExchangeRate = getExchangeRate(maturity, timeToMaturity, 0)

maturity.lastImpliedRate = getImpliedRate(finalExchangeRate, timeToMaturity)

â€‹

return daiAmount

â€‹

def getNewRateAnchor(maturity, timeToMaturity):

exchangeRate = getExchangeRate(maturity, timeToMaturity, 0)

# Converts the spot exchange rate to an implied period rate

impliedPeriodRate = getImpliedRate(exchangeRate, timeToMaturity)

# Converts the difference in implied period rates back to an exchange rate

rateDifference = (impliedPeriodRate - market.lastImpliedRate) * timeToMaturity / MATURITY_LENGTH

â€‹

# Adjusts the rate anchor to ensure Interest Rate Continuity

return maturity.rateAnchor - rateDifference

â€‹

def getExchangeRate(maturity, timeToMaturity, fCashAmount):

# See exchange rate formula above

proportion = (maturity.totalfCash + fCashAmount)

/ (maturity.totalfCash + maturity.totalCurrentCash)

â€‹

# The rate scalar becomes less sensitive as we get closer to maturity. See: Rolldown to Maturity

rateScalar = maturity.rateScalar * MATURITY_LENGTH / timeToMaturity

rate = Math.ln(proportion / (1 - proportion)) / rateScalar + maturity.rateAnchor

â€‹

return rate

â€‹

def getImpliedRate(exchangeRate, timeToMaturity):

# Scale the exchange rate to something that is comparable across blocks

return (exchangeRate - 1) * MATURITY_LENGTH / timeToMaturity

As the curve approaches maturity, a given change in the proportion will produce an exponential change in the implied period rate of fCash. Let's assume that

`rateScalar`

is constant:impliedPeriodRate = (ln(p / (1 - p)) / rateScalar + rateAnchor - 1) * MATURITY_LENGTH / timeToMaturity

d impliedPeriodRate / d proportion = -((p - 1) * p)^-1 * (MATURITY_LENGTH / timeToMaturity)

(d impliedPeriodRate / d proportion) / d timeToMaturity = MATURITY_LENGTH / timeToMaturity^2

In order to counteract this, we change

`rateScalar`

as the curve approaches maturity:rateScalar = rateScalar_0 * MATURITY_LENGTH / timeToMaturity

impliedPeriodRate = (ln(p / (1 - p)) * (timeToMaturity / (rateScalar * MATURITY_LENGTH)) + rateAnchor - 1) * MATURITY_LENGTH / timeToMaturity

= ln(p / (1 - P)) / rateScalar + [(rateAnchor - 1) * MATURITY_LENGTH / timeToMaturity]

d impliedPeriodRate / d proportion = -((p - 1) * p)^-1

(d impliedPeriodRate / d proportion) / d timeToMaturity = 0

Now we see that the implied period rate for a given change in proportion does not change as the curve approaches maturity.

Since we vary the scalar as we approach maturity, it means that implied interest rates will also change without any trading. This creates the possibility that traders could game the system by simply waiting for the rate to change and gradually siphon off value from liquidity providers. To counter act this, we save the implied period rate at every trade. We update the rate anchor before calculating

`tradeExchangeRate`

such that the new implied rate matches the last implied rate the market traded at. The proof is as follows:Prove: ir_0 = (exchangeRate(btm_1) - 1) * (PS / btm_1)

= [ln(p / (1 - p)) * (btm_1 / (s * PS)) + a_1 - 1] * (PS / btm_1)

where:

p := proportion of liquidity pools (does not change between the two rates)

PS := period size

s := rate scalar

btm_1 := time to maturity at t = 1

btm_0 := time to maturity at t = 0

ir_1 := implied rate at t = 1

ir_0 := implied rate at t = 0

a_1 := rate anchor at t = 1

a_0 := rate anchor at t = 0

a := initial rate anchor

â€‹

Note that:

ir_t = [ln(p / (1 - p)) * (btm_t / (s * PS)) + a_(t-1) - 1] * (PS / btm_t)

= ln(p / (1 - p)) / s + [a_(t-1) - 1] * (PS / btm_t)

â€‹

Since p does not change when we calculate the new anchor, the natural log terms cancel in:

a_1 = a_0 - [ir_1 - ir_0] * (btm_1 / PS)

â€‹

Therefore:

a_1 = a_0 - [(a_0 - 1) * (PS / btm_1) - (a - 1) * (PS / btm_0)] * (btm_1 / PS)

= 1 + (a - 1) * (btm_1 / btm_0)

â€‹

Substituting back into our initial equality:

ir_0 = ln(p / (1 - p)) / s + [a - 1] * (PS / btm_0)

â€‹

Which is our original equation for the implied period rate.

We need to ensure that the users trade at an exchange rate which is worse or equal to the pool's exchange rate after the trade. If this is not true, the curve is vulnerable to manipulation. Since the logit curve is monotonic (i.e. always increasing or decreasing), it is enough to prove this using the change in the proportion before and after the trade.

Here is the proof that this is the case. When X is positive, we are selling fCash (i.e. borrowing) and we are buying fCash (i.e. lending) when X is negative.

X = fCashTraded

Y = currentCashTraded

tradeProportion = (totalfCash + X) / (totalfCash + totalCurrentCash)

endProportion = (totalfCash + X) / (totalfCash + totalCurrentCash + (X - Y))

X = Y * exchangeRate

exchangeRate >= 1

â€‹

if X > 0, then X - Y > 0, therefore tradeProportion > endProportion

if X < 0, then X - Y < 0, therefore tradeProportion < endProportion

This curve only allows fCash to be specified by the user. It is not possible for us to solve analytically for the inverse of the formula (i.e. given current cash, how much fCash is required). We can do this via numerical approximation in an off-chain SDK.

Calculating fCash requirements given current cash is not required in any on-chain interactions. In liquidation and settlement, the contract will be calculating the net present value of fCash flows. Therefore it will have an input of a fCash amount and expect an output of the corresponding current cash amount. It will not be the case that we would need to price the fCash amount of some current cash amount since current cash is liquid and does not carry risk.

Notional allows any currency deposited in the system to be used as collateral against debts in another currency. Any currency (if fCash markets are listed by governors of the system) can also be traded as fCash. This also means that

`LIQUIDITY_TOKEN`

s may exist in any currency. Because these are a source of positive free collateral we may have to liquidate them in order to recapitalize an account. Doing so is non trivial and so the algorithm will be described here.The goal of liquidation is to trade either assets in an account's portfolio or positive cash balances in order to repay debts that the account owes. Since each debt requires additional collateral beyond its face value (i.e. a 100 Dai

`CASH_PAYER`

may require 120 Dai worth of other assets), by repaying the 100 Dai debt the account will have a net 20 Dai benefit in its free collateral position.A liquidation procedure requires specifying two currencies, the

**local currency**and the**collateral currency**. The**local currency**is the currency that debts are denominated in (in our above example, Dai). The**collateral currency**is the currency that holds a positive cash balance that can be sold in exchange for**local currency**.**Net Currency Available**: If negative, the post haircut requirement for debts. If positive, the non haircut amount of currency available to collateralize other debts. Defined as:`netCurrencyAvailable = cashBalance + postHaircutCashClaim - requirement`

**Cash Claim**: The sum total of cashClaims (not fCashClaim) held by liquidity tokens in this currency. See: Liquidity Token for more details.**Post Haircut Cash Claim**: The value of**Cash Claim**after the`LIQUIDITY_TOKEN_HAIRCUT`

is applied.**Local Currency Required**: The value of local currency required to recollateralize the account

The

`LIQUIDITY_TOKEN_HAIRCUT`

implies that accounts may become undercollateralized but have a source of positive cash in their liquidity token cash claims. Note that this situation can arise when providing liquidity because it is done on leverage; when the liquidity provider deposits cash into a market they incur a debt equal to their pre haircut fCash claim.`LIQUIDITY_TOKEN_HAIRCUT`

= 0.9`LIQUIDITY_TOKEN_REPO_INCENTIVE`

= 0.01- Assume Dai/ETH exchange rate = 0.1

Asset | Value | Cash Claim | Post Haircut |
---|---|---|---|

Dai `CASH_PAYER` | -1000 | â€‹ | -1000 |

Dai `LIQUIDITY_TOKEN` | â€‹ | +1100 | +990 |

Dai Net Currency Available | â€‹ | â€‹ | -10 |

The account is undercollateralized based on its post haircut values but it has sufficient Dai in its cash claims to recollateralize the account. It is possible for a liquidator to liquidate this account without any capital input. We incentivize the liquidation of these accounts with the

`LIQUIDITY_TOKEN_REPO_INCENTIVE`

. Note that only the difference between the cash claim and the post haircut cash claim is available to recollateralize the account. The post haircut cash claim has already been accounted for in the free collateral calculation. The formula is as follows:`cashClaim - cashClaim * liquidityTokenHaircut = localCurrencyRequired * (1 + incentive)`

Rearranging to calculate the total amount of cashClaim to remove from the account:

`cashClaim = localCurrencyRequired * (1 + incentive) / (1 - liquidityTokenHaircut)`

Since we pay

`localCurrencyRequired * incentive`

to the liquidator, this can be calculated as follows:`incentivePayment = cashClaim * (1 - liquidityTokenHaircut) - localCurrencyRequired`

After this is completed, the account's net currency available should be:

`netCurrencyAvailable' = netCurrencyAvailable + cashClaim * (1 - liquidityTokenHaircut) - incentivePayment`

In our example above these values are:

`cashClaim = 10 Dai * (1.01) / 0.1 = 101 Dai`

`incentivePayment = 101 Dai * (0.1) - 10 Dai = 0.1 Dai`

`netCurrencyAvailable' = -10 + 101 * (0.1) - 0.1 Dai = 0 Dai`

**NOTE**: a careful reader will observe that the

`LIQUIDITY_TOKEN`

in this scenario also holds a claim to fDai which may potentially recollateralize the account if the `CASH_PAYER`

and `LIQUIDITY_TOKEN`

are in the same maturity. This is because the fDai will directly offset the negative balance of the `CASH_PAYER`

. For simplicity, we do not account for this during liquidation at this time.The same scenario above can also arise when trading a collateral currency for local currency during liquidation. It's possible that the value in the collateral currency is providing liquidity and we must remove it in order to settle balances with the liquidator. In order to access the pre haircut cash claim we do the following calculations:

This is the value of the collateral currency's liquidity token haircut:

haircutValue = postHaircutCashClaim / liquidityTokenHaircut - postHaircutCashClaim

= postHaircutCashClaim * (1 / liquidityTokenHaircut - 1)

We calculate the amount of collateral currency we need to sell to the liquidator, accounting for the discount we provide as an incentive:

`collateralToSell = localCurrencyRequired * exchangeRate[local][collateral] * liquidationDiscount`

It is preferable that we

**do not**remove liquidity tokens if we do not have to. We prefer to use cash balances if possible, but we determine this using the following inequalities:`amountToRaise`

: the amount of cash claims that must be removed from liquidity tokens and given to the liquidator`balanceToTransfer`

: the portion of the liquidated account's cash balance that will be given to the liquidator`creditToAccount`

: the amount to credit back to the liquidated account from the`amountToRaise`

value

if netCollateralAvailable >= collateralToSell:

# We have sufficient currency available in either balances or postHaircutCashClaims

# to recollateralize the account

if balance >= collateralToSell:

amountToRaise = 0

creditToAccount = 0

balanceToTransfer = collateralToSell

else:

# We must remove the remaining collateralToSell from liquidity tokens.

amountToRaise = collateralToSell - balance

creditToAccount = 0

# We cannot transfer a negative balance

balanceToTransfer = balance > 0 ? balance : 0

â€‹

else if netCollateralAvailable + haircutValue >= collateralToSell:

# We must access the haircut value in order to recollateralize the account.

amountToRaise = (collateralToSell - netCollateralAvailable) / (1 - liquidityTokenHaircut)

â€‹

if balance >= collateralToSell:

# collateralToSell - netCurrencyAvailable <= haircutValue (this is the amount of haircut we need to access)

# We need to scale this by (1 - liquidityTokenHaircut) to determine the actual amount to raise. In this case

# the amountToRaise is reconstituting the account's balances via liquidity tokens.

balanceToTransfer = collateralToSell

creditToAccount = amountToRaise

else:

if amountToRaise < collateralToSell - balance:

# This will occur when an account has debts that are being collateralized in part by the liquidity token

# post haircut cash claim. See:

# netCollateralAvailable = balance + postHaircutCashClaim - requirement

# netCollateralAvailable + haircutValue = balance + postHaircutCashClaim - requirement + haircutValue > collateralToSell

# balance + postHaircutCashClaim - requirement + haircutValue > collateralToSell

# postHaircutCashClaim - requirement + haircutValue > collateralToSell - balance

# preHaircutCashClaim - requirement > collateralToSell - balance

# Here we've proven that in this case there is sufficient cash in the liquidity tokens to recollateralize the account.

amountToRaise = collateralToSell - balance

creditToAccount = 0

balanceToTransfer = balance > 0 ? balance : 0

else:

creditToAccount = amountToRaise - (collateralToSell - balance)

balanceToTransfer = balance > 0 ? balance : 0

else if netCollateralAvailable + haircutValue < collateralToSell:

# There is not enough collateral currency to fully recollateralize the account, we calculate the

# the maximum amount that we can trade

collateralToSell = netCollateralAvailable + haircutValue

amountToRaise = haircutValue / (1 - liquidityTokenHaircut)

â€‹

if balance >= collateralToSell:

# We have sufficient balance to transfer but the portfolio will be short haircutValue

# if we do not remove this value from its cash claims

balanceToTransfer = collateralToSell

creditToAccount = amountToRaise

else:

# Logic here applies similarly to what is above

if amountToRaise < collateralToSell - balance:

amountToRaise = collateralToSell - balance

creditToAccount = 0

balanceToTransfer = balance > 0 ? balance : 0

else:

creditToAccount = amountToRaise - (collateralToSell - balance)

balanceToTransfer = balance > 0 ? balance : 0

It's possible that a portfolio holds

`CASH_RECEIVER`

assets in a collateral currency during liquidation. Since we will not trade `CASH_RECEIVER`

during liquidation (only during liquidate fCash) we must adjust the netCollateralAvailable figure accordingly. In this case we want to ensure that positive value from fCash is used to net off against negative current cash balances.def calculatePostfCashValue(fCashValue, netCollateralAvailable, collateralBalance):

â€‹

if fCashValue < 0:

# There is net negative fCash value so there is nothing to net off

return {

netCollateralAvailable: netCollateralAvailable

netCollateralBalance: collateralBalance

}

â€‹

if collateralBalance >= 0:

# Collateral balance is positive so there is nothing to net off

return {

netCollateralAvailable: netCollateralAvailable - fCashValue

netCollateralBalance: collateralBalance

}

â€‹

if collateralBalance + fCash > 0:

# Partially net off collateral balance up until zero

return {

netCollateralAvailable: netCollateralAvailable - (collateralBalance + fCash)

netCollateralBalance: 0

}

else:

# Use all fCashValue to net off collateral balance, will remain negative

return {

netCollateralAvailable: netCollateralAvailable

netCollateralBalance: collateralBalance + fCashValue

}

Since a fCash pair

`CASH_PAYER`

and `CASH_RECEIVER`

at the same maturity offset each other, creation and trading of these assets should not be limited to on chain markets. We allow accounts to agree to create offsetting fCash pairs off chain and submit them to Notional for settlement. Accounts holding `CASH_PAYER`

assets must hold sufficient collateral for their debts. `CASH_RECEIVER`

assets that have corresponding on chain cash markets will contribute positive collateral to a portfolio. `CASH_RECEIVER`

assets that are idiosyncratic (i.e do not have a maturity that corresponds to a market on chain), have no positive collateral value. This restriction may be lifted in the future if there are liquid markets for purchasing these types of assets during liquidation.These trades will allow for higher value trades to occur off chain while enabling the security of on chain custody and settlement.

Last modified 2yr ago