julien/lovepublic Fork 0
3cc0f3b8ce3eedfacdb7d55793fef3cdf53e13f7
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 · 386 lines · 12.3 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
add natspec 3cc0f3b julienbrg 9h ago10/// @title LoveTest
11/// @notice Exercises the peg end to end against a WETH9 stand-in: minting on
12/// deposit, redemption on withdraw, the divisibility rule, and the
13/// invariant that supply always equals the wETH backing times the rate.
add love contract 9bf25a7 julienbrg 11h ago14contract LoveTest is Test {
add natspec 3cc0f3b julienbrg 9h ago15 /// @notice The token under test.
add love contract 9bf25a7 julienbrg 11h ago16 Love public love;
add natspec 3cc0f3b julienbrg 9h ago17
18 /// @notice The wETH it is pegged to.
add weth peg tests and docs 5376202 julienbrg 9h ago19 MockWETH public weth;
add love contract 9bf25a7 julienbrg 11h ago20
21 address alice = makeAddr("alice");
22 address bob = makeAddr("bob");
make love an erc20 with public mint 4c8048e julienbrg 11h ago23 address stranger = makeAddr("stranger");
add love contract 9bf25a7 julienbrg 11h ago24
add weth peg tests and docs 5376202 julienbrg 9h ago25 uint256 constant RATE = 100_000;
26
add love contract 9bf25a7 julienbrg 11h ago27 function setUp() public {
add weth peg tests and docs 5376202 julienbrg 9h ago28 weth = new MockWETH();
29 love = new Love(IERC20(address(weth)));
30
31 _fund(alice, 10 ether);
32 _fund(bob, 10 ether);
add love contract 9bf25a7 julienbrg 11h ago33 }
34
make love an erc20 with public mint 4c8048e julienbrg 11h ago35 /*//////////////////////////////////////////////////////////////
36 METADATA
37 //////////////////////////////////////////////////////////////*/
38
39 function test_Metadata() public view {
40 assertEq(love.name(), "Love");
41 assertEq(love.symbol(), "LOVE");
42 assertEq(love.decimals(), 18);
43 }
44
45 function test_InitialSupplyIsZero() public view {
46 assertEq(love.totalSupply(), 0);
47 }
48
add weth peg tests and docs 5376202 julienbrg 9h ago49 function test_PegParameters() public view {
50 assertEq(love.RATE(), RATE);
51 assertEq(address(love.WETH()), address(weth));
52 }
53
54 /// @dev No mint entrypoint may survive: supply moves only through the peg.
55 function test_NoMintFunction() public view {
56 assertEq(address(love).code.length > 0, true);
57 (bool ok,) = address(love).staticcall(abi.encodeWithSignature("mint(address,uint256)", alice, 1e18));
58 assertFalse(ok);
59 }
60
make love an erc20 with public mint 4c8048e julienbrg 11h ago61 /*//////////////////////////////////////////////////////////////
add weth peg tests and docs 5376202 julienbrg 9h ago62 DEPOSIT
make love an erc20 with public mint 4c8048e julienbrg 11h ago63 //////////////////////////////////////////////////////////////*/
64
add weth peg tests and docs 5376202 julienbrg 9h ago65 function test_Deposit() public {
66 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago67
add weth peg tests and docs 5376202 julienbrg 9h ago68 assertEq(love.balanceOf(alice), 1 ether * RATE);
69 assertEq(love.totalSupply(), 1 ether * RATE);
70 assertEq(weth.balanceOf(address(love)), 1 ether);
71 assertEq(weth.balanceOf(alice), 9 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago72 }
73
add weth peg tests and docs 5376202 julienbrg 9h ago74 function test_DepositIsPermissionless() public {
75 _fund(stranger, 1 ether);
76 _deposit(stranger, 1 ether);
77
78 assertEq(love.balanceOf(stranger), 1 ether * RATE);
79 }
80
81 function test_DepositEmitsDeposit() public {
82 vm.prank(alice);
83 weth.approve(address(love), 2 ether);
84
85 vm.expectEmit(true, false, false, true);
86 emit Love.Deposit(alice, 2 ether, 2 ether * RATE);
add love contract 9bf25a7 julienbrg 11h ago87
add weth peg tests and docs 5376202 julienbrg 9h ago88 vm.prank(alice);
89 love.deposit(2 ether);
add love contract 9bf25a7 julienbrg 11h ago90 }
91
add weth peg tests and docs 5376202 julienbrg 9h ago92 function test_DepositEmitsTransferFromZero() public {
93 vm.prank(alice);
94 weth.approve(address(love), 1 ether);
95
make love an erc20 with public mint 4c8048e julienbrg 11h ago96 vm.expectEmit(true, true, false, true);
add weth peg tests and docs 5376202 julienbrg 9h ago97 emit IERC20.Transfer(address(0), alice, 1 ether * RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago98
99 vm.prank(alice);
add weth peg tests and docs 5376202 julienbrg 9h ago100 love.deposit(1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago101 }
102
add weth peg tests and docs 5376202 julienbrg 9h ago103 function test_DepositAccumulates() public {
104 _deposit(alice, 1 ether);
105 _deposit(alice, 3 ether);
add love contract 9bf25a7 julienbrg 11h ago106
add weth peg tests and docs 5376202 julienbrg 9h ago107 assertEq(love.balanceOf(alice), 4 ether * RATE);
108 assertEq(weth.balanceOf(address(love)), 4 ether);
add love contract 9bf25a7 julienbrg 11h ago109 }
110
add weth peg tests and docs 5376202 julienbrg 9h ago111 function test_DepositZero() public {
112 _deposit(alice, 0);
113
114 assertEq(love.balanceOf(alice), 0);
115 assertEq(love.totalSupply(), 0);
make love an erc20 with public mint 4c8048e julienbrg 11h ago116 }
117
add weth peg tests and docs 5376202 julienbrg 9h ago118 function test_RevertWhen_DepositWithoutApproval() public {
119 vm.prank(alice);
120 vm.expectRevert(
121 abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, address(love), 0, 1 ether)
122 );
123 love.deposit(1 ether);
124 }
125
126 function test_RevertWhen_DepositExceedsWethBalance() public {
127 vm.prank(alice);
128 weth.approve(address(love), 100 ether);
129
130 vm.prank(alice);
131 vm.expectRevert(
132 abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 10 ether, 100 ether)
133 );
134 love.deposit(100 ether);
135 }
make love an erc20 with public mint 4c8048e julienbrg 11h ago136
add weth peg tests and docs 5376202 julienbrg 9h ago137 function test_RevertWhen_DepositOverflows() public {
138 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago139 vm.expectRevert();
add weth peg tests and docs 5376202 julienbrg 9h ago140 love.deposit(type(uint256).max);
141 }
142
143 /*//////////////////////////////////////////////////////////////
144 WITHDRAW
145 //////////////////////////////////////////////////////////////*/
146
147 function test_Withdraw() public {
148 _deposit(alice, 1 ether);
149
150 vm.prank(alice);
151 love.withdraw(1 ether * RATE);
152
153 assertEq(love.balanceOf(alice), 0);
154 assertEq(love.totalSupply(), 0);
155 assertEq(weth.balanceOf(alice), 10 ether);
156 assertEq(weth.balanceOf(address(love)), 0);
157 }
158
159 function test_PartialWithdraw() public {
160 _deposit(alice, 1 ether);
161
162 vm.prank(alice);
163 love.withdraw(0.25 ether * RATE);
164
165 assertEq(love.balanceOf(alice), 0.75 ether * RATE);
166 assertEq(weth.balanceOf(alice), 9.25 ether);
167 assertEq(weth.balanceOf(address(love)), 0.75 ether);
168 }
169
170 function test_WithdrawEmitsWithdraw() public {
171 _deposit(alice, 1 ether);
172
173 vm.expectEmit(true, false, false, true);
174 emit Love.Withdraw(alice, 1 ether * RATE, 1 ether);
175
176 vm.prank(alice);
177 love.withdraw(1 ether * RATE);
178 }
179
180 /// @dev LOVE received from someone else is redeemable all the same.
181 function test_WithdrawAfterTransfer() public {
182 _deposit(alice, 1 ether);
183
184 vm.prank(alice);
185 // forge-lint: disable-next-line(erc20-unchecked-transfer)
186 love.transfer(bob, 1 ether * RATE);
187
188 vm.prank(bob);
189 love.withdraw(1 ether * RATE);
190
191 assertEq(weth.balanceOf(bob), 11 ether);
192 assertEq(love.totalSupply(), 0);
193 }
194
195 function test_WithdrawZero() public {
196 _deposit(alice, 1 ether);
197
198 vm.prank(alice);
199 love.withdraw(0);
200
201 assertEq(love.balanceOf(alice), 1 ether * RATE);
202 }
203
204 /// @dev Anything not a multiple of the rate would round wETH out of the peg.
205 function test_RevertWhen_WithdrawNotDivisibleByRate() public {
206 _deposit(alice, 1 ether);
207
208 vm.prank(alice);
209 vm.expectRevert(abi.encodeWithSelector(Love.AmountNotDivisibleByRate.selector, RATE + 1, RATE));
210 love.withdraw(RATE + 1);
211 }
212
213 function test_RevertWhen_WithdrawExceedsBalance() public {
214 _deposit(alice, 1 ether);
215
216 vm.prank(alice);
217 vm.expectRevert(
218 abi.encodeWithSelector(
219 IERC20Errors.ERC20InsufficientBalance.selector, alice, 1 ether * RATE, 2 ether * RATE
220 )
221 );
222 love.withdraw(2 ether * RATE);
223 }
224
225 /// @dev One holder cannot redeem against another holder's collateral.
226 function test_RevertWhen_WithdrawWithoutDepositing() public {
227 _deposit(alice, 1 ether);
228
229 vm.prank(stranger);
230 vm.expectRevert(
231 abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, stranger, 0, 1 ether * RATE)
232 );
233 love.withdraw(1 ether * RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago234 }
235
236 /*//////////////////////////////////////////////////////////////
237 TRANSFER
238 //////////////////////////////////////////////////////////////*/
239
240 function test_Transfer() public {
add weth peg tests and docs 5376202 julienbrg 9h ago241 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago242
243 vm.prank(alice);
244 assertTrue(love.transfer(bob, 4e18));
245
add weth peg tests and docs 5376202 julienbrg 9h ago246 assertEq(love.balanceOf(alice), 1 ether * RATE - 4e18);
make love an erc20 with public mint 4c8048e julienbrg 11h ago247 assertEq(love.balanceOf(bob), 4e18);
248 }
249
250 function test_RevertWhen_TransferExceedsBalance() public {
add love contract 9bf25a7 julienbrg 11h ago251 vm.prank(alice);
add weth peg tests and docs 5376202 julienbrg 9h ago252 vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 0, 2e18));
make love an erc20 with public mint 4c8048e julienbrg 11h ago253 // forge-lint: disable-next-line(erc20-unchecked-transfer)
254 love.transfer(bob, 2e18);
add love contract 9bf25a7 julienbrg 11h ago255 }
256
make love an erc20 with public mint 4c8048e julienbrg 11h ago257 /*//////////////////////////////////////////////////////////////
258 APPROVE / TRANSFERFROM
259 //////////////////////////////////////////////////////////////*/
260
261 function test_ApproveAndTransferFrom() public {
add weth peg tests and docs 5376202 julienbrg 9h ago262 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago263
add love contract 9bf25a7 julienbrg 11h ago264 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago265 assertTrue(love.approve(bob, 6e18));
266 assertEq(love.allowance(alice, bob), 6e18);
267
268 vm.prank(bob);
269 assertTrue(love.transferFrom(alice, stranger, 6e18));
270
add weth peg tests and docs 5376202 julienbrg 9h ago271 assertEq(love.balanceOf(alice), 1 ether * RATE - 6e18);
make love an erc20 with public mint 4c8048e julienbrg 11h ago272 assertEq(love.balanceOf(stranger), 6e18);
273 assertEq(love.allowance(alice, bob), 0);
add love contract 9bf25a7 julienbrg 11h ago274 }
275
make love an erc20 with public mint 4c8048e julienbrg 11h ago276 function test_RevertWhen_TransferFromExceedsAllowance() public {
add weth peg tests and docs 5376202 julienbrg 9h ago277 _deposit(alice, 1 ether);
make love an erc20 with public mint 4c8048e julienbrg 11h ago278
add love contract 9bf25a7 julienbrg 11h ago279 vm.prank(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago280 love.approve(bob, 1e18);
281
282 vm.prank(bob);
283 vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, bob, 1e18, 2e18));
284 // forge-lint: disable-next-line(erc20-unchecked-transfer)
285 love.transferFrom(alice, stranger, 2e18);
286 }
287
add weth peg tests and docs 5376202 julienbrg 9h ago288 /*//////////////////////////////////////////////////////////////
289 PEG
290 //////////////////////////////////////////////////////////////*/
291
292 function test_RoundTripIsLossless() public {
293 uint256 before = weth.balanceOf(alice);
294
295 _deposit(alice, 3 ether);
296 vm.prank(alice);
297 love.withdraw(3 ether * RATE);
298
299 assertEq(weth.balanceOf(alice), before);
300 assertEq(love.balanceOf(alice), 0);
301 assertEq(love.totalSupply(), 0);
302 }
303
304 function test_SupplyStaysFullyBacked() public {
305 _deposit(alice, 1 ether);
306 _deposit(bob, 2 ether);
307
308 assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE);
309
310 vm.prank(alice);
311 love.withdraw(0.5 ether * RATE);
312
313 assertEq(love.totalSupply(), weth.balanceOf(address(love)) * RATE);
314 }
315
316 /// @dev wETH sent straight to the contract is not claimable — it only lifts
317 /// the backing, it never mints LOVE.
318 function test_DonatedWethDoesNotMint() public {
319 _deposit(alice, 1 ether);
320
321 vm.prank(bob);
322 // forge-lint: disable-next-line(erc20-unchecked-transfer)
323 weth.transfer(address(love), 5 ether);
324
325 assertEq(love.totalSupply(), 1 ether * RATE);
326 assertEq(weth.balanceOf(address(love)), 6 ether);
327 }
328
make love an erc20 with public mint 4c8048e julienbrg 11h ago329 /*//////////////////////////////////////////////////////////////
330 FUZZ
331 //////////////////////////////////////////////////////////////*/
332
add weth peg tests and docs 5376202 julienbrg 9h ago333 function testFuzz_DepositByAnyCaller(address caller, uint256 wethAmount) public {
334 vm.assume(caller != address(0) && caller != address(love) && caller != address(weth));
335 wethAmount = bound(wethAmount, 0, type(uint256).max / RATE);
make love an erc20 with public mint 4c8048e julienbrg 11h ago336
add weth peg tests and docs 5376202 julienbrg 9h ago337 _fund(caller, wethAmount);
338 _deposit(caller, wethAmount);
make love an erc20 with public mint 4c8048e julienbrg 11h ago339
add weth peg tests and docs 5376202 julienbrg 9h ago340 assertEq(love.balanceOf(caller), wethAmount * RATE);
341 assertEq(love.totalSupply(), wethAmount * RATE);
342 assertEq(weth.balanceOf(address(love)), wethAmount);
add love contract 9bf25a7 julienbrg 11h ago343 }
344
add weth peg tests and docs 5376202 julienbrg 9h ago345 function testFuzz_RoundTrip(uint256 wethAmount) public {
346 wethAmount = bound(wethAmount, 0, type(uint256).max / RATE);
add love contract 9bf25a7 julienbrg 11h ago347
add weth peg tests and docs 5376202 julienbrg 9h ago348 _fund(alice, wethAmount);
349 uint256 before = weth.balanceOf(alice);
make love an erc20 with public mint 4c8048e julienbrg 11h ago350
add weth peg tests and docs 5376202 julienbrg 9h ago351 _deposit(alice, wethAmount);
make love an erc20 with public mint 4c8048e julienbrg 11h ago352 vm.prank(alice);
add weth peg tests and docs 5376202 julienbrg 9h ago353 love.withdraw(wethAmount * RATE);
354
355 assertEq(weth.balanceOf(alice), before);
356 assertEq(love.totalSupply(), 0);
357 }
358
359 function testFuzz_WithdrawRevertsUnlessDivisible(uint256 loveAmount) public {
360 _deposit(alice, 10 ether);
add love contract 9bf25a7 julienbrg 11h ago361
add weth peg tests and docs 5376202 julienbrg 9h ago362 vm.prank(alice);
363 if (loveAmount % RATE == 0 && loveAmount <= 10 ether * RATE) {
364 love.withdraw(loveAmount);
365 assertEq(weth.balanceOf(address(love)), 10 ether - loveAmount / RATE);
366 } else {
367 vm.expectRevert();
368 love.withdraw(loveAmount);
369 }
370 }
371
add natspec 3cc0f3b julienbrg 9h ago372 /// @dev Gives `account` `amount` wETH by wrapping fresh ETH.
add weth peg tests and docs 5376202 julienbrg 9h ago373 function _fund(address account, uint256 amount) internal {
374 vm.deal(account, amount);
375 vm.prank(account);
376 weth.deposit{value: amount}();
377 }
378
add natspec 3cc0f3b julienbrg 9h ago379 /// @dev Approves and deposits `wethAmount` as `account`, in one step.
add weth peg tests and docs 5376202 julienbrg 9h ago380 function _deposit(address account, uint256 wethAmount) internal {
381 vm.startPrank(account);
382 weth.approve(address(love), wethAmount);
383 love.deposit(wethAmount);
384 vm.stopPrank();
add love contract 9bf25a7 julienbrg 11h ago385 }
386}