Protocol

Architecture

The diagram below describes the high level architecture of Exactly Protocol:
Exactly Protocol Architecture

Main Contracts

Market

There is one Market contract for each asset supported in the protocol. Those assets are DAI, USDC, WBTC and ETH.
Extends ERC4626 (which in time extends ERC20), AccessControl, ReentrancyGuard, Pausable.
It has different functions to make deposits and borrow requests.
Each Market is created with:
  • An auditor (which is an Auditor contract)
  • A maximum number of future pools to be created for borrowing and lending (setMaxFuturePools())
  • An earnings accumulator smooth factor between 0 and 4 used when accruing earnings to the Variable Rate Pool (setEarningsAccumulatorSmoothFactor())
  • An interest rate model to be used to calculate rates (setInterestRateModel(), which in time uses the InterestRateModel contract)
  • A daily penalty rate between 1% and 5% (setPenaltyRate())
  • A backup fee rate charged to the Fixed Rate Pool depositors that the Variable Rate Pool suppliers will retain for initially providing liquidity (setBackupFeeRate())
  • A reserve factor percentage between 0% and 20% that represents the liquidity reserves that can't be borrowed (setReserveFactor())
  • A “damp speed” used to update the floatingAssetsAverage (setDampSpeed())

Main public functions

  • borrow()Borrows a certain amount of assets from the Variable Rate Pool.
  • repay(): Repays a certain amount to the Variable Rate Pool.
  • depositAtMaturity(): Deposits a certain amount of assets to a Fixed Rate Pool.
  • borrowAtMaturity(): Borrows a certain amount of assets from a Fixed Rate Pool.
  • withdrawAtMaturity(): Withdraws a certain amount from a Fixed Rate Pool.
  • repayAtMaturity(): Repays a certain amount to a Fixed Rate Pool.
  • liquidate(): Liquidates undercollateralized position(s).
  • seize(): Public function for liquidator to seize borrowers tokens in the Variable Rate Pool. This function will only be called from another Market, on liquidate() calls.
  • withdraw(): Withdraws the owner's Variable Rate Pool assets to the receiver address.
  • redeem(): Redeems the owner's Variable Rate Pool assets to the receiver address.
  • transfer(): Moves amount of shares from the caller's account to another account.
  • transferFrom(): Moves amount of shares from from to to using the allowance mechanism.
  • accountSnapshot(): Gets current snapshot for an account across all Fixed Rate Pools.
  • previewDebt(): Gets all borrows and penalties for an account.
  • totalFloatingBorrowAssets(): Gets the total amount of assets borrowed from the Variable Rate Pool.
  • totalAssets(): Calculates the Variable Rate Pool balance plus earnings to be accrued at current timestamp.
  • pause(): Pauses the contract in case of emergency, triggered by an authorized account.
  • unpause(): Unpauses the contract.

MarketETHRouter

Implements WETH.sol, a contract that wraps/unwraps ETH into WETH. This way the protocol can operate directly with ETH as a standardized ERC-20 token.

Auditor

Contains a list of all the valid Market instances. Is in charge of making the liquidity calculations between markets.
Each Market asks Auditor before making an operation (borrow, transfer, liquidation).

Main public functions

  • enterMarket(): Allows assets of a certain Market to be used as collateral for borrowing other assets.
  • exitMarket(): Removes a Market from sender's account liquidity calculation. Sender must not have an outstanding borrow balance in the asset, or be providing necessary collateral for an outstanding borrow.
  • accountLiquidity(): Returns account's liquidity for a certain Market.
  • checkBorrow(): Validates that the current state of the position and system are valid (liquidity).
  • checkShortfall(): Checks if the account has liquidity shortfall.
  • checkLiquidation(): Allows or rejects liquidation of assets.
  • checkSeize(): Allow/rejects seizing of assets.
  • calculateSeize(): Calculates the amount of collateral to be seized when a position is undercollateralized.
  • allMarkets(): Retrieves a list of Markets.
  • enableMarket(): Enables a certain Market.

