Skip to content

Commit e6235bf

Browse files
committed
finalize tests
1 parent 27e45ca commit e6235bf

File tree

5 files changed

+1333
-5
lines changed

5 files changed

+1333
-5
lines changed

src/hooks/actions/ERC20Mint/ERC20Mint.sol

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ contract ERC20Mint is RegistryOnchainAction {
2222
ERRORS
2323
//////////////////////////////////////////////////////////////*/
2424

25-
error MaxSupplyExceeded();
2625
error InvalidTokensPerUnit();
2726

2827
/*//////////////////////////////////////////////////////////////
@@ -83,8 +82,8 @@ contract ERC20Mint is RegistryOnchainAction {
8382
(bool success,) =
8483
address(tokenData_.token).call(abi.encodeWithSelector(tokenData_.token.mint.selector, buyer, tokensToMint));
8584

86-
if (tokenData_.revertOnMaxSupplyReached) {
87-
if (!success) revert MaxSupplyExceeded();
85+
if (success) {
86+
// Do nothing, just silence the warning
8887
}
8988
}
9089

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {RegistryOnchainAction, RegistryOnchainActionTest} from "@test/utils/RegistryOnchainActionTest.sol";
5+
import {ERC20Mint} from "@/hooks/actions/ERC20Mint/ERC20Mint.sol";
6+
import {ERC20Data} from "@/hooks/actions/ERC20Mint/types/ERC20Data.sol";
7+
import {ERC20Mint_BaseToken} from "@/hooks/actions/ERC20Mint/utils/ERC20Mint_BaseToken.sol";
8+
9+
import {console2} from "forge-std/console2.sol";
10+
11+
uint256 constant slicerId = 0;
12+
13+
contract ERC20MintTest is RegistryOnchainActionTest {
14+
ERC20Mint erc20Mint;
15+
16+
uint256[] productIds = [1, 2, 3, 4];
17+
18+
function setUp() public {
19+
erc20Mint = new ERC20Mint(PRODUCTS_MODULE);
20+
_setHook(address(erc20Mint));
21+
}
22+
23+
function testConfigureProduct() public {
24+
vm.startPrank(productOwner);
25+
26+
// Configure product 1: Standard token with max supply
27+
erc20Mint.configureProduct(
28+
slicerId,
29+
productIds[0],
30+
abi.encode(
31+
"Test Token 1", // name
32+
"TT1", // symbol
33+
1000, // premintAmount
34+
productOwner, // premintReceiver
35+
true, // revertOnMaxSupplyReached
36+
10000, // maxSupply
37+
100 // tokensPerUnit
38+
)
39+
);
40+
41+
// Configure product 2: Token without max supply limit
42+
erc20Mint.configureProduct(
43+
slicerId,
44+
productIds[1],
45+
abi.encode(
46+
"Test Token 2", // name
47+
"TT2", // symbol
48+
0, // premintAmount (no premint)
49+
address(0), // premintReceiver
50+
false, // revertOnMaxSupplyReached
51+
0, // maxSupply (unlimited)
52+
50 // tokensPerUnit
53+
)
54+
);
55+
56+
// Configure product 3: Token with revert on max supply
57+
erc20Mint.configureProduct(
58+
slicerId,
59+
productIds[2],
60+
abi.encode(
61+
"Test Token 3", // name
62+
"TT3", // symbol
63+
500, // premintAmount
64+
buyer, // premintReceiver
65+
true, // revertOnMaxSupplyReached
66+
1000, // maxSupply
67+
1 // tokensPerUnit
68+
)
69+
);
70+
71+
vm.stopPrank();
72+
73+
// Verify tokenData is set correctly
74+
(ERC20Mint_BaseToken token1, bool revertOnMaxSupply1, uint256 tokensPerUnit1) =
75+
erc20Mint.tokenData(slicerId, productIds[0]);
76+
assertEq(revertOnMaxSupply1, true);
77+
assertEq(tokensPerUnit1, 100);
78+
assertEq(token1.name(), "Test Token 1");
79+
assertEq(token1.symbol(), "TT1");
80+
assertEq(token1.maxSupply(), 10000);
81+
assertEq(token1.totalSupply(), 1000); // premint amount
82+
assertEq(token1.balanceOf(productOwner), 1000);
83+
84+
(ERC20Mint_BaseToken token2, bool revertOnMaxSupply2, uint256 tokensPerUnit2) =
85+
erc20Mint.tokenData(slicerId, productIds[1]);
86+
assertEq(revertOnMaxSupply2, false);
87+
assertEq(tokensPerUnit2, 50);
88+
assertEq(token2.name(), "Test Token 2");
89+
assertEq(token2.symbol(), "TT2");
90+
assertEq(token2.maxSupply(), type(uint256).max);
91+
assertEq(token2.totalSupply(), 0); // no premint
92+
93+
(ERC20Mint_BaseToken token3, bool revertOnMaxSupply3, uint256 tokensPerUnit3) =
94+
erc20Mint.tokenData(slicerId, productIds[2]);
95+
assertEq(revertOnMaxSupply3, true);
96+
assertEq(tokensPerUnit3, 1);
97+
assertEq(token3.totalSupply(), 500); // premint amount
98+
assertEq(token3.balanceOf(buyer), 500);
99+
}
100+
101+
function testConfigureProduct_UpdateExistingToken() public {
102+
vm.startPrank(productOwner);
103+
104+
// First configuration
105+
erc20Mint.configureProduct(
106+
slicerId,
107+
productIds[0],
108+
abi.encode(
109+
"Test Token", // name
110+
"TT", // symbol
111+
100, // premintAmount
112+
productOwner, // premintReceiver
113+
true, // revertOnMaxSupplyReached
114+
1000, // maxSupply
115+
10 // tokensPerUnit
116+
)
117+
);
118+
119+
(ERC20Mint_BaseToken token1,,) = erc20Mint.tokenData(slicerId, productIds[0]);
120+
address tokenAddress = address(token1);
121+
122+
// Second configuration - should update existing token
123+
erc20Mint.configureProduct(
124+
slicerId,
125+
productIds[0],
126+
abi.encode(
127+
"Updated Token", // name (ignored for existing token)
128+
"UT", // symbol (ignored for existing token)
129+
0, // premintAmount
130+
address(0), // premintReceiver
131+
false, // revertOnMaxSupplyReached
132+
2000, // maxSupply (updated)
133+
20 // tokensPerUnit (updated)
134+
)
135+
);
136+
137+
(ERC20Mint_BaseToken token2, bool revertOnMaxSupply2, uint256 tokensPerUnit2) =
138+
erc20Mint.tokenData(slicerId, productIds[0]);
139+
140+
// Token address should be the same
141+
assertEq(address(token2), tokenAddress);
142+
// Config should be updated
143+
assertEq(revertOnMaxSupply2, false);
144+
assertEq(tokensPerUnit2, 20);
145+
assertEq(token2.maxSupply(), 2000);
146+
// Original token properties remain
147+
assertEq(token2.name(), "Test Token");
148+
assertEq(token2.symbol(), "TT");
149+
150+
vm.stopPrank();
151+
}
152+
153+
function testRevert_configureProduct_InvalidTokensPerUnit() public {
154+
vm.startPrank(productOwner);
155+
156+
vm.expectRevert(ERC20Mint.InvalidTokensPerUnit.selector);
157+
erc20Mint.configureProduct(
158+
slicerId,
159+
productIds[0],
160+
abi.encode(
161+
"Test Token", // name
162+
"TT", // symbol
163+
0, // premintAmount
164+
address(0), // premintReceiver
165+
false, // revertOnMaxSupplyReached
166+
1000, // maxSupply
167+
0 // tokensPerUnit (invalid)
168+
)
169+
);
170+
171+
vm.stopPrank();
172+
}
173+
174+
function testIsPurchaseAllowed() public {
175+
vm.startPrank(productOwner);
176+
177+
// Configure product with max supply and revert enabled
178+
erc20Mint.configureProduct(
179+
slicerId,
180+
productIds[0],
181+
abi.encode(
182+
"Test Token", // name
183+
"TT", // symbol
184+
800, // premintAmount
185+
productOwner, // premintReceiver
186+
true, // revertOnMaxSupplyReached
187+
1000, // maxSupply
188+
10 // tokensPerUnit
189+
)
190+
);
191+
192+
// Configure product without max supply limit
193+
erc20Mint.configureProduct(
194+
slicerId,
195+
productIds[1],
196+
abi.encode(
197+
"Test Token 2", // name
198+
"TT2", // symbol
199+
0, // premintAmount
200+
address(0), // premintReceiver
201+
false, // revertOnMaxSupplyReached
202+
0, // maxSupply (unlimited)
203+
50 // tokensPerUnit
204+
)
205+
);
206+
207+
vm.stopPrank();
208+
209+
// Test product 1 (with max supply limit)
210+
// Current supply: 800, max supply: 1000
211+
// Available: 200 tokens, with 10 tokens per unit = 20 units max
212+
213+
assertTrue(erc20Mint.isPurchaseAllowed(slicerId, productIds[0], buyer, 1, "", "")); // 10 tokens needed
214+
assertTrue(erc20Mint.isPurchaseAllowed(slicerId, productIds[0], buyer, 10, "", "")); // 100 tokens needed
215+
assertTrue(erc20Mint.isPurchaseAllowed(slicerId, productIds[0], buyer, 20, "", "")); // 200 tokens needed (exactly at limit)
216+
assertFalse(erc20Mint.isPurchaseAllowed(slicerId, productIds[0], buyer, 21, "", "")); // 210 tokens needed (exceeds limit)
217+
218+
// Test product 2 (unlimited supply)
219+
assertTrue(erc20Mint.isPurchaseAllowed(slicerId, productIds[1], buyer, 1, "", ""));
220+
assertTrue(erc20Mint.isPurchaseAllowed(slicerId, productIds[1], buyer, 1000, "", ""));
221+
assertTrue(erc20Mint.isPurchaseAllowed(slicerId, productIds[1], buyer, type(uint256).max, "", ""));
222+
}
223+
224+
function testOnProductPurchase() public {
225+
vm.startPrank(productOwner);
226+
227+
// Configure products with different settings
228+
erc20Mint.configureProduct(
229+
slicerId,
230+
productIds[0],
231+
abi.encode(
232+
"Test Token 1", // name
233+
"TT1", // symbol
234+
0, // premintAmount
235+
address(0), // premintReceiver
236+
true, // revertOnMaxSupplyReached
237+
1000, // maxSupply
238+
100 // tokensPerUnit
239+
)
240+
);
241+
242+
erc20Mint.configureProduct(
243+
slicerId,
244+
productIds[1],
245+
abi.encode(
246+
"Test Token 2", // name
247+
"TT2", // symbol
248+
0, // premintAmount
249+
address(0), // premintReceiver
250+
false, // revertOnMaxSupplyReached
251+
0, // maxSupply (unlimited)
252+
50 // tokensPerUnit
253+
)
254+
);
255+
256+
vm.stopPrank();
257+
258+
(ERC20Mint_BaseToken token1,,) = erc20Mint.tokenData(slicerId, productIds[0]);
259+
(ERC20Mint_BaseToken token2,,) = erc20Mint.tokenData(slicerId, productIds[1]);
260+
261+
// Test minting for product 1
262+
uint256 initialBalance1 = token1.balanceOf(buyer);
263+
uint256 initialSupply1 = token1.totalSupply();
264+
265+
vm.prank(address(PRODUCTS_MODULE));
266+
erc20Mint.onProductPurchase(slicerId, productIds[0], buyer, 3, "", "");
267+
268+
assertEq(token1.balanceOf(buyer), initialBalance1 + 300); // 3 * 100
269+
assertEq(token1.totalSupply(), initialSupply1 + 300);
270+
271+
// Test minting for product 2
272+
uint256 initialBalance2 = token2.balanceOf(buyer2);
273+
uint256 initialSupply2 = token2.totalSupply();
274+
275+
vm.prank(address(PRODUCTS_MODULE));
276+
erc20Mint.onProductPurchase(slicerId, productIds[1], buyer2, 5, "", "");
277+
278+
assertEq(token2.balanceOf(buyer2), initialBalance2 + 250); // 5 * 50
279+
assertEq(token2.totalSupply(), initialSupply2 + 250);
280+
281+
// Test multiple purchases
282+
vm.prank(address(PRODUCTS_MODULE));
283+
erc20Mint.onProductPurchase(slicerId, productIds[0], buyer3, 2, "", "");
284+
assertEq(token1.balanceOf(buyer3), 200); // 2 * 100
285+
assertEq(token1.totalSupply(), initialSupply1 + 500); // 300 + 200
286+
}
287+
288+
function testOnProductPurchase_NoRevertOnMaxSupply() public {
289+
vm.startPrank(productOwner);
290+
291+
// Configure product with max supply but revert disabled
292+
erc20Mint.configureProduct(
293+
slicerId,
294+
productIds[0],
295+
abi.encode(
296+
"Test Token", // name
297+
"TT", // symbol
298+
990, // premintAmount (close to max)
299+
productOwner, // premintReceiver
300+
false, // revertOnMaxSupplyReached (disabled)
301+
1000, // maxSupply
302+
10 // tokensPerUnit
303+
)
304+
);
305+
306+
vm.stopPrank();
307+
308+
// This should succeed but not mint tokens (exceeds max supply)
309+
(ERC20Mint_BaseToken token,,) = erc20Mint.tokenData(slicerId, productIds[0]);
310+
uint256 initialBalance = token.balanceOf(buyer);
311+
uint256 initialSupply = token.totalSupply();
312+
313+
vm.prank(address(PRODUCTS_MODULE));
314+
erc20Mint.onProductPurchase(slicerId, productIds[0], buyer, 2, "", "");
315+
316+
// Balance and supply should remain unchanged (mint failed silently)
317+
assertEq(token.balanceOf(buyer), initialBalance);
318+
assertEq(token.totalSupply(), initialSupply);
319+
}
320+
321+
function testRevert_onProductPurchase_MaxSupplyReached() public {
322+
vm.startPrank(productOwner);
323+
324+
// Configure product with small max supply and revert enabled
325+
erc20Mint.configureProduct(
326+
slicerId,
327+
productIds[0],
328+
abi.encode(
329+
"Test Token", // name
330+
"TT", // symbol
331+
950, // premintAmount (close to max)
332+
productOwner, // premintReceiver
333+
true, // revertOnMaxSupplyReached
334+
1000, // maxSupply
335+
10 // tokensPerUnit
336+
)
337+
);
338+
339+
vm.stopPrank();
340+
341+
// This should succeed (50 tokens available, need 50)
342+
vm.prank(address(PRODUCTS_MODULE));
343+
erc20Mint.onProductPurchase(slicerId, productIds[0], buyer, 5, "", "");
344+
345+
(ERC20Mint_BaseToken token,,) = erc20Mint.tokenData(slicerId, productIds[0]);
346+
assertEq(token.totalSupply(), 1000); // at max supply
347+
348+
// This should revert (no tokens available)
349+
vm.expectRevert(RegistryOnchainAction.NotAllowed.selector);
350+
vm.prank(address(PRODUCTS_MODULE));
351+
erc20Mint.onProductPurchase(slicerId, productIds[0], buyer, 1, "", "");
352+
}
353+
}

0 commit comments

Comments
 (0)