Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,107 @@ Comments:

- Hardhat node is used with mainnet forking which requires `ALCHEMY_API_KEY` to be set in a `.env` file
- the timeout for tests may need to be adjusted; the mocha `timeout` variable is set in `hardhat.config.js`

## Mainnet contracts

The core parts of the system are upgradable due to their highly sensitive nature. The Index Token (vault)
is the entry point for user interactions, requiring maintenance of changing business logic and safeguards. The LP Account holds the portfolio funds and must be able to interact with external protocols safely.

Other contracts are immutable to reduce complexity and trust assumptions. They can be replaced with newer implementations without significant impact to the functioning of the system.

### Upgradable

- LP Account
- Index Token (not deployed)
- Address Registry (this is a legacy contract, only needed so Chainlink can retrieve the TVL Manager address)

### Immutable

- TVL Manager
- Oracle Adapter
- LP Account Funder (not deployed)

## System architecture

### Vault

An ERC20 token satisfying EIP-4626.

- user deposits 3CRV tokens to mint index tokens
- user redeems index tokens to receive 3CRV tokens

### LP Account

- funded by capital borrowed from the vault
- holds positions in multiple Convex gauges
- periodically harvests and re-invests rewards
- periodically rebalances the portfolio
- can only transfer funds using authorized routes, e.g. to the vault or into Convex position

Note funds only move between the LP Account and the vault through use of a special contract, the LP Account Funder.

#### LP Account Funder

This contract has a higher access privilege than the one to control the LP Account. It can pull funds from the vault into the LP Account or transfer funds from the LP Account into the vault.

### TVL Manager

This can be considered the "write"-side of the oracle subsystem. Position data is written to the contract (asset allocations and erc20 allocations are registered) and Chainlink can retrieve this data as part of its pricing of the portfolio value.

- Convex positions are registered here
- Chainlink nodes can query the manager for a list of all positions
- each position data includes the LP Account's balances in stablecoins
and the symbol and decimals info for each stablecoin
- Chainlink nodes can therefore value each position using market prices
and submit the TVL on-chain

### Oracle Adapter

This can be considered the "read"-side of the oracle subsystem. The prices for the vault asset and the portfolio are retrieved from this contract.

- wrapper contract around Chainlink price feeds
- all necessary oracle pricing, such as 3CRV price or system TVL, is obtained via this contract
- validation checks and safeguards allow proper functioning of the system were Chainlink to fail

## Periphery contracts

The core contracts for the system are listed in the previous section. The LP Account contract allows the registration of types of contracts used as "logic" contracts for delegate-calls from the LP Account:

- zap
- swap

### Zaps and Swaps

Zaps "install" the functionality for entering and withdrawing from Convex positions. Each zap has a name identifying the Convex position by strategy name and enables the following functions on the LP Account for that position:

- deployStrategy
- unwindStrategy
- ...

A Swap "installs" the functionality for a particular set of swap routes. This enables swapping rewards or airdropped tokens to stablecoins tto be re-invested into the portfolio.

#### Deploying and registering a new zap

Zap registration is the only means of controlling the flow of funds from the LP Account and thus must be done after proper review of the zap contract. Registration can only be done through the Emergency Safe.

Zap state is never used; a zap is purely a logic contract for the delegatecalls made by the LP Account.

#### Removing an existing zap

#### Deploying and registering a new swap

#### Removing an existing swap

### Asset Allocations

The TVL Manager requires allocation contracts to be registered with it. An allocation contract for a Convex position decomposes the positioninto underlying balances that are reliably priced by Chainlink. This often requires decomposing LP tokens into its underlying coins and possibly, as in the case of lending protocols, a further unwrapping into base coins. The important observation here is that the allocation interface is general and allows a multitude of decompositions, as long as they reflect a realistic liquidation value for the position.

#### Deploying and registering a new allocation

#### Removing an existing allocation

### ERC20 Allocations

#### Registering a new allocation

#### Removing an existing allocation
16 changes: 8 additions & 8 deletions diagrams/ChainlinkTVLAdapter.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title Chainlink TVL Adapter
participant ChainlinkNode#lightblue
participant TVLManager#pink
participant ERC20#yellow
participant APYPeripheryContract
participant CortexPeripheryContract
participant DeFiProtocol
database PriceFeed-1#lightgray
database PriceFeed-2#lightgray
Expand All @@ -22,13 +22,13 @@ activate TVLManager
alt #yellow Protocol
TVLManager -> ERC20: balanceOf(account)
else Periphery
TVLManager -> APYPeripheryContract: balanceOf(account)
activate APYPeripheryContract
APYPeripheryContract ->DeFiProtocol: call()
APYPeripheryContract ->DeFiProtocol: call()
APYPeripheryContract ->DeFiProtocol: call()
APYPeripheryContract -->TVLManager:
deactivate APYPeripheryContract
TVLManager -> CortexPeripheryContract: balanceOf(account)
activate CortexPeripheryContract
CortexPeripheryContract ->DeFiProtocol: call()
CortexPeripheryContract ->DeFiProtocol: call()
CortexPeripheryContract ->DeFiProtocol: call()
CortexPeripheryContract -->TVLManager:
deactivate CortexPeripheryContract
end
TVLManager -->ChainlinkNode:
deactivate TVLManager
Expand Down
Binary file added diagrams/LpAccountZap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions diagrams/LpAccountZap.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
title LP Account (zap logic)

