Skip to content

Commit 7d9d39a

Browse files
authored
Merge pull request #37 from onflow/jord/architecture-doc
Add architecture doc
2 parents 8a34fce + d5dff89 commit 7d9d39a

2 files changed

Lines changed: 277 additions & 6 deletions

File tree

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,22 @@ Users holding an asset are seeking higher returns than the direct yield opportun
44
We utilize flow's unique feature of scheduled transactions to automate this process, constantly adjusting the position to keep asset exposure at 100%, maximizing the potential debt to maximize yield while making sure the user doesn’t get liquidated.
55
This will bring TVL and users to Flow, provide a revenue stream through fees, and demonstrate a practical application of Flow’s unique feature of scheduled transactions.
66

7+
## Installation
8+
```sh
9+
curl -L https://foundry.paradigm.xyz | bash
10+
source ~/.zshenv # or restart your shell
11+
foundryup
12+
```
13+
14+
## Build & Test
15+
```bash
16+
make ci # fmt check + build + tests on the solidity project
17+
make solidity-test # tests only
18+
```
19+
20+
## Architecture
21+
See [Architecture](./docs/architecture.md)
22+
723
## Dependencies (Flow EVM mainnet)
824

925
### Morpho Blue
@@ -45,9 +61,3 @@ Pools used by the vault (fetched via `Factory.getPool(tokenA, tokenB, fee)`):
4561
| PYUSD0 / Yield token | `100` (0.01%) |
4662
| WETH / PYUSD0 | `3000` (0.30%) |
4763

48-
## Build & test
49-
50-
```bash
51-
make ci # fmt check + build + tests on the solidity project
52-
make solidity-test # tests only
53-
```

