julien/lovepublic Fork 0
4c8048e
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.

make love an erc20 with public mint

julienbrg committed 2026-09-17T20:22:53+02:00 Browse files
4c8048e parent: 23bcc21
modified script/Love.s.sol +3 -0
@@ -12,6 +12,9 @@ contract LoveScript is Script {
1212
1313 love = new Love();
1414 console.log("Love deployed at", address(love));
15+ console.log("name ", love.name());
16+ console.log("symbol ", love.symbol());
17+ console.log("totalSupply ", love.totalSupply());
1518
1619 vm.stopBroadcast();
1720 }
@@ -12,6 +12,9 @@ contract LoveScript is Script {
12 12
13 love = new Love();13 love = new Love();
14 console.log("Love deployed at", address(love));14 console.log("Love deployed at", address(love));
15+ console.log("name ", love.name());
16+ console.log("symbol ", love.symbol());
17+ console.log("totalSupply ", love.totalSupply());
15 18
16 vm.stopBroadcast();19 vm.stopBroadcast();
17 }20 }
modified src/Love.sol +10 -18
@@ -1,25 +1,17 @@
11 // SPDX-License-Identifier: MIT
22 pragma solidity ^0.8.30;
33
4-/// @title Love
5-/// @notice A minimal on-chain counter of love sent between addresses.
6-contract Love {
7- mapping(address sender => mapping(address recipient => uint256 amount)) public sent;
8- mapping(address recipient => uint256 amount) public received;
9-
10- event LoveSent(address indexed from, address indexed to, uint256 amount);
11-
12- error SelfLove();
13- error ZeroAmount();
4+import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
145
15- /// @notice Send `amount` units of love to `to`.
16- function send(address to, uint256 amount) external {
17- if (to == msg.sender) revert SelfLove();
18- if (amount == 0) revert ZeroAmount();
19-
20- sent[msg.sender][to] += amount;
21- received[to] += amount;
6+/// @title Love
7+/// @notice A basic ERC-20 with an unrestricted mint: anyone can mint any amount
8+/// to any address. Supply is therefore unbounded and meaningless — this
9+/// is a faucet/demo token, not a token with economic value.
10+contract Love is ERC20 {
11+ constructor() ERC20("Love", "LOVE") {}
2212
23- emit LoveSent(msg.sender, to, amount);
13+ /// @notice Mint `amount` tokens to `to`. Callable by anyone, without limit.
14+ function mint(address to, uint256 amount) external {
15+ _mint(to, amount);
2416 }
2517 }
@@ -1,25 +1,17 @@
1 // SPDX-License-Identifier: MIT1 // SPDX-License-Identifier: MIT
2 pragma solidity ^0.8.30;2 pragma solidity ^0.8.30;
3 3
4-/// @title Love4+import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5-/// @notice A minimal on-chain counter of love sent between addresses.
6-contract Love {
7- mapping(address sender => mapping(address recipient => uint256 amount)) public sent;
8- mapping(address recipient => uint256 amount) public received;
9-
10- event LoveSent(address indexed from, address indexed to, uint256 amount);
11-
12- error SelfLove();
13- error ZeroAmount();
14 5
15- /// @notice Send `amount` units of love to `to`.6+/// @title Love
16- function send(address to, uint256 amount) external {7+/// @notice A basic ERC-20 with an unrestricted mint: anyone can mint any amount
17- if (to == msg.sender) revert SelfLove();8+/// to any address. Supply is therefore unbounded and meaningless — this
18- if (amount == 0) revert ZeroAmount();9+/// is a faucet/demo token, not a token with economic value.
19-10+contract Love is ERC20 {
20- sent[msg.sender][to] += amount;11+ constructor() ERC20("Love", "LOVE") {}
21- received[to] += amount;
22 12
23- emit LoveSent(msg.sender, to, amount);13+ /// @notice Mint `amount` tokens to `to`. Callable by anyone, without limit.
14+ function mint(address to, uint256 amount) external {
15+ _mint(to, amount);
24 }16 }
25 }17 }
modified test/Love.t.sol +126 -28
@@ -2,6 +2,8 @@
22 pragma solidity ^0.8.30;
33
44 import {Love} from "../src/Love.sol";
5+import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
6+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
57 import {Test} from "forge-std/Test.sol";
68
79 contract LoveTest is Test {
@@ -9,59 +11,155 @@ contract LoveTest is Test {
911
1012 address alice = makeAddr("alice");
1113 address bob = makeAddr("bob");
12-
13- event LoveSent(address indexed from, address indexed to, uint256 amount);
14+ address stranger = makeAddr("stranger");
1415
1516 function setUp() public {
1617 love = new Love();
1718 }
1819
19- function test_Send() public {
20+ /*//////////////////////////////////////////////////////////////
21+ METADATA
22+ //////////////////////////////////////////////////////////////*/
23+
24+ function test_Metadata() public view {
25+ assertEq(love.name(), "Love");
26+ assertEq(love.symbol(), "LOVE");
27+ assertEq(love.decimals(), 18);
28+ }
29+
30+ function test_InitialSupplyIsZero() public view {
31+ assertEq(love.totalSupply(), 0);
32+ }
33+
34+ /*//////////////////////////////////////////////////////////////
35+ MINT
36+ //////////////////////////////////////////////////////////////*/
37+
38+ function test_Mint() public {
2039 vm.prank(alice);
21- love.send(bob, 1);
40+ love.mint(alice, 100e18);
41+
42+ assertEq(love.balanceOf(alice), 100e18);
43+ assertEq(love.totalSupply(), 100e18);
44+ }
45+
46+ /// @dev The point of this contract: `mint` has no access control.
47+ function test_AnyoneCanMintToAnyone() public {
48+ vm.prank(stranger);
49+ love.mint(bob, 1000e18);
2250
23- assertEq(love.sent(alice, bob), 1);
24- assertEq(love.received(bob), 1);
51+ assertEq(love.balanceOf(bob), 1000e18);
52+ assertEq(love.balanceOf(stranger), 0);
53+ assertEq(love.totalSupply(), 1000e18);
2554 }
2655
27- function test_SendAccumulates() public {
56+ function test_MintEmitsTransferFromZero() public {
57+ vm.expectEmit(true, true, false, true);
58+ emit IERC20.Transfer(address(0), alice, 5e18);
59+
60+ vm.prank(alice);
61+ love.mint(alice, 5e18);
62+ }
63+
64+ function test_MintAccumulates() public {
2865 vm.startPrank(alice);
29- love.send(bob, 2);
30- love.send(bob, 3);
66+ love.mint(alice, 1e18);
67+ love.mint(alice, 2e18);
3168 vm.stopPrank();
3269
33- assertEq(love.sent(alice, bob), 5);
34- assertEq(love.received(bob), 5);
70+ assertEq(love.balanceOf(alice), 3e18);
71+ assertEq(love.totalSupply(), 3e18);
3572 }
3673
37- function test_SendEmitsEvent() public {
38- vm.expectEmit(true, true, false, true);
39- emit LoveSent(alice, bob, 7);
74+ function test_RevertWhen_MintingToZeroAddress() public {
75+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidReceiver.selector, address(0)));
76+ love.mint(address(0), 1e18);
77+ }
78+
79+ function test_RevertWhen_MintOverflowsTotalSupply() public {
80+ love.mint(alice, type(uint256).max);
81+
82+ vm.expectRevert();
83+ love.mint(bob, 1);
84+ }
85+
86+ /*//////////////////////////////////////////////////////////////
87+ TRANSFER
88+ //////////////////////////////////////////////////////////////*/
89+
90+ function test_Transfer() public {
91+ love.mint(alice, 10e18);
92+
93+ vm.prank(alice);
94+ assertTrue(love.transfer(bob, 4e18));
95+
96+ assertEq(love.balanceOf(alice), 6e18);
97+ assertEq(love.balanceOf(bob), 4e18);
98+ }
99+
100+ function test_RevertWhen_TransferExceedsBalance() public {
101+ love.mint(alice, 1e18);
40102
41103 vm.prank(alice);
42- love.send(bob, 7);
104+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 1e18, 2e18));
105+ // forge-lint: disable-next-line(erc20-unchecked-transfer)
106+ love.transfer(bob, 2e18);
43107 }
44108
45- function test_RevertWhen_SendingToSelf() public {
109+ /*//////////////////////////////////////////////////////////////
110+ APPROVE / TRANSFERFROM
111+ //////////////////////////////////////////////////////////////*/
112+
113+ function test_ApproveAndTransferFrom() public {
114+ love.mint(alice, 10e18);
115+
46116 vm.prank(alice);
47- vm.expectRevert(Love.SelfLove.selector);
48- love.send(alice, 1);
117+ assertTrue(love.approve(bob, 6e18));
118+ assertEq(love.allowance(alice, bob), 6e18);
119+
120+ vm.prank(bob);
121+ assertTrue(love.transferFrom(alice, stranger, 6e18));
122+
123+ assertEq(love.balanceOf(alice), 4e18);
124+ assertEq(love.balanceOf(stranger), 6e18);
125+ assertEq(love.allowance(alice, bob), 0);
49126 }
50127
51- function test_RevertWhen_AmountIsZero() public {
128+ function test_RevertWhen_TransferFromExceedsAllowance() public {
129+ love.mint(alice, 10e18);
130+
52131 vm.prank(alice);
53- vm.expectRevert(Love.ZeroAmount.selector);
54- love.send(bob, 0);
132+ love.approve(bob, 1e18);
133+
134+ vm.prank(bob);
135+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, bob, 1e18, 2e18));
136+ // forge-lint: disable-next-line(erc20-unchecked-transfer)
137+ love.transferFrom(alice, stranger, 2e18);
138+ }
139+
140+ /*//////////////////////////////////////////////////////////////
141+ FUZZ
142+ //////////////////////////////////////////////////////////////*/
143+
144+ function testFuzz_MintByAnyCaller(address caller, address to, uint256 amount) public {
145+ vm.assume(to != address(0));
146+
147+ vm.prank(caller);
148+ love.mint(to, amount);
149+
150+ assertEq(love.balanceOf(to), amount);
151+ assertEq(love.totalSupply(), amount);
55152 }
56153
57- function testFuzz_Send(address from, address to, uint256 amount) public {
58- vm.assume(from != to);
59- amount = bound(amount, 1, type(uint256).max);
154+ function testFuzz_Transfer(uint256 minted, uint256 amount) public {
155+ amount = bound(amount, 0, minted);
60156
61- vm.prank(from);
62- love.send(to, amount);
157+ love.mint(alice, minted);
158+
159+ vm.prank(alice);
160+ assertTrue(love.transfer(bob, amount));
63161
64- assertEq(love.sent(from, to), amount);
65- assertEq(love.received(to), amount);
162+ assertEq(love.balanceOf(alice), minted - amount);
163+ assertEq(love.balanceOf(bob), amount);
66164 }
67165 }
@@ -2,6 +2,8 @@
2 pragma solidity ^0.8.30;2 pragma solidity ^0.8.30;
3 3
4 import {Love} from "../src/Love.sol";4 import {Love} from "../src/Love.sol";
5+import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
6+import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5 import {Test} from "forge-std/Test.sol";7 import {Test} from "forge-std/Test.sol";
6 8
7 contract LoveTest is Test {9 contract LoveTest is Test {
@@ -9,59 +11,155 @@ contract LoveTest is Test {
9 11
10 address alice = makeAddr("alice");12 address alice = makeAddr("alice");
11 address bob = makeAddr("bob");13 address bob = makeAddr("bob");
12-14+ address stranger = makeAddr("stranger");
13- event LoveSent(address indexed from, address indexed to, uint256 amount);
14 15
15 function setUp() public {16 function setUp() public {
16 love = new Love();17 love = new Love();
17 }18 }
18 19
19- function test_Send() public {20+ /*//////////////////////////////////////////////////////////////
21+ METADATA
22+ //////////////////////////////////////////////////////////////*/
23+
24+ function test_Metadata() public view {
25+ assertEq(love.name(), "Love");
26+ assertEq(love.symbol(), "LOVE");
27+ assertEq(love.decimals(), 18);
28+ }
29+
30+ function test_InitialSupplyIsZero() public view {
31+ assertEq(love.totalSupply(), 0);
32+ }
33+
34+ /*//////////////////////////////////////////////////////////////
35+ MINT
36+ //////////////////////////////////////////////////////////////*/
37+
38+ function test_Mint() public {
20 vm.prank(alice);39 vm.prank(alice);
21- love.send(bob, 1);40+ love.mint(alice, 100e18);
41+
42+ assertEq(love.balanceOf(alice), 100e18);
43+ assertEq(love.totalSupply(), 100e18);
44+ }
45+
46+ /// @dev The point of this contract: `mint` has no access control.
47+ function test_AnyoneCanMintToAnyone() public {
48+ vm.prank(stranger);
49+ love.mint(bob, 1000e18);
22 50
23- assertEq(love.sent(alice, bob), 1);51+ assertEq(love.balanceOf(bob), 1000e18);
24- assertEq(love.received(bob), 1);52+ assertEq(love.balanceOf(stranger), 0);
53+ assertEq(love.totalSupply(), 1000e18);
25 }54 }
26 55
27- function test_SendAccumulates() public {56+ function test_MintEmitsTransferFromZero() public {
57+ vm.expectEmit(true, true, false, true);
58+ emit IERC20.Transfer(address(0), alice, 5e18);
59+
60+ vm.prank(alice);
61+ love.mint(alice, 5e18);
62+ }
63+
64+ function test_MintAccumulates() public {
28 vm.startPrank(alice);65 vm.startPrank(alice);
29- love.send(bob, 2);66+ love.mint(alice, 1e18);
30- love.send(bob, 3);67+ love.mint(alice, 2e18);
31 vm.stopPrank();68 vm.stopPrank();
32 69
33- assertEq(love.sent(alice, bob), 5);70+ assertEq(love.balanceOf(alice), 3e18);
34- assertEq(love.received(bob), 5);71+ assertEq(love.totalSupply(), 3e18);
35 }72 }
36 73
37- function test_SendEmitsEvent() public {74+ function test_RevertWhen_MintingToZeroAddress() public {
38- vm.expectEmit(true, true, false, true);75+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InvalidReceiver.selector, address(0)));
39- emit LoveSent(alice, bob, 7);76+ love.mint(address(0), 1e18);
77+ }
78+
79+ function test_RevertWhen_MintOverflowsTotalSupply() public {
80+ love.mint(alice, type(uint256).max);
81+
82+ vm.expectRevert();
83+ love.mint(bob, 1);
84+ }
85+
86+ /*//////////////////////////////////////////////////////////////
87+ TRANSFER
88+ //////////////////////////////////////////////////////////////*/
89+
90+ function test_Transfer() public {
91+ love.mint(alice, 10e18);
92+
93+ vm.prank(alice);
94+ assertTrue(love.transfer(bob, 4e18));
95+
96+ assertEq(love.balanceOf(alice), 6e18);
97+ assertEq(love.balanceOf(bob), 4e18);
98+ }
99+
100+ function test_RevertWhen_TransferExceedsBalance() public {
101+ love.mint(alice, 1e18);
40 102
41 vm.prank(alice);103 vm.prank(alice);
42- love.send(bob, 7);104+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, alice, 1e18, 2e18));
105+ // forge-lint: disable-next-line(erc20-unchecked-transfer)
106+ love.transfer(bob, 2e18);
43 }107 }
44 108
45- function test_RevertWhen_SendingToSelf() public {109+ /*//////////////////////////////////////////////////////////////
110+ APPROVE / TRANSFERFROM
111+ //////////////////////////////////////////////////////////////*/
112+
113+ function test_ApproveAndTransferFrom() public {
114+ love.mint(alice, 10e18);
115+
46 vm.prank(alice);116 vm.prank(alice);
47- vm.expectRevert(Love.SelfLove.selector);117+ assertTrue(love.approve(bob, 6e18));
48- love.send(alice, 1);118+ assertEq(love.allowance(alice, bob), 6e18);
119+
120+ vm.prank(bob);
121+ assertTrue(love.transferFrom(alice, stranger, 6e18));
122+
123+ assertEq(love.balanceOf(alice), 4e18);
124+ assertEq(love.balanceOf(stranger), 6e18);
125+ assertEq(love.allowance(alice, bob), 0);
49 }126 }
50 127
51- function test_RevertWhen_AmountIsZero() public {128+ function test_RevertWhen_TransferFromExceedsAllowance() public {
129+ love.mint(alice, 10e18);
130+
52 vm.prank(alice);131 vm.prank(alice);
53- vm.expectRevert(Love.ZeroAmount.selector);132+ love.approve(bob, 1e18);
54- love.send(bob, 0);133+
134+ vm.prank(bob);
135+ vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, bob, 1e18, 2e18));
136+ // forge-lint: disable-next-line(erc20-unchecked-transfer)
137+ love.transferFrom(alice, stranger, 2e18);
138+ }
139+
140+ /*//////////////////////////////////////////////////////////////
141+ FUZZ
142+ //////////////////////////////////////////////////////////////*/
143+
144+ function testFuzz_MintByAnyCaller(address caller, address to, uint256 amount) public {
145+ vm.assume(to != address(0));
146+
147+ vm.prank(caller);
148+ love.mint(to, amount);
149+
150+ assertEq(love.balanceOf(to), amount);
151+ assertEq(love.totalSupply(), amount);
55 }152 }
56 153
57- function testFuzz_Send(address from, address to, uint256 amount) public {154+ function testFuzz_Transfer(uint256 minted, uint256 amount) public {
58- vm.assume(from != to);155+ amount = bound(amount, 0, minted);
59- amount = bound(amount, 1, type(uint256).max);
60 156
61- vm.prank(from);157+ love.mint(alice, minted);
62- love.send(to, amount);158+
159+ vm.prank(alice);
160+ assertTrue(love.transfer(bob, amount));
63 161
64- assertEq(love.sent(from, to), amount);162+ assertEq(love.balanceOf(alice), minted - amount);
65- assertEq(love.received(to), amount);163+ assertEq(love.balanceOf(bob), amount);
66 }164 }
67 }165 }