participant LP Safe #teal
participant LP Account#violet
participant TVL Manager#pink
participant Zap
participant Curve#silver
participant Convex#silver
participant Swap
participant DEX#silver


LP Safe ->LP Account: deployStrategy(name, amounts)
activate LP Account #pink

# Register Account Positions
group#pink Check Allocations Registered
LP Account ->Zap: assetAllocations()
activate Zap
Zap -->LP Account: allocation names
deactivate Zap
LP Account ->Zap: erc20Allocations()
activate Zap
Zap -->LP Account: erc20 addresses
deactivate Zap
LP Account ->TVL Manager: _checkAllocationRegistrations(allocation names)
LP Account ->TVL Manager: _checkErc20Registrations(erc20 addresses)
end

#Deploy capital
group#lightblue Deploy Capital
LP Account ->LP Account:get zap from name
LP Account -->Zap: deployLiquidity(amounts) <<delegatecall to zap>>
activate Zap
Zap ->Curve:
activate Curve#silver
Curve ->Zap:
deactivate Curve

Zap ->Convex:
activate Convex#silver

Convex ->Zap:
deactivate Convex

Zap -->LP Account:
deactivate Zap
end

LP Account ->LP Safe:

deactivate LP Account



LP Safe ->LP Account: unwindStrategy(name, amounts)
activate LP Account #pink

#Unwind capital
group#lightblue Unwind Capital
LP Account ->LP Account:get zap from name
LP Account -->Zap: unwindStrategy(amounts) <<delegatecall to zap>>
activate Zap

Zap ->Convex:
activate Convex#silver

Convex ->Zap:
deactivate Convex

Zap ->Curve:
activate Curve#silver
Curve ->Zap:
deactivate Curve

Zap -->LP Account:
deactivate Zap


end

LP Account ->LP Safe:

deactivate LP Account


LP Safe ->LP Account: claim(names)
activate LP Account #pink

#Swap
group#lightblue Claim Rewards
LP Account ->LP Account:get swap from name
LP Account -->Swap: swap(amount) <<delegatecall to swap>>
activate Swap

Swap ->DEX:
activate DEX#silver

DEX ->Swap:
deactivate DEX


Swap -->LP Account:
deactivate Swap

end

LP Account ->LP Safe:

deactivate LP Account

LP Safe ->LP Account: swap(name, amount)
activate LP Account #pink

#Swap
group#lightblue Swap Tokens
LP Account ->LP Account:get swap from name
LP Account -->Swap: swap(amount) <<delegatecall to swap>>
activate Swap

Swap ->DEX:
activate DEX#silver

DEX ->Swap:
deactivate DEX


Swap -->LP Account:
deactivate Swap


end

LP Account ->LP Safe:

deactivate LP Account
Binary file added diagrams/VaultDeposit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions diagrams/VaultDeposit.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
title Vault Deposit

actor User
actor Receiver
participant Vault#violet
participant VaultAsset#yellow
participant OracleAdapter#lightblue

User ->VaultAsset: approve(Vault, assetAmount)
User ->Vault: deposit(assetAmount, Receiver)
activate Vault#lightgrey
Vault ->VaultAsset: allowance(User, Vault) >= assetAmount
Vault ->OracleAdapter: getAssetPrice(token); price the vault asset
Vault ->OracleAdapter: getTvl(); price the deployed capital
Vault ->Vault: mint(Receiver, mintAmount)
Vault ->VaultAsset: transferFrom(User, this, assetAmount)
Vault -->User:
deactivate Vault
Binary file added diagrams/VaultWithdraw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions diagrams/VaultWithdraw.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
title Vault Withdraw

actor User
actor Owner
actor Receiver
participant Vault#violet
participant VaultAsset#yellow
participant OracleAdapter#lightblue


Owner ->VaultAsset: approve(User, shareAmount) (skip if User is Owner)
User ->Vault: redeem(shareAmount)
activate Vault#lightgrey
Vault ->VaultAsset: allowance(Owner, User) >= shareAmount (skip if User is Owner)
Vault ->OracleAdapter: getAssetPrice(VaultAsset); price the vault asset
Vault ->OracleAdapter: getTvl(); price the deployed capital
Vault ->Vault: burn(User, shareAmount)
Vault ->OracleAdapter: transfer(Receiver, assetAmount)
Vault -->User:
deactivate Vault