@bananapus/721-hook-v6 0.0.41 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/foundry.lock +1 -7
  2. package/foundry.toml +1 -1
  3. package/package.json +20 -9
  4. package/script/Deploy.s.sol +2 -2
  5. package/src/JB721Checkpoints.sol +60 -18
  6. package/src/JB721CheckpointsDeployer.sol +10 -5
  7. package/src/JB721TiersHook.sol +4 -1
  8. package/src/JB721TiersHookProjectDeployer.sol +68 -30
  9. package/src/JB721TiersHookStore.sol +1 -4
  10. package/src/interfaces/IJB721Checkpoints.sol +21 -14
  11. package/src/interfaces/IJB721CheckpointsDeployer.sol +6 -2
  12. package/src/interfaces/IJB721TiersHookProjectDeployer.sol +2 -0
  13. package/test/utils/AccessJBLib.sol +49 -0
  14. package/test/utils/ForTest_JB721TiersHook.sol +246 -0
  15. package/test/utils/TestBaseWorkflow.sol +213 -0
  16. package/test/utils/UnitTestSetup.sol +805 -0
  17. package/.gas-snapshot +0 -152
  18. package/ADMINISTRATION.md +0 -87
  19. package/ARCHITECTURE.md +0 -98
  20. package/AUDIT_INSTRUCTIONS.md +0 -77
  21. package/RISKS.md +0 -118
  22. package/SKILLS.md +0 -43
  23. package/STYLE_GUIDE.md +0 -610
  24. package/USER_JOURNEYS.md +0 -121
  25. package/assets/findings/nana-721-hook-v6-pashov-ai-audit-report-20260330-091257.md +0 -83
  26. package/slither-ci.config.json +0 -10
  27. package/test/721HookAttacks.t.sol +0 -408
  28. package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +0 -985
  29. package/test/Fork.t.sol +0 -2346
  30. package/test/TestAuditGaps.sol +0 -1075
  31. package/test/TestCheckpoints.t.sol +0 -341
  32. package/test/TestSafeTransferReentrancy.t.sol +0 -305
  33. package/test/TestVotingUnitsLifecycle.t.sol +0 -313
  34. package/test/audit/AuditRegressions.t.sol +0 -83
  35. package/test/audit/CrossCurrencySplitNoPrices.t.sol +0 -123
  36. package/test/audit/FreshAudit.t.sol +0 -197
  37. package/test/audit/FutureTierPoC.t.sol +0 -39
  38. package/test/audit/FutureTierRemoval.t.sol +0 -47
  39. package/test/audit/Pass12L18.t.sol +0 -80
  40. package/test/audit/PayCreditsBypassTierSplits.t.sol +0 -200
  41. package/test/audit/ProjectDeployerAuth.t.sol +0 -266
  42. package/test/audit/RepoFindings.t.sol +0 -195
  43. package/test/audit/ReserveActivation.t.sol +0 -87
  44. package/test/audit/RetroactiveReserveBeneficiaryDilution.t.sol +0 -149
  45. package/test/audit/SameCurrencyDecimalMismatch.t.sol +0 -249
  46. package/test/audit/SplitCreditsMismatch.t.sol +0 -219
  47. package/test/audit/SplitFailureRedistribution.t.sol +0 -143
  48. package/test/audit/USDTVoidReturnCompat.t.sol +0 -301
  49. package/test/fork/ERC20CashOutFork.t.sol +0 -633
  50. package/test/fork/ERC20TierSplitFork.t.sol +0 -596
  51. package/test/fork/IssueTokensForSplitsFork.t.sol +0 -516
  52. package/test/invariants/TierLifecycleInvariant.t.sol +0 -188
  53. package/test/invariants/TieredHookStoreInvariant.t.sol +0 -86
  54. package/test/invariants/handlers/TierLifecycleHandler.sol +0 -300
  55. package/test/invariants/handlers/TierStoreHandler.sol +0 -165
  56. package/test/regression/BrokenTerminalDoesNotDos.t.sol +0 -277
  57. package/test/regression/CacheTierLookup.t.sol +0 -190
  58. package/test/regression/ProjectDeployerRulesets.t.sol +0 -358
  59. package/test/regression/ReserveBeneficiaryOverwrite.t.sol +0 -155
  60. package/test/regression/SplitDistributionBugs.t.sol +0 -751
  61. package/test/regression/SplitNoBeneficiary.t.sol +0 -140
  62. package/test/unit/AuditFixes_Unit.t.sol +0 -624
  63. package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +0 -116
  64. package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +0 -144
  65. package/test/unit/JBBitmap.t.sol +0 -170
  66. package/test/unit/JBIpfsDecoder.t.sol +0 -136
  67. package/test/unit/TierSupplyReserveCheck.t.sol +0 -221
  68. package/test/unit/adjustTier_Unit.t.sol +0 -1942
  69. package/test/unit/deployer_Unit.t.sol +0 -114
  70. package/test/unit/getters_constructor_Unit.t.sol +0 -593
  71. package/test/unit/mintFor_mintReservesFor_Unit.t.sol +0 -452
  72. package/test/unit/pay_CrossCurrency_Unit.t.sol +0 -530
  73. package/test/unit/pay_Unit.t.sol +0 -1661
  74. package/test/unit/redeem_Unit.t.sol +0 -473
  75. package/test/unit/relayBeneficiary_Unit.t.sol +0 -182
  76. package/test/unit/splitHookDistribution_Unit.t.sol +0 -604
  77. package/test/unit/tierSplitRouting_Unit.t.sol +0 -757