docs/architecture.md

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# FCM Architecture
2+
3+
This repo implements an [ERC4626](https://eips.ethereum.org/EIPS/eip-4626)-compliant vault which implements a levered investment with automated rebalancing.
4+
5+
## Terminology
6+
- **Asset** - ERC4626 term meaning "the unit of account of this vault". Deposits, withdrawals, and NAV for a vault are denominated in the vault's asset. The asset must be an ERC20 token.
7+
- **InnerAsset/OuterAsset** - The asset of the inner vault or outer vault, respective (see below)
8+
- **Share** - ERC4626 term meaning "a portion of the total assets in this vault". Shares are fungible and are represented as ERC20 tokens. Vault users deposit assets and receive shares.
9+
- **InnerShare/OuterShare** - The share of the inner vault or outer vault, respective (see below)
10+
- **Outer Vault** - The ERC4626 vault implemented in this repository, which borrows against deposits to invest in an inner vault.
11+
- **Inner Vault** - The ERC4626 vault which the outer vault invests borrowing proceeds in.
12+
13+
## Dependencies
14+
### Lending Protocol
15+
[Morpho Blue](https://github.com/morpho-org/morpho-blue)
16+
17+
### Automated Market Maker (AMM)
18+
[FlowSwap (Uniswap v3)](https://flowswap.io/)
19+
20+
### Inner Vault
21+
In general, the inner vault may be any ERC4626-compliant vault. As an example, Jon's FUSDEV vault uses [Morpho Vault v2](https://docs.morpho.org/build/earn/concepts/vault-mechanics)
22+
23+
**Liquidity:** We must assume that the inner vault MAY be unable to satisfy any withdrawal requests, at any time (eg. is illiquid). To address this in a general way, we primarily use DEX swaps to acquire/dispose of InnerShares. We then rely on the DEX to provide sufficient liquidity for the shares.
24+
25+
**NAV Reporting:** We must assume that the NAV (share price) reported by the vault may be out of date on the order of days.
26+
27+
## Deposit Flow
28+
### A. AMM-Mediated Deposit
29+
We swap debt tokens (InnerAsset) to InnerShares via an AMM. Our ability to satisfy deposits is dependent on available liquidity in the AMM pool.
30+
31+
```mermaid
32+
sequenceDiagram
33+
autonumber
34+
actor User
35+
participant Outer as Outer ERC4626 Vault
36+
participant Lender as Lending Protocol
37+
participant Dex as AMM
38+
39+
User->>Outer: deposit(outerAsset)
40+
activate Outer
41+
42+
Outer->>Lender: supply (outerAsset)
43+
Lender-->>Outer: borrow (innerAsset)
44+
Note over Lender,Outer: Always supply all deposits. <br />Borrow amount limited by LTV.
45+
46+
Outer->>Dex: swap (innerAsset → innerShare)
47+
Dex-->>Outer: innerShare
48+
49+
Outer-->>User: outerShare
50+
deactivate Outer
51+
```
52+
53+
### B. Direct Deposit
54+
We deposit debt tokens (InnerAsset) to InnerShares via the inner vault's `deposit` function. Our ability to satisfy deposits is dependent on the vault's deposit capacity ([`maxDeposit`](https://ethereum.org/developers/docs/standards/tokens/erc-4626/#maxdeposit))
55+
```mermaid
56+
sequenceDiagram
57+
autonumber
58+
actor User
59+
participant Outer as Outer ERC4626 Vault
60+
participant Lender as Lending Protocol
61+
participant Inner as Inner ERC4626 Vault
62+
63+
User->>Outer: deposit(outerAsset)
64+
activate Outer
65+
66+
Outer->>Lender: supply (outerAsset)
67+
Lender-->>Outer: borrow (innerAsset)
68+
Note over Lender,Outer: Always supply all deposits. <br />Borrow amount limited by LTV.
69+
70+
Outer->>Inner: deposit (innerAsset)
71+
activate Inner
72+
Inner-->>Outer: innerShare
73+
deactivate Inner
74+
75+
Outer-->>User: outerShare
76+
deactivate Outer
77+
```
78+
79+
## Withdrawal Flow
80+
There are several ways to implement withdrawals, enumerated below. The main differences are:
81+
1. Source of liquidity risk (inner vault vs AMM).
82+
2. Ability to withdraw full amount when LTV is near limit. In option C, the flashloan enables always repaying the full debt amount first. In options A/B, we may be unable to do this (depending on LTV). See [below](#ltv-limit-edge-case) for details.
83+
84+
### A. AMM-Mediated Withdrawal
85+
```mermaid
86+
sequenceDiagram
87+
autonumber
88+
actor User
89+
participant Outer as Outer ERC4626 Vault
90+
participant Lender as Lending Protocol
91+
participant Dex as AMM
92+
93+
User->>Outer: redeem(outerShare)
94+
activate Outer
95+
96+
Outer->>Dex: swap (innerShare → innerAsset)
97+
Dex-->>Outer: innerAsset
98+
Note over Outer,Dex: We realize market price, not NAV<br /> (NAV may be higher or lower)
99+
100+
Outer->>Lender: repay (innerAsset)
101+
Lender-->>Outer: withdraw collateral (outerAsset)
102+
103+
Outer->>Dex: reconcile surplus (innerAsset → outerAsset)
104+
Dex-->>Outer: outerAsset
105+
106+
Outer-->>User: outerAsset
107+
deactivate Outer
108+
```
109+
110+
#### Pros
111+
- No inner vault liquidity risk
112+
113+
#### Cons
114+
- Requires repaying debt before withdrawing collateral. Reverts if the yield→debt swap underdelivers and the intermediate HF would dip below 1.
115+
- Pays DEX fees/slippage
116+
- Pool liquidity risk: thin yield/debt pool degrades or blocks large redeems.
117+
118+
### B. Direct Withdrawal
119+
```mermaid
120+
sequenceDiagram
121+
autonumber
122+
actor User
123+
participant Outer as Outer ERC4626 Vault
124+
participant Lender as Lending Protocol
125+
participant Inner as Inner ERC4626 Vault
126+
127+
User->>Outer: redeem(outerShare)
128+
activate Outer
129+
130+
Outer->>Inner: redeem (innerShare)
131+
activate Inner
132+
Inner-->>Outer: innerAsset
133+
deactivate Inner
134+
135+
Outer->>Lender: repay (innerAsset)
136+
Lender-->>Outer: withdraw collateral (outerAsset)
137+
138+
Outer->>Dex: reconcile surplus (outerAsset ↔ innerAsset)
139+
Dex-->>Outer: innerAsset
140+
141+
Outer-->>User: outerAsset
142+
deactivate Outer
143+
```
144+
145+
#### Pros
146+
- Redeems yield at NAV — no LP fee, no slippage on the yield leg.
147+
- Independent on AMM liquidity for the yield asset.
148+
149+
#### Cons
150+
- Dependent on available liquidity in inner vault.
151+
152+
### C. Flash Loan Path
153+
```mermaid
154+
sequenceDiagram
155+
autonumber
156+
actor User
157+
participant Outer as Outer ERC4626 Vault
158+
participant Lender as Lending Protocol
159+
participant Dex as AMM
160+
161+
User->>Outer: redeem(outerShare)
162+
activate Outer
163+
164+
Lender-->>Outer: flashloan (innerAsset)
165+
166+
Outer->>Lender: repay (innerAsset)
167+
Lender-->>Outer: withdraw collateral (outerAsset)
168+
169+
Outer->>Dex: swap (yieldAsset → innerAsset)
170+
Dex-->>Outer: innerAsset
171+
172+
Outer->>Dex: reconcile surplus (outerAsset ↔ innerAsset)
173+
Dex-->>Outer: innerAsset
174+
175+
Outer->>Lender: repay flashloan (innerAsset)
176+
177+
Outer-->>User: outerAsset
178+
deactivate Outer
179+
```
180+
181+
#### Pros
182+
- Deterministic unwind at any HF — debt is cleared before collateral moves
183+
184+
#### Cons
185+
- Most complex: callback-based reentry, encoded calldata, extra Morpho roundtrip.
186+
- Larger attack surface — callback must validate msg.sender and decode data correctly.
187+
- Still depends on DEX for the yield sale and reconcile legs (liquidity, fees/slippage)
188+
189+
#### LTV Limit Edge Case
190+
```mermaid
191+
sequenceDiagram
192+
autonumber
193+
actor User
194+
participant Outer as Outer ERC4626 Vault
195+
participant Lender as Lending Protocol
196+
participant Dex as AMM
197+
198+
Note over Lender: Initial: HF ≈ 1 (LTV near LLTV)<br/>In options A/B, if our recovered outerAsset is < debt<br />then we can't repay full debt amount.
199+
200+
User->>Outer: redeem(outerShare)
201+
activate Outer
202+
203+
Lender-->>Outer: flashloan (innerAsset)
204+
Note over Outer,Lender: Vault holds enough debt-token to repay in full<br/>without touching the position
205+
206+
Outer->>Lender: repay full debtSlice (innerAsset)
207+
Note over Lender: HF improves (debt ↓, coll unchanged)<br/>Position is over-collateralized
208+
209+
Lender-->>Outer: withdraw collSlice (outerAsset)
210+
Note over Lender: Always succeeds because we just repaid full debt slice
211+
212+
Outer->>Dex: sell yieldSlice → innerAsset
213+
Dex-->>Outer: innerAsset (may be < flash loan amount)
214+
215+
alt surplus (yield sale ≥ flash)
216+
Outer->>Dex: swap surplus innerAsset → outerAsset
217+
Dex-->>Outer: extra outerAsset (user bonus)
218+
else deficit (yield sale < flash)
219+
Outer->>Dex: swap some collSlice → innerAsset
220+
Dex-->>Outer: innerAsset (covers deficit)
221+
Note over Outer: User absorbs their own<br/>yield shortfall at market price
222+
end
223+
224+
Outer->>Lender: repay flashloan (innerAsset)
225+
226+
Outer-->>User: outerAsset
227+
deactivate Outer
228+
```
229+
230+
## Rebalancing
231+
See **TODO LINK TO REBALANCING SPEC**
232+
233+
## Custom Behaviour
234+
See [here](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol#L50-L68) for guidance on how to safely extend the base ERC4626 contract.
235+
236+
## Security
237+
### Donation/Inflation Attack
238+
See [explanation from OpenZeppelin](https://docs.openzeppelin.com/contracts/5.x/erc4626#security-concern-inflation-attack).
239+
240+
Our implementation is safe from this attack because we inherit from the OpenZeppelin ERC4626 base contract, which implements a virtual share mitigation. See [here](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol#L22-L47) for guidance on extending this mitigation.
241+
242+
### Re-entrancy Attack (TODO)
243+
For each external function, how does it protect against re-entrancy?
244+
245+
### Sandwich Attack
246+
An attacker manipulates AMM prices before and after our swap to capture part of the value of our swap.
247+
- The primary mitigation is a slippage limit, which limits how much slippage we will accept on each trade. This doesn't prevent the attack, but does limit how much value can be extracted per trade.
248+
- Flow as the underlying platform provides some protection. There is no system akin to [MEV-Boost](https://github.com/flashbots/mev-boost), which systematizes MEV extraction. No individual node in Flow can deterministically dictate transaction ordering. Attackers need to send many transactions, hope some are placed in the desired order, and be able to revert operations on those that are not in the desired order. Still possible, but more complex and expensive.
249+
250+
If an attacker is able to invoke a function which performs a swap (that isn't swapping their funds), then the sandwich attack becomes much more dangerous (eg. a permissionless `rebalance` function).
251+
- The attacker can reliably order their operations by structuring the "full sandwich" as one transaction.
252+
- The attack is repeatable.
253+
254+
### Oracle Manipulation (TODO)
255+
256+
## Dust Strategy (TODO)
257+
258+
## References / Prior Art
259+
260+
- [Patrick's Vault PoC](https://github.com/holyfuchs/fcm-sol-poc)
261+
- [Schlagonia Morpho Lender Vault](https://github.com/Schlagonia/lender-borrower/blob/morpho/src/MorphoBlueLenderBorrower.sol)

0 commit comments

Comments
 (0)