ExactlyOracle

Used by the Auditor contract. Acts as a proxy to get the price of an asset from a price source, in US dollars. At the moment it uses Chainlink Price Feed Aggregator.

InterestRateModel

One for each asset in the protocol. Extends AccessControl and implements FixedPointMathLib.
Contains functions that return interest rates for both Fixed and Variable Rate Pools. It also has all the parameters to calculate the interest rate.

Other contracts

Previewer

Exposes functions to make less calls to request data from the on-chain contracts.

MaturityPosition

ERC721 contract that has data about each Market. Anyone can make calls to it. If the call is made from an address with a position in the protocol it returns a SVG file with the position values.

ReentrancyGuard

Contract module that helps prevent reentrant calls to a function.
Inheriting from ReentrancyGuard will make the nonReentrant() modifier available, which can be applied to functions to make sure there are no nested (reentrant) calls to them.
The nonReentrant() modifier prevents a contract from calling itself, directly or indirectly.

Pausable

Contract module which allows children to implement an emergency stop mechanism that can be triggered by an authorized account.

AccessControl

This contract module allows children to implement role-based access control mechanisms.

ERC-4626: Tokenized Vault Standard

ERC-4626 is an implementation of a standard API for tokenized vaults representing shares of a single underlying token. This standard is an extension on the ERC-20 token standard that provides basic functionality for depositing and withdrawing tokens and reading balances.

The problem

Tokenized vaults on Ethereum aren't actually a new thing. Protocols like Yearn (which is a yield optimizer) allows you to deposit your tokens into vaults and earn yield on those tokens while they are deposited.
Other protocols do something similar with their borrowing and lending contracts. The thing that all these protocols have in common is that when you deposit your DAI, your USDC or any other asset you receive a vault token in return. This vault token acts as kind of an IOU, and for some of these protocols you actually accrue more of the vault token as you have your underlying tokens staked over time. When you want to retrieve your original underlying token you just exchange the vault token and get the original tokens back.
The problem with these protocols is that there was no unifying standard. There's a lot of talk in the DeFi space about being composable with these "money legos", but it's very hard to build things from different protocols if these "money legos" don't actually fit together.

The solution

ERC-4626 is a game changer that attempts to solve this problem.
So again, what are vaults? Vaults are simply smart contracts and their purpose is to accrue yield as you lock your tokens inside. Each vault can have its own strategy for accruing yield but that's just an implementation detail.
An example would be depositing your DAI into a vault and receiving eDAI in return. Over time, as your DAI accrues value, you receive more eDAI. This eDAI is then able to be exchanged later to retrieve actual DAI. eDAI would be an example of the actual vault token or yield bearing token. A yield bearing token represents fractional ownership of the overall pool of DAI locked into the contract. If the value of the amount of tokens in the vault grows then the value of your yield bearing token actually grows because you own a stake in this pool.

How it works

ERC-4626 lays out a standard interface that every tokenized vault should adhere to. Firstly, every tokenized vault should be an ERC-20 token by default. On top of that, ERC-4626 adds a series of functions that can be called on this contract.
For example, you have a totalAssets() function that returns the total amount of the underlying token managed by the vault. Another function is the convertToShares(), which lets you know how many yield bearing tokens you will get in exchange for the underlying token. The convertToAssets() function does just the opposite.
The actual standard is pretty lengthy but you can see all the details in its Ethereum Improvement Proposal (EIP).
In conclusion, this new standard is quickly being adopted by many new protocols like Exactly, and we can expect to see a number of huge benefits in the DeFi space:
  • Huge increase in interoperability.
  • Drastically reduce development time when integrating to other protocols.
  • Helps with security as everybody is basically coding to the same standard, and auditors know what to look for.

Copy link
On this page
Architecture
Main Contracts
Market
MarketETHRouter
Auditor
ExactlyOracle
InterestRateModel
Other contracts
Previewer
MaturityPosition
ReentrancyGuard
Pausable
AccessControl
ERC-4626: Tokenized Vault Standard
The problem
The solution
How it works