julien/lovepublic Fork 0
2f484804347452fadfddc5422faa26b86a55b077
Commits
Clone
git clone https://git.rickub.com/julien/love.git
git clone ssh://git@rickub.com/julien/love.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Love.t.sol · 391 lines · 12.7 KBSolidity Blame HistoryRaw
add love contract 9bf25a7 julienbrg 11h ago1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.30;
3
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago4import {IWETH} from "../src/IWETH.sol";
add love contract 9bf25a7 julienbrg 11h ago5import {Love} from "../src/Love.sol";
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago6import {Fixtures} from "./Fixtures.sol";
make love an erc20 with public mint 4c8048e julienbrg 11h ago7import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
8import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
add love contract 9bf25a7 julienbrg 11h ago9import {Test} from "forge-std/Test.sol";
10
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago11/// @title LoveTest
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago12/// @notice Exercises the peg end to end against real WETH9 bytecode: minting on
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago13/// deposit, redemption on withdraw, the divisibility rule, and the
14/// invariant that supply always equals the wETH backing times the rate.
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago15contract LoveTest is Test, Fixtures {
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago16 /// @notice The token under test.
add love contract 9bf25a7 julienbrg 11h ago17 Love public love;
18
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago19 /// @notice The wETH it is pegged to, etched from a live chain.
20 IWETH public weth;
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago21
add love contract 9bf25a7 julienbrg 11h ago22 address alice = makeAddr("alice");
23 address bob = makeAddr("bob");
make love an erc20 with public mint 4c8048e julienbrg 11h ago24 address stranger = makeAddr("stranger");
add love contract 9bf25a7 julienbrg 11h ago25
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago26 uint256 constant RATE = 100_000;
27
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago28 /// @dev The OP Stack predeploy address, so the setup mirrors a real chain.
29 address constant WETH_AT = 0x4200000000000000000000000000000000000006;
30
add love contract 9bf25a7 julienbrg 11h ago31 function setUp() public {
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago32 (, weth) = setUpChain(WETH_AT, WETH9_OP_LEGACY);
33 love = new Love();
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago34
35 _fund(alice, 10 ether);
36 _fund(bob, 10 ether);
add love contract 9bf25a7 julienbrg 11h ago37 }
38
make love an erc20 with public mint 4c8048e julienbrg 11h ago39 /*//////////////////////////////////////////////////////////////
40 METADATA
41 //////////////////////////////////////////////////////////////*/
42
43 function test_Metadata() public view {
44 assertEq(love.name(), "Love");
45 assertEq(love.symbol(), "LOVE");
46 assertEq(love.decimals(), 18);
47 }
48
49 function test_InitialSupplyIsZero() public view {
50 assertEq(love.totalSupply(), 0);
51 }
52
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago53 function test_PegParameters() public view {
54 assertEq(love.RATE(), RATE);
55 assertEq(address(love.WETH()), address(weth));
56 }
57
58 /// @dev No mint entrypoint may survive: supply moves only through the peg.
59 function test_NoMintFunction() public view {
60 assertEq(address(love).code.length > 0, true);
61 (bool ok,) = address(love).staticcall(abi.encodeWithSignature("mint(address,uint256)", alice, 1e18));
62 assertFalse(ok);
63 }
64
make love an erc20 with public mint 4c8048e julienbrg 11h ago65 /*//////////////////////////////////////////////////////////////
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago66 DEPOSIT
make love an erc20 with public mint 4c8048e julienbrg 11h ago67 //////////////////////////////////////////////////////////////*/
68
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago69 function test_Deposit() public {
70 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago71
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago72 assertEq(love.balanceOf(alice), 1 ether * RATE);
73 assertEq(love.totalSupply(), 1 ether * RATE);
74 assertEq(weth.balanceOf(address(love)), 1 ether);
75 assertEq(weth.balanceOf(alice), 9 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago76 }
77
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago78 function test_DepositIsPermissionless() public {
79 _fund(stranger, 1 ether);
80 _deposit(stranger, 1 ether);
add love contract 9bf25a7 julienbrg 11h ago81
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago82 assertEq(love.balanceOf(stranger), 1 ether * RATE);
add love contract 9bf25a7 julienbrg 11h ago83 }
84
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago85 function test_DepositEmitsDeposit() public {
86 vm.prank(alice);
87 weth.approve(address(love), 2 ether);
88
89 vm.expectEmit(true, false, false, true);
90 emit Love.Deposit(alice, 2 ether, 2 ether * RATE);
91
92 vm.prank(alice);
93 love.deposit(2 ether);
94 }
95
96 function test_DepositEmitsTransferFromZero() public {
97 vm.prank(alice);
98 weth.approve(address(love), 1 ether);
99
make love an erc20 with public mint 4c8048e julienbrg 11h ago100 vm.expectEmit(true, true, false, true);
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago101 emit IERC20.Transfer(address(0), alice, 1 ether * RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago102
103 vm.prank(alice);
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago104 love.deposit(1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago105 }
106
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago107 function test_DepositAccumulates() public {
108 _deposit(alice, 1 ether);
109 _deposit(alice, 3 ether);
add love contract 9bf25a7 julienbrg 11h ago110
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago111 assertEq(love.balanceOf(alice), 4 ether * RATE);
112 assertEq(weth.balanceOf(address(love)), 4 ether);
add love contract 9bf25a7 julienbrg 11h ago113 }
114
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago115 function test_DepositZero() public {
116 _deposit(alice, 0);
117
118 assertEq(love.balanceOf(alice), 0);
119 assertEq(love.totalSupply(), 0);
make love an erc20 with public mint 4c8048e julienbrg 11h ago120 }
121
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago122 /// @dev WETH9 predates custom errors and guards `transferFrom` with a bare
123 /// `require`, so a shortfall comes back as a revert carrying no data
124 /// at all rather than as a typed ERC-20 error. Asserting the empty
125 /// data keeps that documented: anything integrating with LOVE has a
126 /// reason to fail, but not a machine-readable one.
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago127 function test_RevertWhen_DepositWithoutApproval() public {
128 vm.prank(alice);
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago129 vm.expectRevert(bytes(""));
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago130 love.deposit(1 ether);
131 }
132
133 function test_RevertWhen_DepositExceedsWethBalance() public {
134 vm.prank(alice);
135 weth.approve(address(love), 100 ether);
136
137 vm.prank(alice);
read weth from the registry instead of a constructor arg ffa4716 julienbrg 8h ago138 vm.expectRevert(bytes(""));
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago139 love.deposit(100 ether);
140 }
make love an erc20 with public mint 4c8048e julienbrg 11h ago141
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago142 function test_RevertWhen_DepositOverflows() public {
143 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago144 vm.expectRevert();
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago145 love.deposit(type(uint256).max);
146 }
147
148 /*//////////////////////////////////////////////////////////////
149 WITHDRAW
150 //////////////////////////////////////////////////////////////*/
151
152 function test_Withdraw() public {
153 _deposit(alice, 1 ether);
154
155 vm.prank(alice);
156 love.withdraw(1 ether * RATE);
157
158 assertEq(love.balanceOf(alice), 0);
159 assertEq(love.totalSupply(), 0);
160 assertEq(weth.balanceOf(alice), 10 ether);
161 assertEq(weth.balanceOf(address(love)), 0);
162 }
163
164 function test_PartialWithdraw() public {
165 _deposit(alice, 1 ether);
166
167 vm.prank(alice);
168 love.withdraw(0.25 ether * RATE);
169
170 assertEq(love.balanceOf(alice), 0.75 ether * RATE);
171 assertEq(weth.balanceOf(alice), 9.25 ether);
172 assertEq(weth.balanceOf(address(love)), 0.75 ether);
173 }
174
175 function test_WithdrawEmitsWithdraw() public {
176 _deposit(alice, 1 ether);
177
178 vm.expectEmit(true, false, false, true);
179 emit Love.Withdraw(alice, 1 ether * RATE, 1 ether);
180
181 vm.prank(alice);
182 love.withdraw(1 ether * RATE);
183 }
184
185 /// @dev LOVE received from someone else is redeemable all the same.
186 function test_WithdrawAfterTransfer() public {
187 _deposit(alice, 1 ether);
188
189 vm.prank(alice);
190 // forge-lint: disable-next-line(erc20-unchecked-transfer)
191 love.transfer(bob, 1 ether * RATE);
192
193 vm.prank(bob);
194 love.withdraw(1 ether * RATE);
195
196 assertEq(weth.balanceOf(bob), 11 ether);
197 assertEq(love.totalSupply(), 0);
198 }
199
200 function test_WithdrawZero() public {
201 _deposit(alice, 1 ether);
202
203 vm.prank(alice);
204 love.withdraw(0);
205
206 assertEq(love.balanceOf(alice), 1 ether * RATE);
207 }
208
209 /// @dev Anything not a multiple of the rate would round wETH out of the peg.
210 function test_RevertWhen_WithdrawNotDivisibleByRate() public {
211 _deposit(alice, 1 ether);
212
213 vm.prank(alice);
214 vm.expectRevert(abi.encodeWithSelector(Love.AmountNotDivisibleByRate.selector, RATE + 1, RATE));
215 love.withdraw(RATE + 1);
216 }
217
218 function test_RevertWhen_WithdrawExceedsBalance() public {
219 _deposit(alice, 1 ether);
220
221 vm.prank(alice);
222 vm.expectRevert(
223 abi.encodeWithSelector(
224 IERC20Errors.ERC20InsufficientBalance.selector, alice, 1 ether * RATE, 2 ether * RATE
225 )
226 );
227 love.withdraw(2 ether * RATE);
228 }
229
230 /// @dev One holder cannot redeem against another holder's collateral.
231 function test_RevertWhen_WithdrawWithoutDepositing() public {
232 _deposit(alice, 1 ether);
233
234 vm.prank(stranger);
235 vm.expectRevert(
236 abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, stranger, 0, 1 ether * RATE)
237 );
238 love.withdraw(1 ether * RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago239 }
240
241 /*//////////////////////////////////////////////////////////////
242 TRANSFER
243 //////////////////////////////////////////////////////////////*/
244
245 function test_Transfer() public {
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago246 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago247
248 vm.prank(alice);
249 assertTrue(love.transfer(bob, 4e18));
250
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago251 assertEq(love.balanceOf(alice), 1 ether * RATE - 4e18);
make love an erc20 with public mint 4c8048e julienbrg 11h ago252 assertEq(love.balanceOf(bob), 4e18);
253 }
254
255 function test_RevertWhen_TransferExceedsBalance() public {
add love contract 9bf25a7 julienbrg 11h ago256 vm.prank(alice);
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago257 vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 0, 2e18));
make love an erc20 with public mint 4c8048e julienbrg 11h ago258 // forge-lint: disable-next-line(erc20-unchecked-transfer)
259 love.transfer(bob, 2e18);
add love contract 9bf25a7 julienbrg 11h ago260 }
261
make love an erc20 with public mint 4c8048e julienbrg 11h ago262 /*//////////////////////////////////////////////////////////////
263 APPROVE / TRANSFERFROM
264 //////////////////////////////////////////////////////////////*/
265
266 function test_ApproveAndTransferFrom() public {
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago267 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago268
add love contract 9bf25a7 julienbrg 11h ago269 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago270 assertTrue(love.approve(bob, 6e18));
271 assertEq(love.allowance(alice, bob), 6e18);
272
273 vm.prank(bob);
274 assertTrue(love.transferFrom(alice, stranger, 6e18));
275
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago276 assertEq(love.balanceOf(alice), 1 ether * RATE - 6e18);
make love an erc20 with public mint 4c8048e julienbrg 11h ago277 assertEq(love.balanceOf(stranger), 6e18);
278 assertEq(love.allowance(alice, bob), 0);
add love contract 9bf25a7 julienbrg 11h ago279 }
280
make love an erc20 with public mint 4c8048e julienbrg 11h ago281 function test_RevertWhen_TransferFromExceedsAllowance() public {
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago282 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago283
add love contract 9bf25a7 julienbrg 11h ago284 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago285 love.approve(bob, 1e18);
286
287 vm.prank(bob);
288 vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, bob, 1e18, 2e18));
289 // forge-lint: disable-next-line(erc20-unchecked-transfer)
290 love.transferFrom(alice, stranger, 2e18);
291 }
292
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago293 /*//////////////////////////////////////////////////////////////
294 PEG
295 //////////////////////////////////////////////////////////////*/
296
297 function test_RoundTripIsLossless() public {
298 uint256 before = weth.balanceOf(alice);
299
300 _deposit(alice, 3 ether);
301 vm.prank(alice);
302 love.withdraw(3 ether * RATE);
303
304 assertEq(weth.balanceOf(alice), before);
305 assertEq(love.balanceOf(alice), 0);
306 assertEq(love.totalSupply(), 0);
307 }
308
309 function test_SupplyStaysFullyBacked() public {
310 _deposit(alice, 1 ether);
311 _deposit(bob, 2 ether);
312
313 assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE);
314
315 vm.prank(alice);
316 love.withdraw(0.5 ether * RATE);
317
318 assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE);
319 }
320
321 /// @dev wETH sent straight to the contract is not claimable — it only lifts
322 /// the backing, it never mints LOVE.
323 function test_DonatedWethDoesNotMint() public {
324 _deposit(alice, 1 ether);
325
326 vm.prank(bob);
327 // forge-lint: disable-next-line(erc20-unchecked-transfer)
328 weth.transfer(address(love), 5 ether);
329
330 assertEq(love.totalSupply(), 1 ether * RATE);
331 assertEq(weth.balanceOf(address(love)), 6 ether);
332 }
333
make love an erc20 with public mint 4c8048e julienbrg 11h ago334 /*//////////////////////////////////////////////////////////////
335 FUZZ
336 //////////////////////////////////////////////////////////////*/
337
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago338 function testFuzz_DepositByAnyCaller(address caller, uint256 wethAmount) public {
339 vm.assume(caller != address(0) && caller != address(love) && caller != address(weth));
340 wethAmount = bound(wethAmount, 0, type(uint256).max / RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago341
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago342 _fund(caller, wethAmount);
343 _deposit(caller, wethAmount);
make love an erc20 with public mint 4c8048e julienbrg 11h ago344
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago345 assertEq(love.balanceOf(caller), wethAmount * RATE);
346 assertEq(love.totalSupply(), wethAmount * RATE);
347 assertEq(weth.balanceOf(address(love)), wethAmount);
add love contract 9bf25a7 julienbrg 11h ago348 }
349
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago350 function testFuzz_RoundTrip(uint256 wethAmount) public {
351 wethAmount = bound(wethAmount, 0, type(uint256).max / RATE);
add love contract 9bf25a7 julienbrg 11h ago352
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago353 _fund(alice, wethAmount);
354 uint256 before = weth.balanceOf(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago355
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago356 _deposit(alice, wethAmount);
make love an erc20 with public mint 4c8048e julienbrg 11h ago357 vm.prank(alice);
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago358 love.withdraw(wethAmount * RATE);
359
360 assertEq(weth.balanceOf(alice), before);
361 assertEq(love.totalSupply(), 0);
362 }
363
364 function testFuzz_WithdrawRevertsUnlessDivisible(uint256 loveAmount) public {
365 _deposit(alice, 10 ether);
add love contract 9bf25a7 julienbrg 11h ago366
Merge 3-peg-love-to-weth into main ce13509 rickub 9h ago367 vm.prank(alice);
368 if (loveAmount % RATE == 0 && loveAmount <= 10 ether * RATE) {
369 love.withdraw(loveAmount);
370 assertEq(weth.balanceOf(address(love)), 10 ether - loveAmount / RATE);
371 } else {
372 vm.expectRevert();
373 love.withdraw(loveAmount);
374 }
375 }
376
377 /// @dev Gives `account` `amount` wETH by wrapping fresh ETH.
378 function _fund(address account, uint256 amount) internal {
379 vm.deal(account, amount);
380 vm.prank(account);
381 weth.deposit{value: amount}();
382 }
383
384 /// @dev Approves and deposits `wethAmount` as `account`, in one step.
385 function _deposit(address account, uint256 wethAmount) internal {
386 vm.startPrank(account);
387 weth.approve(address(love), wethAmount);
388 love.deposit(wethAmount);
389 vm.stopPrank();
add love contract 9bf25a7 julienbrg 11h ago390 }
391}