@bananapus/721-hook-v6 0.0.42 → 0.0.45

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 (86) 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 +61 -19
  6. package/src/JB721CheckpointsDeployer.sol +10 -5
  7. package/src/JB721TiersHook.sol +66 -53
  8. package/src/JB721TiersHookDeployer.sol +8 -5
  9. package/src/JB721TiersHookProjectDeployer.sol +87 -46
  10. package/src/JB721TiersHookStore.sol +137 -107
  11. package/src/abstract/JB721Hook.sol +8 -6
  12. package/src/interfaces/IJB721Checkpoints.sol +21 -14
  13. package/src/interfaces/IJB721CheckpointsDeployer.sol +7 -3
  14. package/src/interfaces/IJB721TiersHook.sol +3 -3
  15. package/src/interfaces/IJB721TiersHookProjectDeployer.sol +4 -2
  16. package/src/interfaces/IJB721TiersHookStore.sol +11 -11
  17. package/src/libraries/JB721TiersHookLib.sol +1 -1
  18. package/src/structs/JB721TiersHookFlags.sol +1 -1
  19. package/src/structs/JBPayDataHookRulesetMetadata.sol +1 -1
  20. package/test/utils/AccessJBLib.sol +49 -0
  21. package/test/utils/ForTest_JB721TiersHook.sol +246 -0
  22. package/test/utils/TestBaseWorkflow.sol +213 -0
  23. package/test/utils/UnitTestSetup.sol +805 -0
  24. package/.gas-snapshot +0 -152
  25. package/ADMINISTRATION.md +0 -87
  26. package/ARCHITECTURE.md +0 -98
  27. package/AUDIT_INSTRUCTIONS.md +0 -77
  28. package/RISKS.md +0 -118
  29. package/SKILLS.md +0 -43
  30. package/STYLE_GUIDE.md +0 -610
  31. package/USER_JOURNEYS.md +0 -121
  32. package/assets/findings/nana-721-hook-v6-pashov-ai-audit-report-20260330-091257.md +0 -83
  33. package/slither-ci.config.json +0 -10
  34. package/test/721HookAttacks.t.sol +0 -408
  35. package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +0 -985
  36. package/test/Fork.t.sol +0 -2346
  37. package/test/TestAuditGaps.sol +0 -1075
  38. package/test/TestCheckpoints.t.sol +0 -341
  39. package/test/TestSafeTransferReentrancy.t.sol +0 -305
  40. package/test/TestVotingUnitsLifecycle.t.sol +0 -313
  41. package/test/audit/AuditRegressions.t.sol +0 -83
  42. package/test/audit/CodexNemesisReserveSellout.t.sol +0 -66
  43. package/test/audit/CrossCurrencySplitNoPrices.t.sol +0 -123
  44. package/test/audit/FreshAudit.t.sol +0 -197
  45. package/test/audit/FutureTierPoC.t.sol +0 -39
  46. package/test/audit/FutureTierRemoval.t.sol +0 -47
  47. package/test/audit/Pass12L18.t.sol +0 -80
  48. package/test/audit/PayCreditsBypassTierSplits.t.sol +0 -200
  49. package/test/audit/ProjectDeployerAuth.t.sol +0 -266
  50. package/test/audit/RepoFindings.t.sol +0 -195
  51. package/test/audit/ReserveActivation.t.sol +0 -87
  52. package/test/audit/ReserveSlotProtection.t.sol +0 -273
  53. package/test/audit/RetroactiveReserveBeneficiaryDilution.t.sol +0 -149
  54. package/test/audit/SameCurrencyDecimalMismatch.t.sol +0 -249
  55. package/test/audit/SplitCreditsMismatch.t.sol +0 -219
  56. package/test/audit/SplitFailureRedistribution.t.sol +0 -143
  57. package/test/audit/USDTVoidReturnCompat.t.sol +0 -301
  58. package/test/fork/ERC20CashOutFork.t.sol +0 -633
  59. package/test/fork/ERC20TierSplitFork.t.sol +0 -596
  60. package/test/fork/IssueTokensForSplitsFork.t.sol +0 -516
  61. package/test/invariants/TierLifecycleInvariant.t.sol +0 -188
  62. package/test/invariants/TieredHookStoreInvariant.t.sol +0 -86
  63. package/test/invariants/handlers/TierLifecycleHandler.sol +0 -300
  64. package/test/invariants/handlers/TierStoreHandler.sol +0 -165
  65. package/test/regression/BrokenTerminalDoesNotDos.t.sol +0 -277
  66. package/test/regression/CacheTierLookup.t.sol +0 -190
  67. package/test/regression/ProjectDeployerRulesets.t.sol +0 -358
  68. package/test/regression/ReserveBeneficiaryOverwrite.t.sol +0 -155
  69. package/test/regression/SplitDistributionBugs.t.sol +0 -751
  70. package/test/regression/SplitNoBeneficiary.t.sol +0 -140
  71. package/test/unit/AuditFixes_Unit.t.sol +0 -624
  72. package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +0 -116
  73. package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +0 -144
  74. package/test/unit/JBBitmap.t.sol +0 -170
  75. package/test/unit/JBIpfsDecoder.t.sol +0 -136
  76. package/test/unit/TierSupplyReserveCheck.t.sol +0 -221
  77. package/test/unit/adjustTier_Unit.t.sol +0 -1942
  78. package/test/unit/deployer_Unit.t.sol +0 -114
  79. package/test/unit/getters_constructor_Unit.t.sol +0 -593
  80. package/test/unit/mintFor_mintReservesFor_Unit.t.sol +0 -452
  81. package/test/unit/pay_CrossCurrency_Unit.t.sol +0 -530
  82. package/test/unit/pay_Unit.t.sol +0 -1661
  83. package/test/unit/redeem_Unit.t.sol +0 -473
  84. package/test/unit/relayBeneficiary_Unit.t.sol +0 -182
  85. package/test/unit/splitHookDistribution_Unit.t.sol +0 -604
  86. package/test/unit/tierSplitRouting_Unit.t.sol +0 -757
