julien/lovepublic Fork 0
3cc0f3b
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.

add natspec

julienbrg committed 2026-09-17T22:08:00+02:00 Browse files
3cc0f3b parent: 5376202
modified script/Love.s.sol +24 -0
@@ -5,6 +5,8 @@ import {Love} from "../src/Love.sol";
55 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
66 import {Script, console} from "forge-std/Script.sol";
77
8+/// @title LoveScript
9+/// @author Julien Béranger
810 /// @notice Deploys `Love` with CREATE2 through the canonical deterministic
911 /// deployer. The address depends on the salt and the creation code, and
1012 /// the creation code embeds the wETH address — so the token only gets
@@ -12,13 +14,20 @@ import {Script, console} from "forge-std/Script.sol";
1214 /// `solc`, the optimizer settings and `bytecode_hash = "none"` as they
1315 /// are in `foundry.toml`, or the address changes too.
1416 contract LoveScript is Script {
17+ /// @notice Salt used when `SALT` is not set in the environment.
1518 bytes32 public constant DEFAULT_SALT = keccak256("LOVE");
1619
20+ /// @notice wETH address used when `WETH` is not set in the environment.
1721 /// @dev wETH on Base, Optimism and every other OP-Stack chain.
1822 address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006;
1923
24+ /// @notice The token, once `run()` has deployed or found it.
2025 Love public love;
2126
27+ /// @notice Deploy `Love`, or return it untouched if it is already there.
28+ /// @dev Reads `SALT` and `WETH` from the environment, falling back to the
29+ /// defaults, and asserts the deployed address matches the prediction.
30+ /// @return The deployed token.
2231 function run() public returns (Love) {
2332 bytes32 s = salt();
2433 address w = wethAddress();
@@ -53,6 +62,7 @@ contract LoveScript is Script {
5362 }
5463
5564 /// @notice Print the address `run()` would deploy to, without broadcasting.
65+ /// @return predicted The address the current salt and wETH derive to.
5666 function predict() public view returns (address predicted) {
5767 bytes32 s = salt();
5868 address w = wethAddress();
@@ -64,24 +74,38 @@ contract LoveScript is Script {
6474 console.log("predicted ", predicted);
6575 }
6676
77+ /// @notice Derive the deployment address from a salt and a wETH address.
78+ /// @param s The CREATE2 salt.
79+ /// @param w The wETH the token would be pegged to.
80+ /// @return The address `Love` would land at.
6781 function predict(bytes32 s, address w) public pure returns (address) {
6882 return vm.computeCreate2Address(s, initCodeHash(w), CREATE2_FACTORY);
6983 }
7084
85+ /// @notice The creation code CREATE2 is handed, constructor argument included.
86+ /// @param w The wETH the token would be pegged to.
87+ /// @return The creation code, with `w` ABI-encoded onto it.
7188 function initCode(address w) public pure returns (bytes memory) {
7289 return abi.encodePacked(type(Love).creationCode, abi.encode(w));
7390 }
7491
92+ /// @notice Hash of the creation code, the second input to the address derivation.
93+ /// @param w The wETH the token would be pegged to.
94+ /// @return The keccak256 of `initCode(w)`.
7595 function initCodeHash(address w) public pure returns (bytes32) {
7696 return keccak256(initCode(w));
7797 }
7898
99+ /// @notice The salt to deploy with.
79100 /// @dev `SALT` overrides the default, e.g. to mine a vanity address.
101+ /// @return The configured salt, or `DEFAULT_SALT`.
80102 function salt() public view returns (bytes32) {
81103 return vm.envOr("SALT", DEFAULT_SALT);
82104 }
83105
106+ /// @notice The wETH address to peg the deployment to.
84107 /// @dev `WETH` overrides the default, which only holds on OP-Stack chains.
108+ /// @return The configured wETH address, or `DEFAULT_WETH`.
85109 function wethAddress() public view returns (address) {
86110 return vm.envOr("WETH", DEFAULT_WETH);
87111 }
@@ -5,6 +5,8 @@ import {Love} from "../src/Love.sol";
5 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";5 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6 import {Script, console} from "forge-std/Script.sol";6 import {Script, console} from "forge-std/Script.sol";
7 7
8+/// @title LoveScript
9+/// @author Julien Béranger
8 /// @notice Deploys `Love` with CREATE2 through the canonical deterministic10 /// @notice Deploys `Love` with CREATE2 through the canonical deterministic
9 /// deployer. The address depends on the salt and the creation code, and11 /// deployer. The address depends on the salt and the creation code, and
10 /// the creation code embeds the wETH address — so the token only gets12 /// the creation code embeds the wETH address — so the token only gets
@@ -12,13 +14,20 @@ import {Script, console} from "forge-std/Script.sol";
12 /// `solc`, the optimizer settings and `bytecode_hash = "none"` as they14 /// `solc`, the optimizer settings and `bytecode_hash = "none"` as they
13 /// are in `foundry.toml`, or the address changes too.15 /// are in `foundry.toml`, or the address changes too.
14 contract LoveScript is Script {16 contract LoveScript is Script {
17+ /// @notice Salt used when `SALT` is not set in the environment.
15 bytes32 public constant DEFAULT_SALT = keccak256("LOVE");18 bytes32 public constant DEFAULT_SALT = keccak256("LOVE");
16 19
20+ /// @notice wETH address used when `WETH` is not set in the environment.
17 /// @dev wETH on Base, Optimism and every other OP-Stack chain.21 /// @dev wETH on Base, Optimism and every other OP-Stack chain.
18 address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006;22 address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006;
19 23
24+ /// @notice The token, once `run()` has deployed or found it.
20 Love public love;25 Love public love;
21 26
27+ /// @notice Deploy `Love`, or return it untouched if it is already there.
28+ /// @dev Reads `SALT` and `WETH` from the environment, falling back to the
29+ /// defaults, and asserts the deployed address matches the prediction.
30+ /// @return The deployed token.
22 function run() public returns (Love) {31 function run() public returns (Love) {
23 bytes32 s = salt();32 bytes32 s = salt();
24 address w = wethAddress();33 address w = wethAddress();
@@ -53,6 +62,7 @@ contract LoveScript is Script {
53 }62 }
54 63
55 /// @notice Print the address `run()` would deploy to, without broadcasting.64 /// @notice Print the address `run()` would deploy to, without broadcasting.
65+ /// @return predicted The address the current salt and wETH derive to.
56 function predict() public view returns (address predicted) {66 function predict() public view returns (address predicted) {
57 bytes32 s = salt();67 bytes32 s = salt();
58 address w = wethAddress();68 address w = wethAddress();
@@ -64,24 +74,38 @@ contract LoveScript is Script {
64 console.log("predicted ", predicted);74 console.log("predicted ", predicted);
65 }75 }
66 76
77+ /// @notice Derive the deployment address from a salt and a wETH address.
78+ /// @param s The CREATE2 salt.
79+ /// @param w The wETH the token would be pegged to.
80+ /// @return The address `Love` would land at.
67 function predict(bytes32 s, address w) public pure returns (address) {81 function predict(bytes32 s, address w) public pure returns (address) {
68 return vm.computeCreate2Address(s, initCodeHash(w), CREATE2_FACTORY);82 return vm.computeCreate2Address(s, initCodeHash(w), CREATE2_FACTORY);
69 }83 }
70 84
85+ /// @notice The creation code CREATE2 is handed, constructor argument included.
86+ /// @param w The wETH the token would be pegged to.
87+ /// @return The creation code, with `w` ABI-encoded onto it.
71 function initCode(address w) public pure returns (bytes memory) {88 function initCode(address w) public pure returns (bytes memory) {
72 return abi.encodePacked(type(Love).creationCode, abi.encode(w));89 return abi.encodePacked(type(Love).creationCode, abi.encode(w));
73 }90 }
74 91
92+ /// @notice Hash of the creation code, the second input to the address derivation.
93+ /// @param w The wETH the token would be pegged to.
94+ /// @return The keccak256 of `initCode(w)`.
75 function initCodeHash(address w) public pure returns (bytes32) {95 function initCodeHash(address w) public pure returns (bytes32) {
76 return keccak256(initCode(w));96 return keccak256(initCode(w));
77 }97 }
78 98
99+ /// @notice The salt to deploy with.
79 /// @dev `SALT` overrides the default, e.g. to mine a vanity address.100 /// @dev `SALT` overrides the default, e.g. to mine a vanity address.
101+ /// @return The configured salt, or `DEFAULT_SALT`.
80 function salt() public view returns (bytes32) {102 function salt() public view returns (bytes32) {
81 return vm.envOr("SALT", DEFAULT_SALT);103 return vm.envOr("SALT", DEFAULT_SALT);
82 }104 }
83 105
106+ /// @notice The wETH address to peg the deployment to.
84 /// @dev `WETH` overrides the default, which only holds on OP-Stack chains.107 /// @dev `WETH` overrides the default, which only holds on OP-Stack chains.
108+ /// @return The configured wETH address, or `DEFAULT_WETH`.
85 function wethAddress() public view returns (address) {109 function wethAddress() public view returns (address) {
86 return vm.envOr("WETH", DEFAULT_WETH);110 return vm.envOr("WETH", DEFAULT_WETH);
87 }111 }
modified src/Love.sol +31 -2
@@ -6,27 +6,53 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
66 import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
77
88 /// @title Love
9+/// @author Julien Béranger
910 /// @notice An ERC-20 pegged to wETH at a fixed rate of 100000 LOVE per wETH.
1011 /// Every LOVE in circulation is backed by wETH held by this contract:
1112 /// supply only moves through `deposit` and `withdraw`, and both are
1213 /// permissionless.
14+/// @dev There is no mint entrypoint, no owner and no upgrade path, so the peg
15+/// cannot be diluted. `totalSupply() == WETH.balanceOf(address(this)) * RATE`
16+/// holds after every call; wETH transferred straight to this contract
17+/// raises the backing and is not redeemable.
1318 contract Love is ERC20 {
1419 using SafeERC20 for IERC20;
1520
21+ /// @notice LOVE minted per unit of wETH, and burned per unit released.
1622 uint256 public constant RATE = 100_000;
1723
24+ /// @notice The wETH this token is pegged to and collateralised with.
25+ /// @dev Immutable, and part of the creation code — two chains only give this
26+ /// contract the same CREATE2 address if they share a wETH address.
1827 IERC20 public immutable WETH;
1928
29+ /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`.
30+ /// @param loveAmount The rejected LOVE amount.
31+ /// @param rate The rate it has to be a multiple of.
2032 error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate);
2133
34+ /// @notice Emitted when wETH is locked and LOVE minted.
35+ /// @param account The depositor, who pays the wETH and receives the LOVE.
36+ /// @param wethAmount The wETH pulled in.
37+ /// @param loveAmount The LOVE minted, `wethAmount * RATE`.
2238 event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount);
39+
40+ /// @notice Emitted when LOVE is burned and wETH released.
41+ /// @param account The redeemer, who burns the LOVE and receives the wETH.
42+ /// @param loveAmount The LOVE burned.
43+ /// @param wethAmount The wETH released, `loveAmount / RATE`.
2344 event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount);
2445
46+ /// @param weth_ The wETH to peg to. Set once, never changed.
2547 constructor(IERC20 weth_) ERC20("Love", "LOVE") {
2648 WETH = weth_;
2749 }
2850
2951 /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller.
52+ /// @dev The caller must have approved this contract for `wethAmount` first.
53+ /// Reverts on overflow of `wethAmount * RATE`, and on the wETH transfer
54+ /// failing for want of balance or allowance.
55+ /// @param wethAmount The wETH to lock, in wei.
3056 function deposit(uint256 wethAmount) external {
3157 uint256 loveAmount = wethAmount * RATE;
3258
@@ -37,8 +63,11 @@ contract Love is ERC20 {
3763 }
3864
3965 /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller.
40- /// @dev Reverts unless `loveAmount` is a multiple of `RATE`, so the peg never
41- /// rounds against the caller or the remaining holders.
66+ /// @dev Reverts with `AmountNotDivisibleByRate` unless `loveAmount` is a
67+ /// multiple of `RATE`, so the peg never rounds against the caller or
68+ /// the remaining holders. LOVE is burned before the wETH leaves, and
69+ /// the burn already caps the amount at the caller's balance.
70+ /// @param loveAmount The LOVE to burn, a multiple of `RATE`.
4271 function withdraw(uint256 loveAmount) external {
4372 if (loveAmount % RATE != 0) revert AmountNotDivisibleByRate(loveAmount, RATE);
4473
@@ -6,27 +6,53 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6 import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";6 import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7 7
8 /// @title Love8 /// @title Love
9+/// @author Julien Béranger
9 /// @notice An ERC-20 pegged to wETH at a fixed rate of 100000 LOVE per wETH.10 /// @notice An ERC-20 pegged to wETH at a fixed rate of 100000 LOVE per wETH.
10 /// Every LOVE in circulation is backed by wETH held by this contract:11 /// Every LOVE in circulation is backed by wETH held by this contract:
11 /// supply only moves through `deposit` and `withdraw`, and both are12 /// supply only moves through `deposit` and `withdraw`, and both are
12 /// permissionless.13 /// permissionless.
14+/// @dev There is no mint entrypoint, no owner and no upgrade path, so the peg
15+/// cannot be diluted. `totalSupply() == WETH.balanceOf(address(this)) * RATE`
16+/// holds after every call; wETH transferred straight to this contract
17+/// raises the backing and is not redeemable.
13 contract Love is ERC20 {18 contract Love is ERC20 {
14 using SafeERC20 for IERC20;19 using SafeERC20 for IERC20;
15 20
21+ /// @notice LOVE minted per unit of wETH, and burned per unit released.
16 uint256 public constant RATE = 100_000;22 uint256 public constant RATE = 100_000;
17 23
24+ /// @notice The wETH this token is pegged to and collateralised with.
25+ /// @dev Immutable, and part of the creation code — two chains only give this
26+ /// contract the same CREATE2 address if they share a wETH address.
18 IERC20 public immutable WETH;27 IERC20 public immutable WETH;
19 28
29+ /// @notice Thrown when a withdrawal amount is not a multiple of `RATE`.
30+ /// @param loveAmount The rejected LOVE amount.
31+ /// @param rate The rate it has to be a multiple of.
20 error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate);32 error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate);
21 33
34+ /// @notice Emitted when wETH is locked and LOVE minted.
35+ /// @param account The depositor, who pays the wETH and receives the LOVE.
36+ /// @param wethAmount The wETH pulled in.
37+ /// @param loveAmount The LOVE minted, `wethAmount * RATE`.
22 event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount);38 event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount);
39+
40+ /// @notice Emitted when LOVE is burned and wETH released.
41+ /// @param account The redeemer, who burns the LOVE and receives the wETH.
42+ /// @param loveAmount The LOVE burned.
43+ /// @param wethAmount The wETH released, `loveAmount / RATE`.
23 event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount);44 event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount);
24 45
46+ /// @param weth_ The wETH to peg to. Set once, never changed.
25 constructor(IERC20 weth_) ERC20("Love", "LOVE") {47 constructor(IERC20 weth_) ERC20("Love", "LOVE") {
26 WETH = weth_;48 WETH = weth_;
27 }49 }
28 50
29 /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller.51 /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller.
52+ /// @dev The caller must have approved this contract for `wethAmount` first.
53+ /// Reverts on overflow of `wethAmount * RATE`, and on the wETH transfer
54+ /// failing for want of balance or allowance.
55+ /// @param wethAmount The wETH to lock, in wei.
30 function deposit(uint256 wethAmount) external {56 function deposit(uint256 wethAmount) external {
31 uint256 loveAmount = wethAmount * RATE;57 uint256 loveAmount = wethAmount * RATE;
32 58
@@ -37,8 +63,11 @@ contract Love is ERC20 {
37 }63 }
38 64
39 /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller.65 /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller.
40- /// @dev Reverts unless `loveAmount` is a multiple of `RATE`, so the peg never66+ /// @dev Reverts with `AmountNotDivisibleByRate` unless `loveAmount` is a
41- /// rounds against the caller or the remaining holders.67+ /// multiple of `RATE`, so the peg never rounds against the caller or
68+ /// the remaining holders. LOVE is burned before the wETH leaves, and
69+ /// the burn already caps the amount at the caller's balance.
70+ /// @param loveAmount The LOVE to burn, a multiple of `RATE`.
42 function withdraw(uint256 loveAmount) external {71 function withdraw(uint256 loveAmount) external {
43 if (loveAmount % RATE != 0) revert AmountNotDivisibleByRate(loveAmount, RATE);72 if (loveAmount % RATE != 0) revert AmountNotDivisibleByRate(loveAmount, RATE);
44 73
modified test/Love.t.sol +9 -0
@@ -7,8 +7,15 @@ import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.so
77 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
88 import {Test} from "forge-std/Test.sol";
99
10+/// @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.
1014 contract LoveTest is Test {
15+ /// @notice The token under test.
1116 Love public love;
17+
18+ /// @notice The wETH it is pegged to.
1219 MockWETH public weth;
1320
1421 address alice = makeAddr("alice");
@@ -362,12 +369,14 @@ contract LoveTest is Test {
362369 }
363370 }
364371
372+ /// @dev Gives `account` `amount` wETH by wrapping fresh ETH.
365373 function _fund(address account, uint256 amount) internal {
366374 vm.deal(account, amount);
367375 vm.prank(account);
368376 weth.deposit{value: amount}();
369377 }
370378
379+ /// @dev Approves and deposits `wethAmount` as `account`, in one step.
371380 function _deposit(address account, uint256 wethAmount) internal {
372381 vm.startPrank(account);
373382 weth.approve(address(love), wethAmount);
@@ -7,8 +7,15 @@ import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.so
7 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";7 import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8 import {Test} from "forge-std/Test.sol";8 import {Test} from "forge-std/Test.sol";
9 9
10+/// @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.
10 contract LoveTest is Test {14 contract LoveTest is Test {
15+ /// @notice The token under test.
11 Love public love;16 Love public love;
17+
18+ /// @notice The wETH it is pegged to.
12 MockWETH public weth;19 MockWETH public weth;
13 20
14 address alice = makeAddr("alice");21 address alice = makeAddr("alice");
@@ -362,12 +369,14 @@ contract LoveTest is Test {
362 }369 }
363 }370 }
364 371
372+ /// @dev Gives `account` `amount` wETH by wrapping fresh ETH.
365 function _fund(address account, uint256 amount) internal {373 function _fund(address account, uint256 amount) internal {
366 vm.deal(account, amount);374 vm.deal(account, amount);
367 vm.prank(account);375 vm.prank(account);
368 weth.deposit{value: amount}();376 weth.deposit{value: amount}();
369 }377 }
370 378
379+ /// @dev Approves and deposits `wethAmount` as `account`, in one step.
371 function _deposit(address account, uint256 wethAmount) internal {380 function _deposit(address account, uint256 wethAmount) internal {
372 vm.startPrank(account);381 vm.startPrank(account);
373 weth.approve(address(love), wethAmount);382 weth.approve(address(love), wethAmount);
modified test/LoveCreate2.t.sol +2 -0
@@ -5,6 +5,7 @@ import {LoveScript} from "../script/Love.s.sol";
55 import {Love} from "../src/Love.sol";
66 import {Test} from "forge-std/Test.sol";
77
8+/// @title LoveCreate2Test
89 /// @notice The deployment address derives from the salt, the creation code and
910 /// the wETH constructor argument. These tests pin that derivation: same
1011 /// salt and same wETH give the same address on any chain, a different
@@ -79,6 +80,7 @@ contract LoveCreate2Test is Test {
7980 assertEq(script.predict(salt, weth), _deploy(salt, weth));
8081 }
8182
83+ /// @dev Deploys through the canonical deterministic deployer, as the script does.
8284 function _deploy(bytes32 salt, address weth) internal returns (address deployed) {
8385 (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode(weth)));
8486 require(ok, "create2 deployment failed");
@@ -5,6 +5,7 @@ import {LoveScript} from "../script/Love.s.sol";
5 import {Love} from "../src/Love.sol";5 import {Love} from "../src/Love.sol";
6 import {Test} from "forge-std/Test.sol";6 import {Test} from "forge-std/Test.sol";
7 7
8+/// @title LoveCreate2Test
8 /// @notice The deployment address derives from the salt, the creation code and9 /// @notice The deployment address derives from the salt, the creation code and
9 /// the wETH constructor argument. These tests pin that derivation: same10 /// the wETH constructor argument. These tests pin that derivation: same
10 /// salt and same wETH give the same address on any chain, a different11 /// salt and same wETH give the same address on any chain, a different
@@ -79,6 +80,7 @@ contract LoveCreate2Test is Test {
79 assertEq(script.predict(salt, weth), _deploy(salt, weth));80 assertEq(script.predict(salt, weth), _deploy(salt, weth));
80 }81 }
81 82
83+ /// @dev Deploys through the canonical deterministic deployer, as the script does.
82 function _deploy(bytes32 salt, address weth) internal returns (address deployed) {84 function _deploy(bytes32 salt, address weth) internal returns (address deployed) {
83 (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode(weth)));85 (bool ok, bytes memory ret) = CREATE2_FACTORY.call(abi.encodePacked(salt, script.initCode(weth)));
84 require(ok, "create2 deployment failed");86 require(ok, "create2 deployment failed");
modified test/mocks/MockWETH.sol +5 -0
@@ -3,19 +3,24 @@ pragma solidity ^0.8.30;
33
44 import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
55
6+/// @title MockWETH
67 /// @notice Minimal stand-in for WETH9: wraps ETH 1:1, same 18 decimals, same
78 /// `deposit`/`withdraw` entrypoints. Test-only.
89 contract MockWETH is ERC20 {
910 constructor() ERC20("Wrapped Ether", "WETH") {}
1011
12+ /// @notice Wrap any ETH sent with no calldata.
1113 receive() external payable {
1214 deposit();
1315 }
1416
17+ /// @notice Wrap the ETH sent with this call, minting the same amount of wETH.
1518 function deposit() public payable {
1619 _mint(msg.sender, msg.value);
1720 }
1821
22+ /// @notice Burn `amount` wETH and send the caller the same amount of ETH.
23+ /// @param amount The wETH to unwrap, in wei.
1924 function withdraw(uint256 amount) external {
2025 _burn(msg.sender, amount);
2126 payable(msg.sender).transfer(amount);
@@ -3,19 +3,24 @@ pragma solidity ^0.8.30;
3 3
4 import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";4 import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5 5
6+/// @title MockWETH
6 /// @notice Minimal stand-in for WETH9: wraps ETH 1:1, same 18 decimals, same7 /// @notice Minimal stand-in for WETH9: wraps ETH 1:1, same 18 decimals, same
7 /// `deposit`/`withdraw` entrypoints. Test-only.8 /// `deposit`/`withdraw` entrypoints. Test-only.
8 contract MockWETH is ERC20 {9 contract MockWETH is ERC20 {
9 constructor() ERC20("Wrapped Ether", "WETH") {}10 constructor() ERC20("Wrapped Ether", "WETH") {}
10 11
12+ /// @notice Wrap any ETH sent with no calldata.
11 receive() external payable {13 receive() external payable {
12 deposit();14 deposit();
13 }15 }
14 16
17+ /// @notice Wrap the ETH sent with this call, minting the same amount of wETH.
15 function deposit() public payable {18 function deposit() public payable {
16 _mint(msg.sender, msg.value);19 _mint(msg.sender, msg.value);
17 }20 }
18 21
22+ /// @notice Burn `amount` wETH and send the caller the same amount of ETH.
23+ /// @param amount The wETH to unwrap, in wei.
19 function withdraw(uint256 amount) external {24 function withdraw(uint256 amount) external {
20 _burn(msg.sender, amount);25 _burn(msg.sender, amount);
21 payable(msg.sender).transfer(amount);26 payable(msg.sender).transfer(amount);