Skip to content
This repository was archived by the owner on Jan 26, 2024. It is now read-only.

Commit 2fb0aa0

Browse files
authored
working fuzz with updates (#25)
1 parent 6af36da commit 2fb0aa0

File tree

10 files changed

+494
-2
lines changed

10 files changed

+494
-2
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@
2929
[submodule "lib/solady"]
3030
path = lib/solady
3131
url = https://github.com/vectorized/solady
32+
[submodule "lib/murky"]
33+
path = lib/murky
34+
url = https://github.com/dmfxyz/murky

foundry.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,10 @@ remappings = [
1616
'create2-helpers/=lib/create2-clones-with-immutable-args/lib/create2-helpers/src/',
1717
'core/=./src/',
1818
'seaport/=lib/seaport/contracts',
19+
'murky/=lib/murky/src/'
1920
]
20-
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
21+
22+
[fuzz]
23+
runs = 1000
24+
#max_test_rejects = 500000
25+
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

lib/murky

Submodule murky added at 1d9566b

remappings.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ seaport/=lib/seaport/contracts/
1010
seaport-core/=lib/seaport-core/
1111
seaport-types/=lib/seaport-types/
1212
seaport-sol/=lib/seaport-sol/
13+
murky/=lib/murky/src/
1314
core/=./src/
1415

src/test/FuzzTesting.t.sol

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// SPDX-License-Identifier: MIT
2+
import "./TestHelpers.t.sol";
3+
import "./utils/SigUtils.sol";
4+
import {Bound} from "./utils/Bound.sol";
5+
import "murky/Merkle.sol";
6+
import {IERC4626 as ERC4626} from "src/interfaces/IERC4626.sol";
7+
import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol";
8+
9+
//import {CollateralLookup} from "src/libraries/CollateralLookup.sol";
10+
11+
contract AstariaFuzzTest is TestHelpers, SigUtils, Bound {
12+
PublicVault public vault;
13+
bytes32 constant NEW_LIEN_SIG =
14+
0xd03fcb98c0b64b239ccfeed4d62fcf721b2cf2c8ded60319cd2230f80dd2536c;
15+
16+
function setUp() public override {
17+
super.setUp();
18+
19+
vm.warp(100_000);
20+
21+
vm.startPrank(strategistOne);
22+
vault = PublicVault(
23+
payable(
24+
ASTARIA_ROUTER.newPublicVault(
25+
14 days,
26+
strategistTwo,
27+
address(WETH9),
28+
0,
29+
false,
30+
new address[](0),
31+
uint256(0)
32+
)
33+
)
34+
);
35+
vm.stopPrank();
36+
37+
vm.label(address(vault), "PublicVault");
38+
vm.label(address(TRANSFER_PROXY), "TransferProxy");
39+
}
40+
41+
function onERC721Received(
42+
address operator, // operator_
43+
address from, // from_
44+
uint256 tokenId, // tokenId_
45+
bytes calldata data // data_
46+
) public virtual override returns (bytes4) {
47+
return ERC721TokenReceiver.onERC721Received.selector;
48+
}
49+
50+
struct FuzzCommit {
51+
address borrower;
52+
address lender;
53+
uint256 amount;
54+
uint40 duration;
55+
uint40 strategyDuration;
56+
FuzzTerm[] terms;
57+
uint256 termIndex;
58+
}
59+
60+
struct FuzzTerm {
61+
uint256 tokenId;
62+
uint256 maxAmount;
63+
uint256 rate;
64+
uint256 liquidationInitialAsk;
65+
address borrower;
66+
uint40 duration;
67+
bool isBorrowerSpecific;
68+
bool isUnique;
69+
}
70+
71+
struct LoanAssertions {
72+
uint256 borrowerBalance;
73+
uint256 vaultBalance;
74+
uint256 slope;
75+
uint256 liensOpen;
76+
uint256 shares;
77+
}
78+
79+
function boundFuzzTerm(
80+
FuzzTerm memory term
81+
) internal view returns (FuzzTerm memory) {
82+
term.tokenId = _boundNonZero(term.tokenId);
83+
84+
if (term.isBorrowerSpecific) {
85+
term.borrower = _toAddress(_boundMin(_toUint(term.borrower), 100));
86+
} else {
87+
term.borrower = address(0);
88+
}
89+
90+
//term.duration = 3 days;
91+
term.duration = uint40(_bound(term.duration, 1 hours, 365 days));
92+
term.maxAmount = _boundMin(term.maxAmount, vault.minDepositAmount() + 1);
93+
term.rate = _bound(term.rate, 1, ((uint256(1e16) * 200) / (365 days)));
94+
//TODO: bound lia
95+
term.liquidationInitialAsk = type(uint256).max;
96+
97+
return term;
98+
}
99+
100+
function willArithmeticOverflow(
101+
FuzzCommit memory commit,
102+
FuzzTerm memory term
103+
) internal pure returns (bool) {
104+
// mulDivWad requirements
105+
unchecked {
106+
//calculateSlope
107+
if (term.rate > type(uint256).max / commit.amount) {
108+
return true;
109+
}
110+
111+
//getOwed()
112+
if (
113+
term.duration > type(uint256).max / term.rate ||
114+
term.duration * term.rate > type(uint256).max / commit.amount
115+
) {
116+
return true;
117+
}
118+
}
119+
120+
return false;
121+
}
122+
123+
function testFuzzCommitToLien(FuzzCommit memory params) public {
124+
vm.assume(params.terms.length > 1);
125+
126+
//BOUND PARAMS
127+
params.termIndex = _bound(
128+
params.termIndex,
129+
0,
130+
params.terms.length > 100_000 ? 99_999 : params.terms.length - 1
131+
);
132+
133+
params.terms[params.termIndex] = boundFuzzTerm(
134+
params.terms[params.termIndex]
135+
);
136+
137+
params.strategyDuration = uint40(
138+
_boundNonZero(uint256(params.strategyDuration))
139+
);
140+
141+
FuzzTerm memory term = params.terms[params.termIndex];
142+
params.amount = _bound(params.amount, 1, term.maxAmount);
143+
144+
if (term.isBorrowerSpecific) {
145+
params.borrower = term.borrower;
146+
} else {
147+
params.borrower = _toAddress(_boundMin(_toUint(params.borrower), 100));
148+
}
149+
150+
vm.assume(params.borrower != COLLATERAL_TOKEN.getConduit());
151+
vm.assume(!willArithmeticOverflow(params, term));
152+
153+
//BORROWER MINT & APPROVE NFT
154+
vm.startPrank(params.borrower);
155+
156+
TestNFT tokenContract = new TestNFT(0);
157+
tokenContract.mint(address(params.borrower), term.tokenId);
158+
159+
tokenContract.approve(address(ASTARIA_ROUTER), term.tokenId);
160+
161+
vm.stopPrank();
162+
163+
//LEND
164+
vm.deal(address(params.lender), term.maxAmount);
165+
166+
vm.startPrank(address(params.lender));
167+
168+
WETH9.deposit{value: term.maxAmount}();
169+
WETH9.approve(address(ASTARIA_ROUTER), term.maxAmount);
170+
WETH9.approve(address(TRANSFER_PROXY), term.maxAmount);
171+
172+
ASTARIA_ROUTER.depositToVault(
173+
ERC4626(address(vault)),
174+
address(params.lender),
175+
term.maxAmount,
176+
0
177+
);
178+
179+
vm.stopPrank();
180+
LoanAssertions memory before;
181+
{
182+
//GET STRATEGY DETAILS
183+
IAstariaRouter.StrategyDetailsParam
184+
memory strategyDetails = IAstariaRouter.StrategyDetailsParam({
185+
version: uint8(0),
186+
deadline: block.timestamp + params.strategyDuration,
187+
vault: payable(address(vault))
188+
});
189+
190+
//MERKLEIZE
191+
bytes32[] memory data = new bytes32[](
192+
params.terms.length > 100_000 ? 100_000 : params.terms.length
193+
);
194+
195+
bytes memory nlrDetails;
196+
for (uint256 i = 0; i < data.length; i++) {
197+
//TODO: include other validators
198+
IUniqueValidator.Details memory validatorDetails = IUniqueValidator
199+
.Details({
200+
version: uint8(1),
201+
token: address(tokenContract),
202+
tokenId: params.terms[i].tokenId,
203+
borrower: params.terms[i].borrower,
204+
lien: ILienToken.Details({
205+
maxAmount: params.terms[i].maxAmount,
206+
rate: params.terms[i].rate,
207+
duration: params.terms[i].duration,
208+
maxPotentialDebt: 0,
209+
liquidationInitialAsk: params.terms[i].liquidationInitialAsk
210+
})
211+
});
212+
if (i == params.termIndex) {
213+
nlrDetails = abi.encode(validatorDetails);
214+
}
215+
216+
data[i] = keccak256(abi.encode(validatorDetails));
217+
}
218+
219+
Merkle m = new Merkle();
220+
//bytes32 root = m.getRoot(data);
221+
bytes32 strategyHash = getTypedDataHash(
222+
vault.domainSeparator(),
223+
EIP712Message({
224+
nonce: vault.getStrategistNonce(),
225+
root: m.getRoot(data),
226+
deadline: block.timestamp + params.strategyDuration
227+
})
228+
);
229+
230+
IAstariaRouter.NewLienRequest memory lienRequest = IAstariaRouter
231+
.NewLienRequest({
232+
strategy: strategyDetails,
233+
nlrDetails: nlrDetails,
234+
root: m.getRoot(data),
235+
proof: m.getProof(data, params.termIndex),
236+
amount: params.amount,
237+
v: 0,
238+
r: 0,
239+
s: 0
240+
});
241+
242+
(lienRequest.v, lienRequest.r, lienRequest.s) = vm.sign(
243+
strategistOnePK,
244+
strategyHash
245+
);
246+
247+
before = LoanAssertions({
248+
borrowerBalance: params.borrower.balance,
249+
vaultBalance: WETH9.balanceOf(address(vault)),
250+
slope: vault.getSlope(),
251+
liensOpen: 0,
252+
shares: 0
253+
});
254+
255+
(before.liensOpen, ) = vault.getEpochData(
256+
vault.getLienEpoch(uint64(block.timestamp + term.duration))
257+
);
258+
(, , , , , , before.shares) = vault.getPublicVaultState();
259+
260+
vm.prank(params.borrower);
261+
vm.recordLogs();
262+
ASTARIA_ROUTER.commitToLien(
263+
IAstariaRouter.Commitment({
264+
tokenContract: address(tokenContract),
265+
tokenId: term.tokenId,
266+
lienRequest: lienRequest
267+
})
268+
);
269+
}
270+
Vm.Log[] memory logs = vm.getRecordedLogs();
271+
ILienToken.Stack memory stack;
272+
for (uint256 i = 0; i < logs.length; ++i) {
273+
if (logs[i].topics[0] == NEW_LIEN_SIG) {
274+
stack = abi.decode(logs[i].data, (ILienToken.Stack));
275+
}
276+
}
277+
278+
assertEq(tokenContract.ownerOf(term.tokenId), address(COLLATERAL_TOKEN));
279+
280+
assertEq(params.borrower.balance, before.borrowerBalance + params.amount);
281+
282+
assertEq(
283+
WETH9.balanceOf(address(vault)),
284+
before.vaultBalance - params.amount,
285+
"vault balance did not decrease as expected"
286+
);
287+
288+
assertEq(
289+
vault.getSlope(),
290+
before.slope + LIEN_TOKEN.calculateSlope(stack),
291+
"slope did not increase as expected"
292+
);
293+
294+
(uint256 liensAfter, ) = vault.getEpochData(
295+
vault.getLienEpoch(stack.point.end)
296+
);
297+
298+
assertEq(liensAfter, before.liensOpen + 1, "no lien opened for epoch");
299+
300+
assertEq(
301+
COLLATERAL_TOKEN.ownerOf(
302+
CollateralLookup.computeId(address(tokenContract), term.tokenId)
303+
),
304+
params.borrower,
305+
"CT not transferred"
306+
);
307+
308+
assertEq(
309+
LIEN_TOKEN.ownerOf(uint256(keccak256(abi.encode(stack)))),
310+
vault.recipient(),
311+
"LT not issued"
312+
);
313+
}
314+
}
315+
//Test invalid conditions

src/test/InvariantHandler.t.sol

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.17;
4+
5+
//import "./TestHelpers.t.sol";
6+
//import "./FuzzTesting.t.sol";
7+
//import "./utils/SigUtils.sol";
8+
//import {Bound} from "./utils/Bound.sol";
9+
//import "murky/Merkle.sol";
10+
//import {IERC4626 as ERC4626} from "src/interfaces/IERC4626.sol";
11+
//import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol";
12+
13+
contract AstariaInvariantHandler {
14+
// PublicVault public vault;
15+
//
16+
// set up contracts
17+
// pass through to fuzzers
18+
// track loans
19+
// add invariant to ensure all loans can be paid back and liquidated
20+
}

0 commit comments

Comments
 (0)