@@ -1,593 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- // forge-lint: disable-next-line(unaliased-plain-import)
5
- import "../utils/UnitTestSetup.sol";
6
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
7
- import {JB721TierFlags} from "../../src/structs/JB721TierFlags.sol";
8
-
9
- contract Test_Getters_Constructor_Unit is UnitTestSetup {
10
- using stdStorage for StdStorage;
11
-
12
- function test_tiersOf_returnsAllTiers(uint256 numberOfTiers) public {
13
- numberOfTiers = bound(numberOfTiers, 0, 30);
14
-
15
- (, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
16
-
17
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
18
-
19
- // Check: is everything from `tiersOf` in `tiers`, and vice versa (do they match)?
20
- assertTrue(_isIn(hook.test_store().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers), tiers));
21
- assertTrue(_isIn(tiers, hook.test_store().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers)));
22
- }
23
-
24
- function test_pricingContext_packingFunctionsAsExpected(uint32 currency, uint8 decimals, bytes32 salt) public {
25
- // Decimals must be <= 18 per validation in initialize.
26
- vm.assume(decimals <= 18);
27
- JBDeploy721TiersHookConfig memory hookConfig = JBDeploy721TiersHookConfig({
28
- name: name,
29
- symbol: symbol,
30
- baseUri: baseUri,
31
- tokenUriResolver: IJB721TokenUriResolver(mockTokenUriResolver),
32
- contractUri: contractUri,
33
- tiersConfig: JB721InitTiersConfig({tiers: tiers, currency: currency, decimals: decimals}),
34
- flags: JB721TiersHookFlags({
35
- preventOverspending: false,
36
- issueTokensForSplits: false,
37
- noNewTiersWithReserves: true,
38
- noNewTiersWithVotes: true,
39
- noNewTiersWithOwnerMinting: true
40
- })
41
- });
42
-
43
- JB721TiersHook hook = JB721TiersHook(address(jbHookDeployer.deployHookFor(projectId, hookConfig, salt)));
44
-
45
- (uint256 currency2, uint256 decimals2) = hook.pricingContext();
46
- // Check: do the unpacked values from `pricingContext` match the values we used in the config?
47
- assertEq(currency2, uint256(currency));
48
- assertEq(decimals2, uint256(decimals));
49
- }
50
-
51
- function test_bools_doesPackingAndUnpackingWork(bool a, bool b, bool c, bool d, bool e, bool f) public {
52
- ForTest_JB721TiersHookStore store = new ForTest_JB721TiersHookStore();
53
- uint8 packed = store.ForTest_packBools(a, b, c, d, e, f);
54
- (bool a2, bool b2, bool c2, bool d2, bool e2, bool f2) = store.ForTest_unpackBools(packed);
55
- // Check: do the packed values match the unpacked values?
56
- assertEq(a, a2);
57
- assertEq(b, b2);
58
- assertEq(c, c2);
59
- assertEq(d, d2);
60
- assertEq(e, e2);
61
- assertEq(f, f2);
62
- }
63
-
64
- function test_tiersOf_returnsAllTiersWithResolver(uint256 numberOfTiers) public {
65
- numberOfTiers = bound(numberOfTiers, 0, 30);
66
-
67
- // Use a non-null resolved URI.
68
- // forge-lint: disable-next-line(unsafe-typecast)
69
- defaultTierConfig.encodedIPFSUri = bytes32(hex"69");
70
-
71
- (, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
72
-
73
- mockTokenUriResolver = makeAddr("mockTokenUriResolver");
74
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
75
-
76
- for (uint256 i; i < numberOfTiers; i++) {
77
- // Mock the URI resolver call
78
- mockAndExpect(
79
- mockTokenUriResolver,
80
- abi.encodeWithSelector(
81
- IJB721TokenUriResolver.tokenUriOf.selector, address(hook), _generateTokenId(i + 1, 0)
82
- ),
83
- abi.encode(string(abi.encodePacked("resolverURI", _generateTokenId(i + 1, 0))))
84
- );
85
- }
86
-
87
- // Check: is everything from `tiersOf` in `tiers`, and vice versa (do they match)? Do the resolved URIs match?
88
- assertTrue(_isIn(hook.test_store().tiersOf(address(hook), new uint256[](0), true, 0, 100), tiers));
89
- assertTrue(_isIn(tiers, hook.test_store().tiersOf(address(hook), new uint256[](0), true, 0, 100)));
90
- }
91
-
92
- function test_tiersOf_returnsAllTiersExcludingRemovedOnes(
93
- uint256 numberOfTiers,
94
- uint256 firstRemovedTier,
95
- uint256 secondRemovedTier
96
- )
97
- public
98
- {
99
- numberOfTiers = bound(numberOfTiers, 1, 30);
100
- firstRemovedTier = bound(firstRemovedTier, 1, numberOfTiers);
101
- secondRemovedTier = bound(secondRemovedTier, 1, numberOfTiers);
102
- vm.assume(firstRemovedTier != secondRemovedTier);
103
-
104
- (, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
105
-
106
- // Only copy the tiers we keep.
107
- JB721Tier[] memory nonRemovedTiers = new JB721Tier[](numberOfTiers - 2);
108
- uint256 j;
109
- for (uint256 i; i < numberOfTiers; i++) {
110
- if (i != firstRemovedTier - 1 && i != secondRemovedTier - 1) {
111
- nonRemovedTiers[j] = tiers[i];
112
- j++;
113
- }
114
- }
115
-
116
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
117
-
118
- // Set the removed tiers.
119
- hook.test_store().ForTest_setIsTierRemoved(address(hook), firstRemovedTier);
120
- hook.test_store().ForTest_setIsTierRemoved(address(hook), secondRemovedTier);
121
-
122
- JB721Tier[] memory storedTiers =
123
- hook.test_store().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers);
124
-
125
- // Check: was the returned tier array resized correctly?
126
- assertEq(storedTiers.length, numberOfTiers - 2);
127
-
128
- // Check: is everything from `storedTiers` a `nonRemovedTier`, and vice versa (do they match)?
129
- assertTrue(_isIn(storedTiers, nonRemovedTiers));
130
- assertTrue(_isIn(nonRemovedTiers, storedTiers));
131
- }
132
-
133
- function test_tierOf_returnsAGivenTier(uint256 numberOfTiers, uint16 givenTier) public {
134
- numberOfTiers = bound(numberOfTiers, 0, 30);
135
-
136
- (, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
137
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
138
-
139
- // Check: if the tier exists, it is returned correctly?
140
- if (givenTier <= numberOfTiers && givenTier != 0) {
141
- assertEq(hook.test_store().tierOf(address(hook), givenTier, false), tiers[givenTier - 1]);
142
- } else {
143
- assertEq( // Check: if the tier doesn't exist, is an empty tier returned?
144
- hook.test_store().tierOf(address(hook), givenTier, false),
145
- JB721Tier({
146
- id: givenTier,
147
- price: 0,
148
- remainingSupply: 0,
149
- initialSupply: 0,
150
- votingUnits: 0,
151
- reserveFrequency: 0,
152
- reserveBeneficiary: address(0),
153
- encodedIPFSUri: bytes32(0),
154
- category: uint24(100),
155
- discountPercent: uint8(0),
156
- flags: JB721TierFlags({
157
- allowOwnerMint: false,
158
- transfersPausable: false,
159
- cantBeRemoved: false,
160
- cantIncreaseDiscountPercent: false,
161
- cantBuyWithCredits: false
162
- }),
163
- splitPercent: 0,
164
- resolvedUri: ""
165
- })
166
- );
167
- }
168
- }
169
-
170
- function test_totalSupplyOf_returnsTotalSupply(uint256 numberOfTiers) public {
171
- numberOfTiers = bound(numberOfTiers, 0, 30);
172
-
173
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
174
-
175
- // Initialize `numberOfTiers` tiers with an initial supply of 100, and (i + 1) mints.
176
- // This should yield a total supply of (`numberOfTiers` * (`numberOfTiers` + 1)) / 2,
177
- // which is the sum of natural numbers from 1 to `numberOfTiers`.
178
- for (uint256 i; i < numberOfTiers; i++) {
179
- hook.test_store()
180
- .ForTest_setTier(
181
- address(hook),
182
- i + 1,
183
- JBStored721Tier({
184
- price: uint104((i + 1) * 10),
185
- remainingSupply: uint32(100 - (i + 1)),
186
- initialSupply: uint32(100),
187
- reserveFrequency: uint16(0),
188
- category: uint24(100),
189
- discountPercent: uint8(0),
190
- packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
191
- splitPercent: 0
192
- })
193
- );
194
- }
195
-
196
- // Check: does the total supply match the expected value?
197
- assertEq(hook.test_store().totalSupplyOf(address(hook)), ((numberOfTiers * (numberOfTiers + 1)) / 2));
198
- }
199
-
200
- function test_balanceOf_returnsCompleteBalance(uint256 numberOfTiers, address holder) public {
201
- vm.assume(holder != address(0));
202
- numberOfTiers = bound(numberOfTiers, 0, 30);
203
-
204
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
205
-
206
- // Give the holder (i + 1) * 10 NFTs from each tier up to `numberOfTiers`.
207
- for (uint256 i; i < numberOfTiers; i++) {
208
- hook.test_store().ForTest_setBalanceOf(address(hook), holder, i + 1, (i + 1) * 10);
209
- }
210
-
211
- // Check: does the holder have the correct NFT balance?
212
- // Calculated using 10 * sum of natural numbers from 1 to `numberOfTiers`.
213
- assertEq(hook.balanceOf(holder), 10 * ((numberOfTiers * (numberOfTiers + 1)) / 2));
214
- }
215
-
216
- function test_numberOfPendingReservesFor_returnsPendingReserves() public {
217
- uint256 initialSupply = 200; // the starting supply
218
- uint256 totalMinted = 120; // the number to mint from the supply
219
- uint256 reservedMinted = 10; // the number of reserve mints (out of `totalMinted`)
220
- uint256 reserveFrequency = 9; // the reserve frequency
221
-
222
- // For each tier, 120 NFTs are minted, and 10 of these are reserve mints.
223
- // This means 110 non-reserved NFTs are minted.
224
- // Since the `reserveFrequency` is 9, for every 9 non-reserved tokens minted, 1 reserved token is minted.
225
- // The total number of reserve mints should be `ceil(non-reserve mints / reserveFrequency)`.
226
- // In our case, `ceil(110/9)` comes out to 13, and 10 reserve mints have already been minted.
227
- // Therefore, there should be 3 reserve mints remaining for each tier.
228
-
229
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
230
-
231
- // Set up 10 tiers, each with the parameters above.
232
- for (uint256 i; i < 10; i++) {
233
- hook.test_store()
234
- .ForTest_setTier(
235
- address(hook),
236
- i + 1,
237
- JBStored721Tier({
238
- price: uint104((i + 1) * 10),
239
- // forge-lint: disable-next-line(unsafe-typecast)
240
- remainingSupply: uint32(initialSupply - totalMinted),
241
- // forge-lint: disable-next-line(unsafe-typecast)
242
- initialSupply: uint32(initialSupply),
243
- // forge-lint: disable-next-line(unsafe-typecast)
244
- reserveFrequency: uint16(reserveFrequency),
245
- category: uint24(100),
246
- discountPercent: uint8(0),
247
- packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
248
- splitPercent: 0
249
- })
250
- );
251
- // Manually set the number of reserve mints for each tier.
252
- hook.test_store().ForTest_setReservesMintedFor(address(hook), i + 1, reservedMinted);
253
- }
254
-
255
- // Check: does each tier have the correct number of pending reserves?
256
- for (uint256 i; i < 10; i++) {
257
- assertEq(hook.test_store().numberOfPendingReservesFor(address(hook), i + 1), 3);
258
- }
259
- }
260
-
261
- function test_votingUnitsOf_returnsVotingUnitsCorrectly(
262
- uint256 numberOfTiers,
263
- uint256 votingUnits,
264
- uint256 balances
265
- )
266
- public
267
- {
268
- numberOfTiers = bound(numberOfTiers, 1, 30);
269
- votingUnits = bound(votingUnits, 1, type(uint32).max);
270
- balances = bound(balances, 1, type(uint32).max);
271
-
272
- defaultTierConfig.flags.useVotingUnits = true;
273
- // forge-lint: disable-next-line(unsafe-typecast)
274
- defaultTierConfig.votingUnits = uint32(votingUnits);
275
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
276
-
277
- // Set up tier 1 with 0 voting units.
278
- hook.test_store()
279
- .ForTest_setTier(
280
- address(hook),
281
- 1,
282
- JBStored721Tier({
283
- price: uint104(10),
284
- remainingSupply: uint32(10),
285
- initialSupply: uint32(20),
286
- reserveFrequency: uint16(100),
287
- category: uint24(100),
288
- discountPercent: uint8(0),
289
- packedBools: hook.test_store().ForTest_packBools(false, false, true, false, false, false),
290
- splitPercent: 0
291
- })
292
- );
293
- // Clear the voting units mapping for tier 1 (ForTest_setTier only overwrites the packed struct).
294
- hook.test_store().ForTest_setTierVotingUnits(address(hook), 1, 0);
295
-
296
- // Give the beneficiary `balances` NFTs from each tier up to `numberOfTiers`.
297
- for (uint256 i; i < numberOfTiers; i++) {
298
- hook.test_store().ForTest_setBalanceOf(address(hook), beneficiary, i + 1, balances);
299
- }
300
-
301
- // Check: does the beneficiary have the correct number voting units?
302
- assertEq(
303
- hook.test_store().votingUnitsOf(address(hook), beneficiary),
304
- numberOfTiers * votingUnits * balances - (votingUnits * balances) // One tier has no voting units.
305
- );
306
- }
307
-
308
- function test_tierOfTokenId_returnsCorrectTierNumber(uint16 tierId, uint16 tokenNumber) public {
309
- vm.assume(tierId > 0 && tokenNumber > 0);
310
- uint256 tokenId = _generateTokenId(tierId, tokenNumber);
311
- // Check: does the generated token ID match the provided `tierId`.
312
- assertEq(hook.STORE().tierOfTokenId(address(hook), tokenId, false).id, tierId);
313
- }
314
-
315
- function test_tokenURI_returnsCorrectUriWithResolver(uint256 tokenId) public {
316
- mockTokenUriResolver = makeAddr("mockTokenUriResolver");
317
-
318
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
319
-
320
- // Mock the URI resolver call.
321
- mockAndExpect(
322
- mockTokenUriResolver,
323
- abi.encodeWithSelector(IJB721TokenUriResolver.tokenUriOf.selector, address(hook), tokenId),
324
- abi.encode("resolverURI")
325
- );
326
-
327
- hook.ForTest_setOwnerOf(tokenId, beneficiary);
328
-
329
- // Check: does the token URI resolver return the correct URI from the resolver?
330
- assertEq(hook.tokenURI(tokenId), "resolverURI");
331
- }
332
-
333
- function test_tokenURI_returnsCorrectUriWithoutResolver() public {
334
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
335
-
336
- // Check: for each tier, does the tier's token URI match the theoretic hash?
337
- for (uint256 i = 1; i <= 10; i++) {
338
- uint256 tokenId = _generateTokenId(i, 1);
339
- // Set an owner so the token existence check passes.
340
- hook.ForTest_setOwnerOf(tokenId, beneficiary);
341
- assertEq(hook.tokenURI(tokenId), string(abi.encodePacked(baseUri, theoreticHashes[i - 1])));
342
- }
343
- }
344
-
345
- function test_setEncodedIPFSUriOf_returnsCorrectEncodedURI() public {
346
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
347
-
348
- uint256 tokenId = _generateTokenId(1, 1);
349
- hook.ForTest_setOwnerOf(tokenId, address(123));
350
-
351
- vm.prank(owner);
352
- hook.setMetadata("", "", "", "", IJB721TokenUriResolver(address(0)), 1, tokenUris[1]);
353
-
354
- // Check: does the token URI match the theoretic hash?
355
- assertEq(hook.tokenURI(tokenId), string(abi.encodePacked(baseUri, theoreticHashes[1])));
356
- }
357
-
358
- function test_setMetadata_setsNameAndSymbol() public {
359
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
360
-
361
- // Verify initial name and symbol.
362
- assertEq(hook.name(), name);
363
- assertEq(hook.symbol(), symbol);
364
-
365
- // Set new name and symbol.
366
- vm.prank(owner);
367
- hook.setMetadata("New Name", "NEW", "", "", IJB721TokenUriResolver(address(this)), 0, bytes32(0));
368
-
369
- // Check: did the name and symbol change?
370
- assertEq(hook.name(), "New Name");
371
- assertEq(hook.symbol(), "NEW");
372
- }
373
-
374
- function test_setMetadata_emptyNameAndSymbolUnchanged() public {
375
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
376
-
377
- // Set only baseUri, leaving name and symbol empty.
378
- vm.prank(owner);
379
- hook.setMetadata("", "", "ipfs://newBase/", "", IJB721TokenUriResolver(address(this)), 0, bytes32(0));
380
-
381
- // Check: name and symbol should be unchanged.
382
- assertEq(hook.name(), name);
383
- assertEq(hook.symbol(), symbol);
384
- // Check: baseURI should be updated.
385
- assertEq(hook.baseURI(), "ipfs://newBase/");
386
- }
387
-
388
- function test_cashOutWeightOf_returnsCorrectWeightAsCumSumOfPrices(
389
- uint256 numberOfTiers,
390
- uint256 firstTier,
391
- uint256 lastTier
392
- )
393
- public
394
- {
395
- numberOfTiers = bound(numberOfTiers, 0, 30);
396
- lastTier = bound(lastTier, 0, numberOfTiers);
397
- firstTier = bound(firstTier, 0, lastTier);
398
-
399
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
400
-
401
- // Each tier has `tierId` mintable NFTs, so the maximum number of mints
402
- // is the sum of natural numbers from 1 to `numberOfTiers`.
403
- uint256 maxNumberOfTiers = (numberOfTiers * (numberOfTiers + 1)) / 2;
404
-
405
- // Initialize an array `tierToGetWeightOf` to store the token IDs for each tier,
406
- // which will later be used to calculate the cash out weight.
407
- uint256[] memory tierToGetWeightOf = new uint256[](maxNumberOfTiers);
408
- uint256 iterator;
409
- uint256 theoreticalWeight;
410
-
411
- // Mint `tierId` NFTs for each tier. In the inner loop, `i + 1` is the tier ID, and `j + 1` is the token ID.
412
- for (uint256 i; i < numberOfTiers; i++) {
413
- if (i >= firstTier && i < lastTier) {
414
- for (uint256 j; j <= i; j++) {
415
- tierToGetWeightOf[iterator] = _generateTokenId(i + 1, j + 1);
416
- iterator++;
417
- }
418
- theoreticalWeight += (i + 1) * (i + 1) * 10; // Add the price of the NFTs to the weight.
419
- // (10 is the price multiplier).
420
- }
421
- }
422
-
423
- // Check: does the cash out weight match the expected value?
424
- assertEq(hook.test_store().cashOutWeightOf(address(hook), tierToGetWeightOf), theoreticalWeight);
425
- }
426
-
427
- function test_totalCashOutWeight_returnsCorrectTotalWeightAsCumSumOfPrices(uint256 numberOfTiers) public {
428
- numberOfTiers = bound(numberOfTiers, 0, 30);
429
-
430
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
431
-
432
- uint256 theoreticalWeight;
433
-
434
- // Set up `numberOfTiers` tiers and calculate the theoretical weight for each.
435
- for (uint256 i = 1; i <= numberOfTiers; i++) {
436
- hook.test_store()
437
- .ForTest_setTier(
438
- address(hook),
439
- i,
440
- JBStored721Tier({
441
- // forge-lint: disable-next-line(unsafe-typecast)
442
- price: uint104(i * 10),
443
- // forge-lint: disable-next-line(unsafe-typecast)
444
- remainingSupply: uint32(10 * i - 5 * i),
445
- // forge-lint: disable-next-line(unsafe-typecast)
446
- initialSupply: uint32(10 * i),
447
- reserveFrequency: uint16(0),
448
- category: uint24(100),
449
- discountPercent: uint8(0),
450
- packedBools: hook.test_store().ForTest_packBools(false, false, false, false, false, false),
451
- splitPercent: 0
452
- })
453
- );
454
- // Calculate the theoretical weight for the current tier. 10 the price multiplier.
455
- theoreticalWeight += (10 * i - 5 * i) * i * 10;
456
- }
457
- // Check: does the total cash out weight match the theoretical weight calculated?
458
- assertEq(hook.test_store().totalCashOutWeight(address(hook)), theoreticalWeight);
459
- }
460
-
461
- function test_firstOwnerOf_shouldReturnCurrentOwnerIfFirstOwner(uint256 tokenId, address owner) public {
462
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
463
-
464
- hook.ForTest_setOwnerOf(tokenId, owner);
465
-
466
- // Check: is the first owner of the NFT is the current owner?
467
- assertEq(hook.firstOwnerOf(tokenId), owner);
468
- }
469
-
470
- function test_firstOwnerOf_shouldReturnFirstOwnerIfOwnerChanged(address newOwner, address previousOwner) public {
471
- // Assume that the new owner and previous owner are different and not the zero address.
472
- vm.assume(newOwner != previousOwner);
473
- vm.assume(newOwner != address(0));
474
- vm.assume(previousOwner != address(0));
475
-
476
- // Trusted forwarder is a special case, it can only be the sender if the transaction is a meta transaction.
477
- // which we aren't doing here.
478
- vm.assume(newOwner != trustedForwarder);
479
- vm.assume(previousOwner != trustedForwarder);
480
-
481
- defaultTierConfig.flags.allowOwnerMint = true;
482
- defaultTierConfig.reserveFrequency = 0;
483
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
484
-
485
- uint16[] memory tiersToMint = new uint16[](1);
486
- tiersToMint[0] = 1;
487
-
488
- uint256 tokenId = _generateTokenId(tiersToMint[0], 1);
489
-
490
- vm.prank(owner);
491
- hook.mintFor(tiersToMint, previousOwner);
492
-
493
- // Check: is the first owner of the NFT the previous owner?
494
- assertEq(hook.firstOwnerOf(tokenId), previousOwner);
495
-
496
- // Prank the previous owner and transfer the NFT to the new owner.
497
- vm.startPrank(previousOwner);
498
- IERC721(hook).transferFrom(previousOwner, newOwner, tokenId);
499
- vm.stopPrank();
500
-
501
- // Check: is the first owner of the NFT still the previous owner?
502
- assertEq(hook.firstOwnerOf(tokenId), previousOwner);
503
- }
504
-
505
- function test_firstOwnerOf_shouldReturnZeroAddressIfNotMinted(uint256 tokenId) public {
506
- ForTest_JB721TiersHook hook = _initializeForTestHook(10);
507
- // Check: is the "first owner" of the NFT the zero address?
508
- assertEq(hook.firstOwnerOf(tokenId), address(0));
509
- }
510
-
511
- function test_constructor_deployIfInitialSuppliesNotEmpty(uint256 numberOfTiers) public {
512
- numberOfTiers = bound(numberOfTiers, 0, 10);
513
- // Create new tiers array.
514
- ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
515
- (, JB721Tier[] memory tiers) = _createTiers(defaultTierConfig, numberOfTiers);
516
-
517
- // Check: do the hook's parameters match the expected values?
518
- assertEq(hook.PROJECT_ID(), projectId);
519
- assertEq(address(hook.DIRECTORY()), mockJBDirectory);
520
- assertEq(hook.name(), name);
521
- assertEq(hook.symbol(), symbol);
522
- assertEq(address(hook.STORE().tokenUriResolverOf(address(hook))), mockTokenUriResolver);
523
- assertEq(hook.contractURI(), contractUri);
524
- assertEq(hook.owner(), owner);
525
- // Check: are all of the `tiers` in `hook.STORE().tiersOf`, and vice versa (do they match)?
526
- // Order is not guaranteed, so we use `_isIn` and check both ways.
527
- assertTrue(_isIn(hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers), tiers));
528
- assertTrue(_isIn(tiers, hook.STORE().tiersOf(address(hook), new uint256[](0), false, 0, numberOfTiers)));
529
- }
530
-
531
- function test_constructor_revertDeploymentIfOneEmptyInitialSupply(
532
- uint256 numberOfTiers,
533
- uint256 errorIndex
534
- )
535
- public
536
- {
537
- numberOfTiers = bound(numberOfTiers, 1, 20);
538
- errorIndex = bound(errorIndex, 0, numberOfTiers - 1);
539
- JB721TierConfig[] memory tiers = new JB721TierConfig[](numberOfTiers);
540
-
541
- // Populate the tiers array with the default tier config.
542
- for (uint256 i; i < numberOfTiers; i++) {
543
- tiers[i] = JB721TierConfig({
544
- // forge-lint: disable-next-line(unsafe-typecast)
545
- price: uint104(i * 10),
546
- initialSupply: uint32(100),
547
- votingUnits: uint16(0),
548
- reserveFrequency: uint16(0),
549
- reserveBeneficiary: reserveBeneficiary,
550
- encodedIPFSUri: tokenUris[0],
551
- category: uint24(100),
552
- discountPercent: uint8(0),
553
- flags: JB721TierConfigFlags({
554
- allowOwnerMint: false,
555
- useReserveBeneficiaryAsDefault: false,
556
- transfersPausable: false,
557
- useVotingUnits: true,
558
- cantBeRemoved: false,
559
- cantIncreaseDiscountPercent: false,
560
- cantBuyWithCredits: false
561
- }),
562
- splitPercent: 0,
563
- splits: new JBSplit[](0)
564
- });
565
- }
566
-
567
- // Set the initial supply of the tier at `errorIndex` to 0. This should cause an error.
568
- tiers[errorIndex].initialSupply = 0;
569
-
570
- // Expect the error.
571
- vm.expectRevert(
572
- abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_ZeroInitialSupply.selector, errorIndex + 1)
573
- );
574
- vm.etch(hook_i, address(hook).code);
575
- JB721TiersHook hook = JB721TiersHook(hook_i);
576
- hook.initialize(
577
- projectId,
578
- name,
579
- symbol,
580
- baseUri,
581
- IJB721TokenUriResolver(mockTokenUriResolver),
582
- contractUri,
583
- JB721InitTiersConfig({tiers: tiers, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18}),
584
- JB721TiersHookFlags({
585
- preventOverspending: false,
586
- issueTokensForSplits: false,
587
- noNewTiersWithReserves: true,
588
- noNewTiersWithVotes: true,
589
- noNewTiersWithOwnerMinting: true
590
- })
591
- );
592
- }
593
- }