@@ -1,313 +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 {IJB721TiersHookStore} from "../src/interfaces/IJB721TiersHookStore.sol";
7
- import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
8
-
9
- /// @title TestVotingUnitsLifecycle
10
- /// @notice Tests that voting units are correctly tracked through the full NFT lifecycle:
11
- /// mint, transfer, and burn. Verifies that the store's votingUnitsOf aggregation stays
12
- /// consistent as token ownership changes across tiers.
13
- contract TestVotingUnitsLifecycle is UnitTestSetup {
14
- using stdStorage for StdStorage;
15
-
16
- // ---------------------------------------------------------------
17
- // Test 1: Mint -> Transfer -> Burn lifecycle for voting units
18
- // ---------------------------------------------------------------
19
- /// @notice Verifies that custom voting units (useVotingUnits=true) are correctly tracked
20
- /// through the full lifecycle: mint to user A, transfer to user B, burn by user B.
21
- function test_votingUnits_mintTransferBurn_lifecycle() public {
22
- // Configure a tier with custom voting units.
23
- defaultTierConfig.flags.allowOwnerMint = true;
24
- defaultTierConfig.reserveFrequency = 0;
25
- defaultTierConfig.flags.useVotingUnits = true;
26
- defaultTierConfig.votingUnits = 100;
27
-
28
- ForTest_JB721TiersHook testHook = _initializeForTestHook(1);
29
- IJB721TiersHookStore hookStore = testHook.STORE();
30
-
31
- address userA = makeAddr("userA");
32
- address userB = makeAddr("userB");
33
-
34
- // --- Mint: NFT goes to userA ---
35
- uint16[] memory tiersToMint = new uint16[](1);
36
- tiersToMint[0] = 1;
37
- vm.prank(owner);
38
- testHook.mintFor(tiersToMint, userA);
39
-
40
- uint256 tokenId = _generateTokenId(1, 1);
41
-
42
- // Verify userA has 100 voting units.
43
- assertEq(
44
- hookStore.votingUnitsOf(address(testHook), userA), 100, "UserA should have 100 voting units after mint"
45
- );
46
- assertEq(hookStore.votingUnitsOf(address(testHook), userB), 0, "UserB should have 0 voting units after mint");
47
-
48
- // --- Transfer: NFT from userA to userB ---
49
- vm.prank(userA);
50
- IERC721(address(testHook)).transferFrom(userA, userB, tokenId);
51
-
52
- // Verify voting units moved from userA to userB.
53
- assertEq(
54
- hookStore.votingUnitsOf(address(testHook), userA), 0, "UserA should have 0 voting units after transfer"
55
- );
56
- assertEq(
57
- hookStore.votingUnitsOf(address(testHook), userB), 100, "UserB should have 100 voting units after transfer"
58
- );
59
-
60
- // --- Burn: userB burns the NFT ---
61
- uint256[] memory tokensToBurn = new uint256[](1);
62
- tokensToBurn[0] = tokenId;
63
- vm.prank(address(0)); // burn uses _burn internally via ForTest
64
- testHook.burn(tokensToBurn);
65
-
66
- // Verify both users have 0 voting units after burn.
67
- assertEq(hookStore.votingUnitsOf(address(testHook), userA), 0, "UserA should have 0 voting units after burn");
68
- assertEq(hookStore.votingUnitsOf(address(testHook), userB), 0, "UserB should have 0 voting units after burn");
69
- }
70
-
71
- // ---------------------------------------------------------------
72
- // Test 2: Multi-tier voting units aggregation
73
- // ---------------------------------------------------------------
74
- /// @notice Verifies that voting units from multiple tiers aggregate correctly
75
- /// and update properly when NFTs are transferred between users.
76
- function test_votingUnits_multiTier_aggregation() public {
77
- // Configure tiers with different custom voting units.
78
- defaultTierConfig.flags.allowOwnerMint = true;
79
- defaultTierConfig.reserveFrequency = 0;
80
- defaultTierConfig.flags.useVotingUnits = true;
81
-
82
- ForTest_JB721TiersHook testHook = _initializeForTestHook(3);
83
- IJB721TiersHookStore hookStore = testHook.STORE();
84
-
85
- // Set custom voting units for each tier.
86
- // Tier 1 = 100, Tier 2 = 200, Tier 3 = 500.
87
- testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 1, 100);
88
- testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 2, 200);
89
- testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 3, 500);
90
-
91
- address user = makeAddr("user");
92
- address recipient = makeAddr("recipient");
93
-
94
- // --- Mint one NFT from each tier to the same user ---
95
- uint16[] memory tier1 = new uint16[](1);
96
- tier1[0] = 1;
97
- uint16[] memory tier2 = new uint16[](1);
98
- tier2[0] = 2;
99
- uint16[] memory tier3 = new uint16[](1);
100
- tier3[0] = 3;
101
-
102
- vm.startPrank(owner);
103
- testHook.mintFor(tier1, user);
104
- testHook.mintFor(tier2, user);
105
- testHook.mintFor(tier3, user);
106
- vm.stopPrank();
107
-
108
- // Verify total voting units = 100 + 200 + 500 = 800.
109
- assertEq(hookStore.votingUnitsOf(address(testHook), user), 800, "User should have 800 voting units total");
110
-
111
- // --- Transfer tier 3 NFT (500 units) to recipient ---
112
- uint256 tier3TokenId = _generateTokenId(3, 1);
113
- vm.prank(user);
114
- IERC721(address(testHook)).transferFrom(user, recipient, tier3TokenId);
115
-
116
- // Verify user now has 300, recipient has 500.
117
- assertEq(
118
- hookStore.votingUnitsOf(address(testHook), user),
119
- 300,
120
- "User should have 300 voting units after transferring tier 3"
121
- );
122
- assertEq(
123
- hookStore.votingUnitsOf(address(testHook), recipient),
124
- 500,
125
- "Recipient should have 500 voting units from tier 3"
126
- );
127
-
128
- // --- Transfer tier 1 NFT (100 units) to recipient ---
129
- uint256 tier1TokenId = _generateTokenId(1, 1);
130
- vm.prank(user);
131
- IERC721(address(testHook)).transferFrom(user, recipient, tier1TokenId);
132
-
133
- // Verify user now has 200, recipient has 600.
134
- assertEq(
135
- hookStore.votingUnitsOf(address(testHook), user),
136
- 200,
137
- "User should have 200 voting units after transferring tier 1"
138
- );
139
- assertEq(
140
- hookStore.votingUnitsOf(address(testHook), recipient),
141
- 600,
142
- "Recipient should have 600 voting units from tiers 1 and 3"
143
- );
144
- }
145
-
146
- // ---------------------------------------------------------------
147
- // Test 3: Price-based voting units (useVotingUnits=false)
148
- // ---------------------------------------------------------------
149
- /// @notice Verifies that when useVotingUnits is false, the tier price is used as voting power.
150
- function test_votingUnits_priceBasedVoting_lifecycle() public {
151
- // Configure tiers WITHOUT custom voting units (price-based voting).
152
- defaultTierConfig.flags.allowOwnerMint = true;
153
- defaultTierConfig.reserveFrequency = 0;
154
- defaultTierConfig.flags.useVotingUnits = false;
155
- defaultTierConfig.votingUnits = 0;
156
-
157
- ForTest_JB721TiersHook testHook = _initializeForTestHook(3);
158
- IJB721TiersHookStore hookStore = testHook.STORE();
159
-
160
- address user = makeAddr("user");
161
- address recipient = makeAddr("recipient");
162
-
163
- // Default tiers have prices: tier 1 = 10, tier 2 = 20, tier 3 = 30 (from _createTiers).
164
- uint16[] memory tier1 = new uint16[](1);
165
- tier1[0] = 1;
166
- uint16[] memory tier2 = new uint16[](1);
167
- tier2[0] = 2;
168
- uint16[] memory tier3 = new uint16[](1);
169
- tier3[0] = 3;
170
-
171
- vm.startPrank(owner);
172
- testHook.mintFor(tier1, user);
173
- testHook.mintFor(tier2, user);
174
- testHook.mintFor(tier3, user);
175
- vm.stopPrank();
176
-
177
- // Verify total voting units = 10 + 20 + 30 = 60 (prices).
178
- assertEq(hookStore.votingUnitsOf(address(testHook), user), 60, "User should have 60 price-based voting units");
179
-
180
- // Transfer tier 2 (price 20).
181
- uint256 tier2TokenId = _generateTokenId(2, 1);
182
- vm.prank(user);
183
- IERC721(address(testHook)).transferFrom(user, recipient, tier2TokenId);
184
-
185
- assertEq(
186
- hookStore.votingUnitsOf(address(testHook), user),
187
- 40,
188
- "User should have 40 voting units after transferring tier 2"
189
- );
190
- assertEq(
191
- hookStore.votingUnitsOf(address(testHook), recipient),
192
- 20,
193
- "Recipient should have 20 voting units from tier 2"
194
- );
195
- }
196
-
197
- // ---------------------------------------------------------------
198
- // Test 4: Multiple NFTs from the same tier
199
- // ---------------------------------------------------------------
200
- /// @notice Verifies that voting units scale correctly when a user owns multiple NFTs from one tier.
201
- function test_votingUnits_multipleMintsSameTier() public {
202
- // Configure tier with custom voting units.
203
- defaultTierConfig.flags.allowOwnerMint = true;
204
- defaultTierConfig.reserveFrequency = 0;
205
- defaultTierConfig.flags.useVotingUnits = true;
206
- defaultTierConfig.votingUnits = 50;
207
-
208
- ForTest_JB721TiersHook testHook = _initializeForTestHook(1);
209
- IJB721TiersHookStore hookStore = testHook.STORE();
210
-
211
- address user = makeAddr("user");
212
-
213
- // Mint 3 NFTs from tier 1 to the same user.
214
- uint16[] memory tiersToMint = new uint16[](3);
215
- tiersToMint[0] = 1;
216
- tiersToMint[1] = 1;
217
- tiersToMint[2] = 1;
218
-
219
- vm.prank(owner);
220
- testHook.mintFor(tiersToMint, user);
221
-
222
- // Verify total voting units = 50 * 3 = 150.
223
- assertEq(hookStore.votingUnitsOf(address(testHook), user), 150, "User should have 150 voting units (3 x 50)");
224
-
225
- // Transfer one NFT away.
226
- address recipient = makeAddr("recipient");
227
- uint256 tokenId1 = _generateTokenId(1, 1);
228
- vm.prank(user);
229
- IERC721(address(testHook)).transferFrom(user, recipient, tokenId1);
230
-
231
- // Verify voting units: user = 100, recipient = 50.
232
- assertEq(
233
- hookStore.votingUnitsOf(address(testHook), user),
234
- 100,
235
- "User should have 100 voting units after transferring 1"
236
- );
237
- assertEq(hookStore.votingUnitsOf(address(testHook), recipient), 50, "Recipient should have 50 voting units");
238
- }
239
-
240
- // ---------------------------------------------------------------
241
- // Test 5: Voting units are zero for addresses with no NFTs
242
- // ---------------------------------------------------------------
243
- /// @notice Verifies that addresses with no NFTs always return 0 voting units.
244
- function test_votingUnits_zeroForNonHolders() public {
245
- defaultTierConfig.flags.useVotingUnits = true;
246
- defaultTierConfig.votingUnits = 100;
247
-
248
- ForTest_JB721TiersHook testHook = _initializeForTestHook(5);
249
- IJB721TiersHookStore hookStore = testHook.STORE();
250
-
251
- address nonHolder = makeAddr("nonHolder");
252
-
253
- // Verify zero voting units for an address that never held any NFTs.
254
- assertEq(hookStore.votingUnitsOf(address(testHook), nonHolder), 0, "Non-holder should have 0 voting units");
255
- }
256
-
257
- // ---------------------------------------------------------------
258
- // Test 6: Voting units with mixed tier configs
259
- // ---------------------------------------------------------------
260
- /// @notice Verifies that voting units work correctly when some tiers use custom voting units
261
- /// and others use price-based voting. The tier with useVotingUnits=false should use price.
262
- function test_votingUnits_mixedTierConfigs() public {
263
- defaultTierConfig.flags.allowOwnerMint = true;
264
- defaultTierConfig.reserveFrequency = 0;
265
- defaultTierConfig.flags.useVotingUnits = true;
266
- defaultTierConfig.votingUnits = 100;
267
-
268
- ForTest_JB721TiersHook testHook = _initializeForTestHook(3);
269
- IJB721TiersHookStore hookStore = testHook.STORE();
270
-
271
- // Override tier 2 to NOT use custom voting units (price-based).
272
- // Tier 2 price from _createTiers is 20.
273
- testHook.test_store()
274
- .ForTest_setTier(
275
- address(testHook),
276
- 2,
277
- JBStored721Tier({
278
- price: uint104(20),
279
- remainingSupply: uint32(100),
280
- initialSupply: uint32(100),
281
- reserveFrequency: uint16(0),
282
- category: uint24(100),
283
- discountPercent: uint8(0),
284
- packedBools: testHook.test_store().ForTest_packBools(true, false, false, false, false, false),
285
- splitPercent: 0
286
- })
287
- );
288
- // Clear tier 2's custom voting units (so it falls back to price).
289
- testHook.test_store().ForTest_setTierVotingUnits(address(testHook), 2, 0);
290
-
291
- address user = makeAddr("user");
292
-
293
- // Mint one NFT from each tier.
294
- uint16[] memory tier1 = new uint16[](1);
295
- tier1[0] = 1;
296
- uint16[] memory tier2 = new uint16[](1);
297
- tier2[0] = 2;
298
- uint16[] memory tier3 = new uint16[](1);
299
- tier3[0] = 3;
300
-
301
- vm.startPrank(owner);
302
- testHook.mintFor(tier1, user);
303
- testHook.mintFor(tier2, user);
304
- testHook.mintFor(tier3, user);
305
- vm.stopPrank();
306
-
307
- // Tier 1: custom voting units = 100
308
- // Tier 2: price-based = 20 (useVotingUnits=false, so uses price)
309
- // Tier 3: custom voting units = 100
310
- // Total: 100 + 20 + 100 = 220
311
- assertEq(hookStore.votingUnitsOf(address(testHook), user), 220, "User should have 220 mixed voting units");
312
- }
313
- }
@@ -1,83 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- // Import the shared unit test setup which deploys a hook clone with 10 tiers.
5
- // forge-lint: disable-next-line(unaliased-plain-import)
6
- import "../utils/UnitTestSetup.sol";
7
-
8
- // Import IERC2981 to compute its interface ID for the supportsInterface test.
9
- import {IERC2981} from "@openzeppelin/contracts/interfaces/IERC2981.sol";
10
-
11
- /// @notice Regression tests covering three audit findings for nana-721-hook-v6.
12
- contract AuditRegressions is UnitTestSetup {
13
- // -----------------------------------------------------------------------
14
- // 1. Double-initialization guard
15
- // -----------------------------------------------------------------------
16
-
17
- /// @notice Calling initialize on an already-initialized clone must revert.
18
- function test_doubleInitialization_reverts() public {
19
- // The `hook` from setUp() is already initialized via the deployer.
20
- // Expect a revert with AlreadyInitialized carrying the existing project ID.
21
- vm.expectRevert(abi.encodeWithSelector(JB721TiersHook.JB721TiersHook_AlreadyInitialized.selector, projectId));
22
-
23
- // Attempt to initialize the hook again with valid parameters — must revert.
24
- hook.initialize(
25
- projectId,
26
- "AnotherName",
27
- "AN",
28
- baseUri,
29
- IJB721TokenUriResolver(mockTokenUriResolver),
30
- contractUri,
31
- JB721InitTiersConfig({
32
- tiers: new JB721TierConfig[](0), currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
33
- }),
34
- JB721TiersHookFlags({
35
- preventOverspending: false,
36
- issueTokensForSplits: false,
37
- noNewTiersWithReserves: false,
38
- noNewTiersWithVotes: false,
39
- noNewTiersWithOwnerMinting: false
40
- })
41
- );
42
- }
43
-
44
- // -----------------------------------------------------------------------
45
- // 2. recordSetDiscountPercentOf on a removed tier must revert
46
- // -----------------------------------------------------------------------
47
-
48
- /// @notice Setting the discount percent on a tier that has been removed must revert.
49
- function test_setDiscountPercent_removedTier_reverts() public {
50
- // The hook from setUp() has 10 tiers (IDs 1-10). Remove tier 1.
51
- uint256[] memory tierIdsToRemove = new uint256[](1);
52
-
53
- // Select tier 1 to remove.
54
- tierIdsToRemove[0] = 1;
55
-
56
- // Remove tier 1 as the hook owner.
57
- vm.prank(owner);
58
- hook.adjustTiers(new JB721TierConfig[](0), tierIdsToRemove);
59
-
60
- // Expect a revert with TierRemoved when setting discount on the removed tier.
61
- vm.expectRevert(abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_TierRemoved.selector, 1));
62
-
63
- // Attempt to set a discount on the removed tier — must revert.
64
- vm.prank(owner);
65
- hook.setDiscountPercentOf(1, 50);
66
- }
67
-
68
- // -----------------------------------------------------------------------
69
- // 3. ERC-2981 supportsInterface returns false (support was removed)
70
- // -----------------------------------------------------------------------
71
-
72
- /// @notice supportsInterface must return false for IERC2981 since royalty support was removed.
73
- function test_supportsInterface_erc2981_returnsFalse() public {
74
- // Compute the IERC2981 interface ID from the imported interface.
75
- bytes4 erc2981InterfaceId = type(IERC2981).interfaceId;
76
-
77
- // Query supportsInterface on the hook.
78
- bool supported = hook.supportsInterface(erc2981InterfaceId);
79
-
80
- // Assert that ERC-2981 is NOT supported.
81
- assertFalse(supported, "ERC-2981 must not be supported after royalty removal");
82
- }
83
- }
@@ -1,66 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
6
-
7
- import {JB721TiersHookStore} from "../../src/JB721TiersHookStore.sol";
8
- import {JB721Tier} from "../../src/structs/JB721Tier.sol";
9
- import {JB721TierConfig} from "../../src/structs/JB721TierConfig.sol";
10
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
11
-
12
- contract CodexNemesisReserveSellout is Test {
13
- JB721TiersHookStore internal store;
14
-
15
- function setUp() public {
16
- store = new JB721TiersHookStore();
17
- }
18
-
19
- /// @notice Verifies that a paid mint cannot consume the last slot when it is reserved.
20
- /// @dev Previously this test demonstrated the bug (paid mint succeeded). Now it confirms the fix.
21
- function test_paidMintCannotConsumeReservedFinalSlot() public {
22
- JB721TierConfig[] memory tiers = new JB721TierConfig[](1);
23
- tiers[0] = JB721TierConfig({
24
- price: 1 ether,
25
- initialSupply: 2,
26
- votingUnits: 0,
27
- reserveFrequency: 1,
28
- reserveBeneficiary: address(0xBEEF),
29
- encodedIPFSUri: bytes32(0),
30
- category: 0,
31
- discountPercent: 0,
32
- flags: JB721TierConfigFlags({
33
- allowOwnerMint: false,
34
- useReserveBeneficiaryAsDefault: false,
35
- transfersPausable: false,
36
- useVotingUnits: false,
37
- cantBeRemoved: false,
38
- cantIncreaseDiscountPercent: false,
39
- cantBuyWithCredits: false
40
- }),
41
- splitPercent: 0,
42
- splits: new JBSplit[](0)
43
- });
44
-
45
- store.recordAddTiers(tiers);
46
-
47
- uint16[] memory tierIds = new uint16[](1);
48
- tierIds[0] = 1;
49
-
50
- store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
51
-
52
- JB721Tier memory tier = store.tierOf(address(this), 1, false);
53
- assertEq(tier.remainingSupply, 1, "one paid mint leaves one slot");
54
- assertEq(store.numberOfPendingReservesFor(address(this), 1), 1, "one reserve is pending");
55
-
56
- // With the fix, the second paid mint reverts because the remaining slot is reserved.
57
- vm.expectRevert(
58
- abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientSupplyRemaining.selector, 1)
59
- );
60
- store.recordMint({amount: 1 ether, tierIds: tierIds, isOwnerMint: false});
61
-
62
- // Reserve beneficiary can still claim their entitled mint.
63
- store.recordMintReservesFor({tierId: 1, count: 1});
64
- assertEq(store.numberOfReservesMintedFor(address(this), 1), 1, "reserve beneficiary got their token");
65
- }
66
- }
@@ -1,123 +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 {IJB721TokenUriResolver} from "../../src/interfaces/IJB721TokenUriResolver.sol";
7
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
8
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
9
- import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
10
- import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
11
- import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
12
- import {JBBeforePayRecordedContext} from "@bananapus/core-v6/src/structs/JBBeforePayRecordedContext.sol";
13
-
14
- contract CrossCurrencySplitNoPrices is UnitTestSetup {
15
- function test_crossCurrencySplit_withoutPrices_locksForwardedNativeFunds() public {
16
- JB721TiersHook noPricesOrigin = new JB721TiersHook(
17
- IJBDirectory(mockJBDirectory),
18
- IJBPermissions(mockJBPermissions),
19
- IJBPrices(address(0)),
20
- IJBRulesets(mockJBRulesets),
21
- store,
22
- IJBSplits(mockJBSplits),
23
- IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
24
- trustedForwarder
25
- );
26
-
27
- address noPricesProxy = makeAddr("noPricesProxy");
28
- vm.etch(noPricesProxy, address(noPricesOrigin).code);
29
- JB721TiersHook crossHook = JB721TiersHook(noPricesProxy);
30
-
31
- (JB721TierConfig[] memory tierConfigs,) = _createTiers(defaultTierConfig, 1);
32
- tierConfigs[0].price = 1 ether;
33
- tierConfigs[0].splitPercent = 500_000_000; // 50%
34
-
35
- crossHook.initialize(
36
- projectId,
37
- name,
38
- symbol,
39
- baseUri,
40
- IJB721TokenUriResolver(mockTokenUriResolver),
41
- contractUri,
42
- JB721InitTiersConfig({tiers: tierConfigs, currency: uint32(USD()), decimals: 18}),
43
- JB721TiersHookFlags({
44
- preventOverspending: false,
45
- issueTokensForSplits: false,
46
- noNewTiersWithReserves: false,
47
- noNewTiersWithVotes: false,
48
- noNewTiersWithOwnerMinting: false
49
- })
50
- );
51
-
52
- uint16[] memory tierIdsToMint = new uint16[](1);
53
- tierIdsToMint[0] = 1;
54
- bytes[] memory data = new bytes[](1);
55
- data[0] = abi.encode(true, tierIdsToMint);
56
- bytes4[] memory ids = new bytes4[](1);
57
- ids[0] = metadataHelper.getId("pay", crossHook.METADATA_ID_TARGET());
58
- bytes memory payerMetadata = metadataHelper.createMetadata(ids, data);
59
-
60
- (uint256 weight, JBPayHookSpecification[] memory hookSpecifications) = crossHook.beforePayRecordedWith(
61
- JBBeforePayRecordedContext({
62
- terminal: mockTerminalAddress,
63
- payer: beneficiary,
64
- amount: JBTokenAmount({
65
- token: JBConstants.NATIVE_TOKEN,
66
- value: 1 ether,
67
- decimals: 18,
68
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
69
- }),
70
- projectId: projectId,
71
- rulesetId: 0,
72
- beneficiary: beneficiary,
73
- weight: 10e18,
74
- reservedPercent: 0,
75
- metadata: payerMetadata
76
- })
77
- );
78
-
79
- // When PRICES is address(0) and currencies differ, convertAndCapSplitAmounts returns 0
80
- // to avoid forwarding an unconverted amount in the wrong currency denomination.
81
- // This means weight is NOT reduced (full weight) and no funds are forwarded.
82
- assertEq(weight, 10e18, "weight unchanged when split conversion fails due to missing prices");
83
- assertEq(hookSpecifications.length, 1, "one pay hook spec");
84
- assertEq(hookSpecifications[0].amount, 0, "split amount is zero when prices unavailable for conversion");
85
-
86
- mockAndExpect(
87
- address(mockJBDirectory),
88
- abi.encodeWithSelector(IJBDirectory.isTerminalOf.selector, projectId, mockTerminalAddress),
89
- abi.encode(true)
90
- );
91
-
92
- JBAfterPayRecordedContext memory payContext = JBAfterPayRecordedContext({
93
- payer: beneficiary,
94
- projectId: projectId,
95
- rulesetId: 0,
96
- amount: JBTokenAmount({
97
- token: JBConstants.NATIVE_TOKEN,
98
- value: 1 ether,
99
- decimals: 18,
100
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
101
- }),
102
- forwardedAmount: JBTokenAmount({
103
- token: JBConstants.NATIVE_TOKEN,
104
- value: hookSpecifications[0].amount,
105
- decimals: 18,
106
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
107
- }),
108
- weight: weight,
109
- newlyIssuedTokenCount: 0,
110
- beneficiary: beneficiary,
111
- hookMetadata: hookSpecifications[0].metadata,
112
- payerMetadata: payerMetadata
113
- });
114
-
115
- vm.deal(mockTerminalAddress, 1 ether);
116
- vm.prank(mockTerminalAddress);
117
- crossHook.afterPayRecordedWith{value: hookSpecifications[0].amount}(payContext);
118
-
119
- assertEq(crossHook.balanceOf(beneficiary), 0, "no NFTs minted (currency mismatch, no prices)");
120
- assertEq(crossHook.payCreditsOf(beneficiary), 0, "no credits accrued (currency mismatch, no prices)");
121
- assertEq(address(crossHook).balance, 0, "no funds forwarded to hook when split conversion returns zero");
122
- }
123
- }