julien/lovepublic Fork 0
5376202c13fb4103a21f603fecc7fd40680b607b
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 · 377 lines · 11.8 KBSolidity Blame HistoryRaw
add love contract 9bf25a7 julienbrg 11h ago1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.30;
3
4import {Love} from "../src/Love.sol";
add weth peg tests and docs 5376202 julienbrg 9h ago5import {MockWETH} from "./mocks/MockWETH.sol";
make love an erc20 with public mint 4c8048e julienbrg 11h ago6import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
7import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
add love contract 9bf25a7 julienbrg 11h ago8import {Test} from "forge-std/Test.sol";
9
10contract LoveTest is Test {
11 Love public love;
add weth peg tests and docs 5376202 julienbrg 9h ago12 MockWETH public weth;
add love contract 9bf25a7 julienbrg 11h ago13
14 address alice = makeAddr("alice");
15 address bob = makeAddr("bob");
make love an erc20 with public mint 4c8048e julienbrg 11h ago16 address stranger = makeAddr("stranger");
add love contract 9bf25a7 julienbrg 11h ago17
add weth peg tests and docs 5376202 julienbrg 9h ago18 uint256 constant RATE = 100_000;
19
add love contract 9bf25a7 julienbrg 11h ago20 function setUp() public {
add weth peg tests and docs 5376202 julienbrg 9h ago21 weth = new MockWETH();
22 love = new Love(IERC20(address(weth)));
23
24 _fund(alice, 10 ether);
25 _fund(bob, 10 ether);
add love contract 9bf25a7 julienbrg 11h ago26 }
27
make love an erc20 with public mint 4c8048e julienbrg 11h ago28 /*//////////////////////////////////////////////////////////////
29 METADATA
30 //////////////////////////////////////////////////////////////*/
31
32 function test_Metadata() public view {
33 assertEq(love.name(), "Love");
34 assertEq(love.symbol(), "LOVE");
35 assertEq(love.decimals(), 18);
36 }
37
38 function test_InitialSupplyIsZero() public view {
39 assertEq(love.totalSupply(), 0);
40 }
41
add weth peg tests and docs 5376202 julienbrg 9h ago42 function test_PegParameters() public view {
43 assertEq(love.RATE(), RATE);
44 assertEq(address(love.WETH()), address(weth));
45 }
46
47 /// @dev No mint entrypoint may survive: supply moves only through the peg.
48 function test_NoMintFunction() public view {
49 assertEq(address(love).code.length > 0, true);
50 (bool ok,) = address(love).staticcall(abi.encodeWithSignature("mint(address,uint256)", alice, 1e18));
51 assertFalse(ok);
52 }
53
make love an erc20 with public mint 4c8048e julienbrg 11h ago54 /*//////////////////////////////////////////////////////////////
add weth peg tests and docs 5376202 julienbrg 9h ago55 DEPOSIT
make love an erc20 with public mint 4c8048e julienbrg 11h ago56 //////////////////////////////////////////////////////////////*/
57
add weth peg tests and docs 5376202 julienbrg 9h ago58 function test_Deposit() public {
59 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago60
add weth peg tests and docs 5376202 julienbrg 9h ago61 assertEq(love.balanceOf(alice), 1 ether * RATE);
62 assertEq(love.totalSupply(), 1 ether * RATE);
63 assertEq(weth.balanceOf(address(love)), 1 ether);
64 assertEq(weth.balanceOf(alice), 9 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago65 }
66
add weth peg tests and docs 5376202 julienbrg 9h ago67 function test_DepositIsPermissionless() public {
68 _fund(stranger, 1 ether);
69 _deposit(stranger, 1 ether);
70
71 assertEq(love.balanceOf(stranger), 1 ether * RATE);
72 }
73
74 function test_DepositEmitsDeposit() public {
75 vm.prank(alice);
76 weth.approve(address(love), 2 ether);
77
78 vm.expectEmit(true, false, false, true);
79 emit Love.Deposit(alice, 2 ether, 2 ether * RATE);
add love contract 9bf25a7 julienbrg 11h ago80
add weth peg tests and docs 5376202 julienbrg 9h ago81 vm.prank(alice);
82 love.deposit(2 ether);
add love contract 9bf25a7 julienbrg 11h ago83 }
84
add weth peg tests and docs 5376202 julienbrg 9h ago85 function test_DepositEmitsTransferFromZero() public {
86 vm.prank(alice);
87 weth.approve(address(love), 1 ether);
88
make love an erc20 with public mint 4c8048e julienbrg 11h ago89 vm.expectEmit(true, true, false, true);
add weth peg tests and docs 5376202 julienbrg 9h ago90 emit IERC20.Transfer(address(0), alice, 1 ether * RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago91
92 vm.prank(alice);
add weth peg tests and docs 5376202 julienbrg 9h ago93 love.deposit(1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago94 }
95
add weth peg tests and docs 5376202 julienbrg 9h ago96 function test_DepositAccumulates() public {
97 _deposit(alice, 1 ether);
98 _deposit(alice, 3 ether);
add love contract 9bf25a7 julienbrg 11h ago99
add weth peg tests and docs 5376202 julienbrg 9h ago100 assertEq(love.balanceOf(alice), 4 ether * RATE);
101 assertEq(weth.balanceOf(address(love)), 4 ether);
add love contract 9bf25a7 julienbrg 11h ago102 }
103
add weth peg tests and docs 5376202 julienbrg 9h ago104 function test_DepositZero() public {
105 _deposit(alice, 0);
106
107 assertEq(love.balanceOf(alice), 0);
108 assertEq(love.totalSupply(), 0);
make love an erc20 with public mint 4c8048e julienbrg 11h ago109 }
110
add weth peg tests and docs 5376202 julienbrg 9h ago111 function test_RevertWhen_DepositWithoutApproval() public {
112 vm.prank(alice);
113 vm.expectRevert(
114 abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(love), 0, 1 ether)
115 );
116 love.deposit(1 ether);
117 }
118
119 function test_RevertWhen_DepositExceedsWethBalance() public {
120 vm.prank(alice);
121 weth.approve(address(love), 100 ether);
122
123 vm.prank(alice);
124 vm.expectRevert(
125 abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 10 ether, 100 ether)
126 );
127 love.deposit(100 ether);
128 }
make love an erc20 with public mint 4c8048e julienbrg 11h ago129
add weth peg tests and docs 5376202 julienbrg 9h ago130 function test_RevertWhen_DepositOverflows() public {
131 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago132 vm.expectRevert();
add weth peg tests and docs 5376202 julienbrg 9h ago133 love.deposit(type(uint256).max);
134 }
135
136 /*//////////////////////////////////////////////////////////////
137 WITHDRAW
138 //////////////////////////////////////////////////////////////*/
139
140 function test_Withdraw() public {
141 _deposit(alice, 1 ether);
142
143 vm.prank(alice);
144 love.withdraw(1 ether * RATE);
145
146 assertEq(love.balanceOf(alice), 0);
147 assertEq(love.totalSupply(), 0);
148 assertEq(weth.balanceOf(alice), 10 ether);
149 assertEq(weth.balanceOf(address(love)), 0);
150 }
151
152 function test_PartialWithdraw() public {
153 _deposit(alice, 1 ether);
154
155 vm.prank(alice);
156 love.withdraw(0.25 ether * RATE);
157
158 assertEq(love.balanceOf(alice), 0.75 ether * RATE);
159 assertEq(weth.balanceOf(alice), 9.25 ether);
160 assertEq(weth.balanceOf(address(love)), 0.75 ether);
161 }
162
163 function test_WithdrawEmitsWithdraw() public {
164 _deposit(alice, 1 ether);
165
166 vm.expectEmit(true, false, false, true);
167 emit Love.Withdraw(alice, 1 ether * RATE, 1 ether);
168
169 vm.prank(alice);
170 love.withdraw(1 ether * RATE);
171 }
172
173 /// @dev LOVE received from someone else is redeemable all the same.
174 function test_WithdrawAfterTransfer() public {
175 _deposit(alice, 1 ether);
176
177 vm.prank(alice);
178 // forge-lint: disable-next-line(erc20-unchecked-transfer)
179 love.transfer(bob, 1 ether * RATE);
180
181 vm.prank(bob);
182 love.withdraw(1 ether * RATE);
183
184 assertEq(weth.balanceOf(bob), 11 ether);
185 assertEq(love.totalSupply(), 0);
186 }
187
188 function test_WithdrawZero() public {
189 _deposit(alice, 1 ether);
190
191 vm.prank(alice);
192 love.withdraw(0);
193
194 assertEq(love.balanceOf(alice), 1 ether * RATE);
195 }
196
197 /// @dev Anything not a multiple of the rate would round wETH out of the peg.
198 function test_RevertWhen_WithdrawNotDivisibleByRate() public {
199 _deposit(alice, 1 ether);
200
201 vm.prank(alice);
202 vm.expectRevert(abi.encodeWithSelector(Love.AmountNotDivisibleByRate.selector, RATE + 1, RATE));
203 love.withdraw(RATE + 1);
204 }
205
206 function test_RevertWhen_WithdrawExceedsBalance() public {
207 _deposit(alice, 1 ether);
208
209 vm.prank(alice);
210 vm.expectRevert(
211 abi.encodeWithSelector(
212 IERC20Errors.ERC20InsufficientBalance.selector, alice, 1 ether * RATE, 2 ether * RATE
213 )
214 );
215 love.withdraw(2 ether * RATE);
216 }
217
218 /// @dev One holder cannot redeem against another holder's collateral.
219 function test_RevertWhen_WithdrawWithoutDepositing() public {
220 _deposit(alice, 1 ether);
221
222 vm.prank(stranger);
223 vm.expectRevert(
224 abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, stranger, 0, 1 ether * RATE)
225 );
226 love.withdraw(1 ether * RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago227 }
228
229 /*//////////////////////////////////////////////////////////////
230 TRANSFER
231 //////////////////////////////////////////////////////////////*/
232
233 function test_Transfer() public {
add weth peg tests and docs 5376202 julienbrg 9h ago234 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago235
236 vm.prank(alice);
237 assertTrue(love.transfer(bob, 4e18));
238
add weth peg tests and docs 5376202 julienbrg 9h ago239 assertEq(love.balanceOf(alice), 1 ether * RATE - 4e18);
make love an erc20 with public mint 4c8048e julienbrg 11h ago240 assertEq(love.balanceOf(bob), 4e18);
241 }
242
243 function test_RevertWhen_TransferExceedsBalance() public {
add love contract 9bf25a7 julienbrg 11h ago244 vm.prank(alice);
add weth peg tests and docs 5376202 julienbrg 9h ago245 vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 0, 2e18));
make love an erc20 with public mint 4c8048e julienbrg 11h ago246 // forge-lint: disable-next-line(erc20-unchecked-transfer)
247 love.transfer(bob, 2e18);
add love contract 9bf25a7 julienbrg 11h ago248 }
249
make love an erc20 with public mint 4c8048e julienbrg 11h ago250 /*//////////////////////////////////////////////////////////////
251 APPROVE / TRANSFERFROM
252 //////////////////////////////////////////////////////////////*/
253
254 function test_ApproveAndTransferFrom() public {
add weth peg tests and docs 5376202 julienbrg 9h ago255 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago256
add love contract 9bf25a7 julienbrg 11h ago257 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago258 assertTrue(love.approve(bob, 6e18));
259 assertEq(love.allowance(alice, bob), 6e18);
260
261 vm.prank(bob);
262 assertTrue(love.transferFrom(alice, stranger, 6e18));
263
add weth peg tests and docs 5376202 julienbrg 9h ago264 assertEq(love.balanceOf(alice), 1 ether * RATE - 6e18);
make love an erc20 with public mint 4c8048e julienbrg 11h ago265 assertEq(love.balanceOf(stranger), 6e18);
266 assertEq(love.allowance(alice, bob), 0);
add love contract 9bf25a7 julienbrg 11h ago267 }
268
make love an erc20 with public mint 4c8048e julienbrg 11h ago269 function test_RevertWhen_TransferFromExceedsAllowance() public {
add weth peg tests and docs 5376202 julienbrg 9h ago270 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago271
add love contract 9bf25a7 julienbrg 11h ago272 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago273 love.approve(bob, 1e18);
274
275 vm.prank(bob);
276 vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, bob, 1e18, 2e18));
277 // forge-lint: disable-next-line(erc20-unchecked-transfer)
278 love.transferFrom(alice, stranger, 2e18);
279 }
280
add weth peg tests and docs 5376202 julienbrg 9h ago281 /*//////////////////////////////////////////////////////////////
282 PEG
283 //////////////////////////////////////////////////////////////*/
284
285 function test_RoundTripIsLossless() public {
286 uint256 before = weth.balanceOf(alice);
287
288 _deposit(alice, 3 ether);
289 vm.prank(alice);
290 love.withdraw(3 ether * RATE);
291
292 assertEq(weth.balanceOf(alice), before);
293 assertEq(love.balanceOf(alice), 0);
294 assertEq(love.totalSupply(), 0);
295 }
296
297 function test_SupplyStaysFullyBacked() public {
298 _deposit(alice, 1 ether);
299 _deposit(bob, 2 ether);
300
301 assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE);
302
303 vm.prank(alice);
304 love.withdraw(0.5 ether * RATE);
305
306 assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE);
307 }
308
309 /// @dev wETH sent straight to the contract is not claimable — it only lifts
310 /// the backing, it never mints LOVE.
311 function test_DonatedWethDoesNotMint() public {
312 _deposit(alice, 1 ether);
313
314 vm.prank(bob);
315 // forge-lint: disable-next-line(erc20-unchecked-transfer)
316 weth.transfer(address(love), 5 ether);
317
318 assertEq(love.totalSupply(), 1 ether * RATE);
319 assertEq(weth.balanceOf(address(love)), 6 ether);
320 }
321
make love an erc20 with public mint 4c8048e julienbrg 11h ago322 /*//////////////////////////////////////////////////////////////
323 FUZZ
324 //////////////////////////////////////////////////////////////*/
325
add weth peg tests and docs 5376202 julienbrg 9h ago326 function testFuzz_DepositByAnyCaller(address caller, uint256 wethAmount) public {
327 vm.assume(caller != address(0) && caller != address(love) && caller != address(weth));
328 wethAmount = bound(wethAmount, 0, type(uint256).max / RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago329
add weth peg tests and docs 5376202 julienbrg 9h ago330 _fund(caller, wethAmount);
331 _deposit(caller, wethAmount);
make love an erc20 with public mint 4c8048e julienbrg 11h ago332
add weth peg tests and docs 5376202 julienbrg 9h ago333 assertEq(love.balanceOf(caller), wethAmount * RATE);
334 assertEq(love.totalSupply(), wethAmount * RATE);
335 assertEq(weth.balanceOf(address(love)), wethAmount);
add love contract 9bf25a7 julienbrg 11h ago336 }
337
add weth peg tests and docs 5376202 julienbrg 9h ago338 function testFuzz_RoundTrip(uint256 wethAmount) public {
339 wethAmount = bound(wethAmount, 0, type(uint256).max / RATE);
add love contract 9bf25a7 julienbrg 11h ago340
add weth peg tests and docs 5376202 julienbrg 9h ago341 _fund(alice, wethAmount);
342 uint256 before = weth.balanceOf(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago343
add weth peg tests and docs 5376202 julienbrg 9h ago344 _deposit(alice, wethAmount);
make love an erc20 with public mint 4c8048e julienbrg 11h ago345 vm.prank(alice);
add weth peg tests and docs 5376202 julienbrg 9h ago346 love.withdraw(wethAmount * RATE);
347
348 assertEq(weth.balanceOf(alice), before);
349 assertEq(love.totalSupply(), 0);
350 }
351
352 function testFuzz_WithdrawRevertsUnlessDivisible(uint256 loveAmount) public {
353 _deposit(alice, 10 ether);
add love contract 9bf25a7 julienbrg 11h ago354
add weth peg tests and docs 5376202 julienbrg 9h ago355 vm.prank(alice);
356 if (loveAmount % RATE == 0 && loveAmount <= 10 ether * RATE) {
357 love.withdraw(loveAmount);
358 assertEq(weth.balanceOf(address(love)), 10 ether - loveAmount / RATE);
359 } else {
360 vm.expectRevert();
361 love.withdraw(loveAmount);
362 }
363 }
364
365 function _fund(address account, uint256 amount) internal {
366 vm.deal(account, amount);
367 vm.prank(account);
368 weth.deposit{value: amount}();
369 }
370
371 function _deposit(address account, uint256 wethAmount) internal {
372 vm.startPrank(account);
373 weth.approve(address(love), wethAmount);
374 love.deposit(wethAmount);
375 vm.stopPrank();
add love contract 9bf25a7 julienbrg 11h ago376 }
377}