Skip to content

Commit 2c34f43

Browse files
committed
add & clean up fork tests
1 parent 5b2a9a8 commit 2c34f43

7 files changed

Lines changed: 329 additions & 627 deletions

File tree

.github/workflows/solidity.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
run: FOUNDRY_PROFILE=ci forge lint
3838

3939
- name: Run Forge gas snapshot test
40-
run: FOUNDRY_PROFILE=ci forge snapshot --match-path test/gas/*.sol --check
40+
run: FOUNDRY_PROFILE=ci forge snapshot --match-path "test/gas/*.sol" --check
4141

4242
- name: Run Forge build
4343
run: FOUNDRY_PROFILE=ci forge build --sizes

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ solidity-fmt-fix:
3131

3232
.PHONY:
3333
snapshot:
34-
cd solidity && FOUNDRY_PROFILE=ci forge snapshot --match-path test/gas/*.sol
34+
cd solidity && FOUNDRY_PROFILE=ci forge snapshot --match-path "test/gas/*.sol"
3535

3636
# ---------------------------------------------------------------------------
3737
# Pyth oracle maintenance (Flow EVM mainnet — MANUAL ONLY)

solidity/.gas-snapshot

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
1+
<<<<<<< HEAD
12
FCMGasSnapshotsTest:test_gas_deposit() (gas: 318756)
23
FCMGasSnapshotsTest:test_gas_harvest() (gas: 166758)
34
FCMGasSnapshotsTest:test_gas_rebalance() (gas: 117675)
4-
FCMGasSnapshotsTest:test_gas_redeem() (gas: 209098)
5+
FCMGasSnapshotsTest:test_gas_redeem() (gas: 209098)
6+
||||||| parent of 7e1f953 (add & clean up fork tests)
7+
FCMGasSnapshotsTest:test_gas_deposit() (gas: 314629)
8+
FCMGasSnapshotsTest:test_gas_harvest() (gas: 164232)
9+
FCMGasSnapshotsTest:test_gas_rebalance() (gas: 115148)
10+
FCMGasSnapshotsTest:test_gas_redeem() (gas: 206572)
11+
=======
12+
FCMForkGasSnapshotsTest:test_gasFork_deposit() (gas: 687421)
13+
FCMForkGasSnapshotsTest:test_gasFork_harvest() (gas: 43288)
14+
FCMForkGasSnapshotsTest:test_gasFork_rebalance() (gas: 70307)
15+
FCMForkGasSnapshotsTest:test_gasFork_redeem() (gas: 3234137)
16+
FCMGasSnapshotsTest:test_gas_deposit() (gas: 314629)
17+
FCMGasSnapshotsTest:test_gas_harvest() (gas: 164232)
18+
FCMGasSnapshotsTest:test_gas_rebalance() (gas: 115148)
19+
FCMGasSnapshotsTest:test_gas_redeem() (gas: 206572)
20+
>>>>>>> 7e1f953 (add & clean up fork tests)
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.24;
3+
4+
import {FCMVault} from "../../src/FCMVault.sol";
5+
import {IFCMVault} from "../../src/interfaces/IFCMVault.sol";
6+
import {ISwapRouter02} from "../../src/interfaces/external/ISwapRouter02.sol";
7+
import {IUniswapV3Pool} from "../../src/interfaces/external/IUniswapV3Pool.sol";
8+
import {IMorpho, Id, Market, MarketParams, Position} from "@morpho-blue/interfaces/IMorpho.sol";
9+
import {IOracle} from "@morpho-blue/interfaces/IOracle.sol";
10+
import {MarketParamsLib} from "@morpho-blue/libraries/MarketParamsLib.sol";
11+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
12+
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
13+
import {Test} from "forge-std/Test.sol";
14+
15+
contract ForkDeployers is Test {
16+
IERC20 constant WBTC = IERC20(0x717DAE2BaF7656BE9a9B01deE31d571a9d4c9579);
17+
IERC20 constant PYUSD0 = IERC20(0x99aF3EeA856556646C98c8B9b2548Fe815240750);
18+
IERC20 constant FUSDEV = IERC20(0xd069d989e2F44B70c65347d1853C0c67e10a9F8D);
19+
address constant MARKET_ORACLE = 0x5B3e0BA14443B444D557C0C2F85592d88B88f5c8;
20+
address constant MARKET_IRM = 0xdFC4f7951EcDd2D505b6406e9c886c0dB9393546;
21+
IOracle constant YIELD_ORACLE = IOracle(0x144F613490DD55C9844Ef139CFB9B63433dD349F);
22+
address constant SWAP_FACTORY = 0xca6d7Bb03334bBf135902e1d919a5feccb461632;
23+
IMorpho constant MORPHO = IMorpho(0x9a094eA4AbE343D908E1bDE9fC478D71b41D665f);
24+
ISwapRouter02 constant SWAP_ROUTER = ISwapRouter02(0xeEDC6Ff75e1b10B903D9013c358e446a73d35341);
25+
26+
address constant YIELD_LOAN_POOL = 0x9196e243b7562B0866309013f2F9EB63F83A690f;
27+
28+
uint256 constant HEALTH_FACTOR_MIN = 1_228_571_428_571_428_571;
29+
uint256 constant HEALTH_FACTOR_MIN_TARGET = 1_230_329_041_487_839_771;
30+
uint256 constant HEALTH_FACTOR_MAX = 1_433_333_333_333_333_333;
31+
uint256 constant HEALTH_FACTOR_MAX_TARGET = 1_430_948_419_301_164_725;
32+
uint256 constant MARKET_LLTV = 0.86e18;
33+
uint256 constant YIELD_FACTOR_MAX = 1.01e18;
34+
uint24 constant COLLATERAL_LOAN_POOL_FEE = 3000;
35+
uint24 constant YIELD_LOAN_POOL_FEE = 100;
36+
37+
uint256 constant ORACLE_PRICE = 100_000e36;
38+
uint256 constant YIELD_ORACLE_PRICE = 1e24;
39+
40+
FCMVault internal vault;
41+
MarketParams internal mp;
42+
Id internal marketId;
43+
address internal collateralLoanPool;
44+
uint160 internal cleanSpot;
45+
46+
address internal owner = address(this);
47+
address internal arb = makeAddr("arb");
48+
address internal alice = makeAddr("alice");
49+
50+
function _forkSetup() internal {
51+
vm.createSelectFork("http://localhost:8545");
52+
53+
setCollateralPrice(ORACLE_PRICE);
54+
setYieldPrice(YIELD_ORACLE_PRICE);
55+
56+
collateralLoanPool = _getPool(SWAP_FACTORY, address(WBTC), address(PYUSD0), COLLATERAL_LOAN_POOL_FEE);
57+
require(collateralLoanPool != address(0), "WBTC/PYUSD0 pool missing");
58+
59+
mp = MarketParams({
60+
loanToken: address(PYUSD0),
61+
collateralToken: address(WBTC),
62+
oracle: MARKET_ORACLE,
63+
irm: MARKET_IRM,
64+
lltv: MARKET_LLTV
65+
});
66+
marketId = MarketParamsLib.id(mp);
67+
68+
_supplyMorphoLiquidity(100_000_000_000e6);
69+
70+
(cleanSpot,,,,,,) = IUniswapV3Pool(YIELD_LOAN_POOL).slot0();
71+
72+
vault = new FCMVault(
73+
IFCMVault.InitParams({
74+
collateralToken: WBTC,
75+
loanToken: PYUSD0,
76+
yieldToken: FUSDEV,
77+
healthFactorMin: HEALTH_FACTOR_MIN,
78+
healthFactorMinTarget: HEALTH_FACTOR_MIN_TARGET,
79+
healthFactorMax: HEALTH_FACTOR_MAX,
80+
healthFactorMaxTarget: HEALTH_FACTOR_MAX_TARGET,
81+
yieldFactorMax: YIELD_FACTOR_MAX,
82+
collateralLoanPool: collateralLoanPool,
83+
collateralLoanPoolFee: COLLATERAL_LOAN_POOL_FEE,
84+
yieldLoanPool: YIELD_LOAN_POOL,
85+
yieldLoanPoolFee: YIELD_LOAN_POOL_FEE,
86+
marketOracle: MARKET_ORACLE,
87+
marketIrm: MARKET_IRM,
88+
marketLltv: MARKET_LLTV,
89+
yieldOracle: YIELD_ORACLE,
90+
morpho: MORPHO,
91+
swapRouter: SWAP_ROUTER,
92+
owner: owner,
93+
name: "fcmWBTC-fork",
94+
symbol: "fcmWBTC-F"
95+
})
96+
);
97+
vault.setMaxTvl(type(uint256).max);
98+
vault.setMaxSlippageBps(100);
99+
}
100+
101+
function _supplyMorphoLiquidity(uint256 amount) internal {
102+
address supplier = makeAddr("supplier");
103+
deal(address(PYUSD0), supplier, amount);
104+
vm.startPrank(supplier);
105+
PYUSD0.approve(address(MORPHO), type(uint256).max);
106+
MORPHO.supply(mp, amount, 0, supplier, "");
107+
vm.stopPrank();
108+
}
109+
110+
function _fundArb() internal {
111+
vm.prank(owner);
112+
vault.grantEarlyAccess(arb);
113+
deal(address(WBTC), arb, 100_000_000e8);
114+
deal(address(PYUSD0), arb, 100_000_000e6);
115+
deal(address(FUSDEV), arb, 100_000_000e18);
116+
vm.startPrank(arb);
117+
WBTC.approve(address(vault), type(uint256).max);
118+
PYUSD0.approve(address(SWAP_ROUTER), type(uint256).max);
119+
FUSDEV.approve(address(SWAP_ROUTER), type(uint256).max);
120+
vm.stopPrank();
121+
}
122+
123+
function _depositUsers(uint256 nUsers, uint256 depositAmount) internal {
124+
for (uint256 i = 0; i < nUsers; i++) {
125+
address u = makeAddr(string.concat("user", vm.toString(i)));
126+
vault.grantEarlyAccess(u);
127+
deal(address(WBTC), u, depositAmount);
128+
vm.startPrank(u);
129+
WBTC.approve(address(vault), depositAmount);
130+
vault.deposit(depositAmount, u);
131+
vm.stopPrank();
132+
_arbPoolToSpot();
133+
}
134+
}
135+
136+
function setCollateralPrice(uint256 price) internal {
137+
vm.mockCall(MARKET_ORACLE, abi.encodeWithSelector(IOracle.price.selector), abi.encode(price));
138+
}
139+
140+
function setYieldPrice(uint256 price) internal {
141+
vm.mockCall(address(YIELD_ORACLE), abi.encodeWithSelector(IOracle.price.selector), abi.encode(price));
142+
}
143+
144+
function _arbPoolToSpot() internal {
145+
(uint160 currentSpot,,,,,,) = IUniswapV3Pool(YIELD_LOAN_POOL).slot0();
146+
if (currentSpot < cleanSpot) {
147+
vm.prank(arb);
148+
ISwapRouter02(address(SWAP_ROUTER))
149+
.exactInputSingle(
150+
ISwapRouter02.ExactInputSingleParams({
151+
tokenIn: address(FUSDEV),
152+
tokenOut: address(PYUSD0),
153+
fee: YIELD_LOAN_POOL_FEE,
154+
recipient: arb,
155+
amountIn: 1e6,
156+
amountOutMinimum: 0,
157+
sqrtPriceLimitX96: cleanSpot
158+
})
159+
);
160+
} else if (currentSpot > cleanSpot) {
161+
vm.prank(arb);
162+
ISwapRouter02(address(SWAP_ROUTER))
163+
.exactInputSingle(
164+
ISwapRouter02.ExactInputSingleParams({
165+
tokenIn: address(PYUSD0),
166+
tokenOut: address(FUSDEV),
167+
fee: YIELD_LOAN_POOL_FEE,
168+
recipient: arb,
169+
amountIn: 1e6,
170+
amountOutMinimum: 0,
171+
sqrtPriceLimitX96: cleanSpot
172+
})
173+
);
174+
}
175+
}
176+
177+
function _tvlUsd() internal view returns (uint256) {
178+
return Math.mulDiv(vault.totalAssets(), IOracle(MARKET_ORACLE).price(), 1e36);
179+
}
180+
181+
function _hf() internal view returns (uint256) {
182+
Position memory pos = MORPHO.position(marketId, address(vault));
183+
if (pos.borrowShares == 0) return type(uint256).max;
184+
Market memory mkt = MORPHO.market(marketId);
185+
uint256 debt = Math.mulDiv(
186+
uint256(pos.borrowShares),
187+
uint256(mkt.totalBorrowAssets) + 1,
188+
uint256(mkt.totalBorrowShares) + 1e6,
189+
Math.Rounding.Ceil
190+
);
191+
uint256 maxBorrow =
192+
Math.mulDiv(uint256(pos.collateral), Math.mulDiv(IOracle(MARKET_ORACLE).price(), MARKET_LLTV, 1e36), 1e18);
193+
return Math.mulDiv(maxBorrow, 1e18, debt);
194+
}
195+
196+
function _debt() internal view returns (uint256) {
197+
Position memory pos = MORPHO.position(marketId, address(vault));
198+
if (pos.borrowShares == 0) return 0;
199+
Market memory mkt = MORPHO.market(marketId);
200+
return Math.mulDiv(
201+
uint256(pos.borrowShares),
202+
uint256(mkt.totalBorrowAssets) + 1,
203+
uint256(mkt.totalBorrowShares) + 1e6,
204+
Math.Rounding.Ceil
205+
);
206+
}
207+
208+
function _getPool(address factory, address tokenA, address tokenB, uint24 fee) internal view returns (address) {
209+
(bool ok, bytes memory data) = factory.staticcall(abi.encodeWithSelector(0x1698ee82, tokenA, tokenB, fee));
210+
require(ok, "factory call failed");
211+
return abi.decode(data, (address));
212+
}
213+
}

0 commit comments

Comments
 (0)