@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,358 +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
- // forge-lint: disable-next-line(unaliased-plain-import)
7
- import "../../src/JB721TiersHookProjectDeployer.sol";
8
- // forge-lint: disable-next-line(unaliased-plain-import)
9
- import "../../src/structs/JBLaunchRulesetsConfig.sol";
10
- // forge-lint: disable-next-line(unaliased-plain-import)
11
- import "../../src/structs/JBQueueRulesetsConfig.sol";
12
-
13
- import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
14
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
15
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
16
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
17
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
18
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
19
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
20
-
21
- /// @notice A mock projects contract that returns configurable owner and count.
22
- contract MockProjects {
23
- uint256 private _count;
24
- address private _owner;
25
-
26
- function setup(uint256 initialCount, address projectOwner) external {
27
- _count = initialCount;
28
- _owner = projectOwner;
29
- }
30
-
31
- function count() external view returns (uint256) {
32
- return _count;
33
- }
34
-
35
- function ownerOf(uint256) external view returns (address) {
36
- return _owner;
37
- }
38
- }
39
-
40
- /// @notice A mock controller that records calls and returns a ruleset ID.
41
- contract MockController {
42
- uint256 public lastProjectId;
43
- uint256 public lastRulesetConfigCount;
44
- bool public launchRulesetsForCalled;
45
- bool public queueRulesetsOfCalled;
46
-
47
- receive() external payable {}
48
-
49
- // The fallback accepts any call and returns uint256(42) as the ruleset ID.
50
- fallback() external payable {
51
- // Decode the selector to track which function was called.
52
- bytes4 selector = msg.sig;
53
-
54
- // launchRulesetsFor(uint256,JBRulesetConfig[],JBTerminalConfig[],string)
55
- if (selector == IJBController.launchRulesetsFor.selector) {
56
- launchRulesetsForCalled = true;
57
- }
58
- // queueRulesetsOf(uint256,JBRulesetConfig[],string)
59
- if (selector == IJBController.queueRulesetsOf.selector) {
60
- queueRulesetsOfCalled = true;
61
- }
62
-
63
- // Return uint256(42) as the ruleset ID.
64
- bytes memory result = abi.encode(uint256(42));
65
- assembly {
66
- return(add(result, 32), mload(result))
67
- }
68
- }
69
- }
70
-
71
- /// @notice Regression tests for JB721TiersHookProjectDeployer's launchRulesetsFor and queueRulesetsOf.
72
- /// @dev These verify that the deployer correctly deploys a hook, transfers ownership, wires up the data hook,
73
- /// and delegates to the controller for both launch and queue paths.
74
- contract Test_ProjectDeployerRulesets is UnitTestSetup {
75
- using stdStorage for StdStorage;
76
-
77
- JB721TiersHookProjectDeployer deployer;
78
- MockProjects mockProj;
79
- MockController mockCtrl;
80
-
81
- uint256 testProjectId = 5;
82
-
83
- function setUp() public override {
84
- super.setUp();
85
-
86
- // Deploy mock projects contract and etch onto the existing mockJBProjects address.
87
- mockProj = new MockProjects();
88
- vm.etch(mockJBProjects, address(mockProj).code);
89
- MockProjects(mockJBProjects).setup(testProjectId, owner);
90
-
91
- // Mock DIRECTORY.PROJECTS() to return mockJBProjects.
92
- vm.mockCall(mockJBDirectory, abi.encodeWithSelector(IJBDirectory.PROJECTS.selector), abi.encode(mockJBProjects));
93
-
94
- // Mock all permission checks to return true.
95
- vm.mockCall(mockJBPermissions, abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(true));
96
-
97
- // Deploy the mock controller.
98
- mockCtrl = new MockController();
99
-
100
- // Deploy the project deployer.
101
- deployer = new JB721TiersHookProjectDeployer(
102
- IJBDirectory(mockJBDirectory), IJBPermissions(mockJBPermissions), jbHookDeployer, address(0)
103
- );
104
- }
105
-
106
- /// @notice Build a minimal deploy config and launch rulesets config for testing.
107
- function _buildLaunchRulesetsConfigs()
108
- internal
109
- view
110
- returns (JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig)
111
- {
112
- // Build a minimal tier config (1 tier).
113
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
114
- tierConfigs[0] = JB721TierConfig({
115
- price: uint104(10),
116
- initialSupply: uint32(100),
117
- votingUnits: uint16(0),
118
- reserveFrequency: uint16(0),
119
- reserveBeneficiary: reserveBeneficiary,
120
- encodedIPFSUri: tokenUris[0],
121
- category: uint24(100),
122
- discountPercent: uint8(0),
123
- flags: JB721TierConfigFlags({
124
- allowOwnerMint: false,
125
- useReserveBeneficiaryAsDefault: false,
126
- transfersPausable: false,
127
- useVotingUnits: true,
128
- cantBeRemoved: false,
129
- cantIncreaseDiscountPercent: false,
130
- cantBuyWithCredits: false
131
- }),
132
- splitPercent: 0,
133
- splits: new JBSplit[](0)
134
- });
135
-
136
- hookConfig = JBDeploy721TiersHookConfig({
137
- name: name,
138
- symbol: symbol,
139
- baseUri: baseUri,
140
- tokenUriResolver: IJB721TokenUriResolver(mockTokenUriResolver),
141
- contractUri: contractUri,
142
- tiersConfig: JB721InitTiersConfig({
143
- tiers: tierConfigs, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
144
- }),
145
- flags: JB721TiersHookFlags({
146
- preventOverspending: false,
147
- issueTokensForSplits: false,
148
- noNewTiersWithReserves: true,
149
- noNewTiersWithVotes: true,
150
- noNewTiersWithOwnerMinting: true
151
- })
152
- });
153
-
154
- // Build a minimal ruleset config.
155
- JBPayDataHookRulesetConfig[] memory rulesetConfigs = new JBPayDataHookRulesetConfig[](1);
156
- rulesetConfigs[0].mustStartAtOrAfter = 0;
157
- rulesetConfigs[0].duration = 14;
158
- rulesetConfigs[0].weight = 10 ** 18;
159
- rulesetConfigs[0].weightCutPercent = 0;
160
- rulesetConfigs[0].approvalHook = IJBRulesetApprovalHook(address(0));
161
- rulesetConfigs[0].metadata = JBPayDataHookRulesetMetadata({
162
- reservedPercent: 5000,
163
- cashOutTaxRate: 5000,
164
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
165
- pausePay: false,
166
- pauseCreditTransfers: false,
167
- allowOwnerMinting: false,
168
- allowSetCustomToken: false,
169
- allowTerminalMigration: false,
170
- allowSetTerminals: false,
171
- allowSetController: false,
172
- ownerMustSendPayouts: false,
173
- allowAddAccountingContext: false,
174
- allowAddPriceFeed: false,
175
- holdFees: false,
176
- useTotalSurplusForCashOuts: false,
177
- useDataHookForCashOut: false,
178
- metadata: 0x00
179
- });
180
-
181
- JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
182
- JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
183
- accountingContexts[0] = JBAccountingContext({
184
- currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18, token: JBConstants.NATIVE_TOKEN
185
- });
186
- terminalConfigs[0] = JBTerminalConfig({
187
- terminal: IJBTerminal(mockTerminalAddress), accountingContextsToAccept: accountingContexts
188
- });
189
-
190
- launchConfig = JBLaunchRulesetsConfig({
191
- // forge-lint: disable-next-line(unsafe-typecast)
192
- projectId: uint56(testProjectId),
193
- rulesetConfigurations: rulesetConfigs,
194
- terminalConfigurations: terminalConfigs,
195
- memo: "launch rulesets"
196
- });
197
- }
198
-
199
- /// @notice Build a minimal deploy config and queue rulesets config for testing.
200
- function _buildQueueRulesetsConfigs()
201
- internal
202
- view
203
- returns (JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig)
204
- {
205
- // Reuse the launch config builder for the hook config.
206
- JBLaunchRulesetsConfig memory launchConfig;
207
- (hookConfig, launchConfig) = _buildLaunchRulesetsConfigs();
208
-
209
- queueConfig = JBQueueRulesetsConfig({
210
- // forge-lint: disable-next-line(unsafe-typecast)
211
- projectId: uint56(testProjectId),
212
- rulesetConfigurations: launchConfig.rulesetConfigurations,
213
- memo: "queue rulesets"
214
- });
215
- }
216
-
217
- // -----------------------------------------------------------------------
218
- // launchRulesetsFor
219
- // -----------------------------------------------------------------------
220
-
221
- /// @notice launchRulesetsFor deploys a hook, transfers ownership to the project, and calls the controller.
222
- function test_launchRulesetsFor_deploysHookAndCallsController() external {
223
- (JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
224
- _buildLaunchRulesetsConfigs();
225
-
226
- // Call as the project owner.
227
- vm.prank(owner);
228
- (uint256 rulesetId, IJB721TiersHook deployedHook) = deployer.launchRulesetsFor(
229
- testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), bytes32(0)
230
- );
231
-
232
- // Verify the ruleset ID returned by the mock controller.
233
- assertEq(rulesetId, 42, "Should return the ruleset ID from the controller");
234
-
235
- // Verify a hook was deployed (non-zero address).
236
- assertTrue(address(deployedHook) != address(0), "Hook should be deployed");
237
-
238
- // Verify the controller's launchRulesetsFor was called.
239
- assertTrue(mockCtrl.launchRulesetsForCalled(), "Controller launchRulesetsFor should be called");
240
-
241
- // Verify the hook's PROJECT_ID is correct.
242
- assertEq(deployedHook.PROJECT_ID(), testProjectId, "Hook PROJECT_ID should match");
243
-
244
- // Verify the hook's ownership was transferred to the project.
245
- (, uint88 ownerProjectId,) = JBOwnable(address(deployedHook)).jbOwner();
246
- assertEq(ownerProjectId, testProjectId, "Hook should be owned by project");
247
- }
248
-
249
- /// @notice launchRulesetsFor with a deterministic salt produces a hook at a predictable address.
250
- function test_launchRulesetsFor_deterministicSalt() external {
251
- (JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
252
- _buildLaunchRulesetsConfigs();
253
-
254
- bytes32 salt = bytes32(uint256(0xdead));
255
-
256
- vm.prank(owner);
257
- (, IJB721TiersHook hook1) =
258
- deployer.launchRulesetsFor(testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), salt);
259
-
260
- // Deploy a second hook with a different salt to verify addresses differ.
261
- bytes32 salt2 = bytes32(uint256(0xbeef));
262
-
263
- vm.prank(owner);
264
- (, IJB721TiersHook hook2) =
265
- deployer.launchRulesetsFor(testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), salt2);
266
-
267
- assertTrue(address(hook1) != address(hook2), "Different salts should produce different hook addresses");
268
- }
269
-
270
- /// @notice launchRulesetsFor reverts when the caller lacks QUEUE_RULESETS permission.
271
- function test_launchRulesetsFor_revertsWithoutPermission() external {
272
- (JBDeploy721TiersHookConfig memory hookConfig, JBLaunchRulesetsConfig memory launchConfig) =
273
- _buildLaunchRulesetsConfigs();
274
-
275
- // Override: mock permissions to deny.
276
- vm.mockCall(mockJBPermissions, abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(false));
277
-
278
- address unauthorized = makeAddr("unauthorized");
279
- vm.prank(unauthorized);
280
- vm.expectRevert();
281
- deployer.launchRulesetsFor(
282
- testProjectId, hookConfig, launchConfig, IJBController(address(mockCtrl)), bytes32(0)
283
- );
284
- }
285
-
286
- // -----------------------------------------------------------------------
287
- // queueRulesetsOf
288
- // -----------------------------------------------------------------------
289
-
290
- /// @notice queueRulesetsOf deploys a hook, transfers ownership to the project, and calls the controller.
291
- function test_queueRulesetsOf_deploysHookAndCallsController() external {
292
- (JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
293
- _buildQueueRulesetsConfigs();
294
-
295
- vm.prank(owner);
296
- (uint256 rulesetId, IJB721TiersHook deployedHook) = deployer.queueRulesetsOf(
297
- testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0)
298
- );
299
-
300
- // Verify the ruleset ID returned by the mock controller.
301
- assertEq(rulesetId, 42, "Should return the ruleset ID from the controller");
302
-
303
- // Verify a hook was deployed (non-zero address).
304
- assertTrue(address(deployedHook) != address(0), "Hook should be deployed");
305
-
306
- // Verify the controller's queueRulesetsOf was called.
307
- assertTrue(mockCtrl.queueRulesetsOfCalled(), "Controller queueRulesetsOf should be called");
308
-
309
- // Verify the hook's PROJECT_ID is correct.
310
- assertEq(deployedHook.PROJECT_ID(), testProjectId, "Hook PROJECT_ID should match");
311
-
312
- // Verify the hook's ownership was transferred to the project.
313
- (, uint88 ownerProjectId,) = JBOwnable(address(deployedHook)).jbOwner();
314
- assertEq(ownerProjectId, testProjectId, "Hook should be owned by project");
315
- }
316
-
317
- /// @notice queueRulesetsOf correctly wires useDataHookForPay = true in the forwarded ruleset metadata.
318
- function test_queueRulesetsOf_wiresDataHookForPay() external {
319
- (JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
320
- _buildQueueRulesetsConfigs();
321
-
322
- vm.prank(owner);
323
- (, IJB721TiersHook deployedHook) = deployer.queueRulesetsOf(
324
- testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0)
325
- );
326
-
327
- // The hook address should not be zero -- this indirectly validates the dataHook was wired.
328
- assertTrue(address(deployedHook) != address(0), "Hook should be deployed and wired as data hook");
329
- }
330
-
331
- /// @notice queueRulesetsOf reverts when the caller lacks QUEUE_RULESETS permission.
332
- function test_queueRulesetsOf_revertsWithoutPermission() external {
333
- (JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
334
- _buildQueueRulesetsConfigs();
335
-
336
- // Override: mock permissions to deny.
337
- vm.mockCall(mockJBPermissions, abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(false));
338
-
339
- address unauthorized = makeAddr("unauthorized");
340
- vm.prank(unauthorized);
341
- vm.expectRevert();
342
- deployer.queueRulesetsOf(testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0));
343
- }
344
-
345
- /// @notice queueRulesetsOf with a zero salt uses non-deterministic deployment.
346
- function test_queueRulesetsOf_zeroSaltNonDeterministic() external {
347
- (JBDeploy721TiersHookConfig memory hookConfig, JBQueueRulesetsConfig memory queueConfig) =
348
- _buildQueueRulesetsConfigs();
349
-
350
- vm.prank(owner);
351
- (uint256 rulesetId, IJB721TiersHook deployedHook) = deployer.queueRulesetsOf(
352
- testProjectId, hookConfig, queueConfig, IJBController(address(mockCtrl)), bytes32(0)
353
- );
354
-
355
- assertEq(rulesetId, 42, "Should return the ruleset ID");
356
- assertTrue(address(deployedHook) != address(0), "Hook should be deployed");
357
- }
358
- }
@@ -1,155 +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
-
8
- /// @notice defaultReserveBeneficiaryOf is globally overwritten when adding a tier with
9
- /// useReserveBeneficiaryAsDefault=true.
10
- contract Test_L34_ReserveBeneficiaryOverwrite is UnitTestSetup {
11
- using stdStorage for StdStorage;
12
-
13
- address alice = makeAddr("alice");
14
- address bob = makeAddr("bob");
15
-
16
- /// @notice Verify that adding a tier with useReserveBeneficiaryAsDefault=true overwrites the global default
17
- /// and emits the SetDefaultReserveBeneficiary event.
18
- function test_addTierWithDefaultBeneficiary_overwritesGlobal() public {
19
- ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
20
- IJB721TiersHookStore hookStore = testHook.STORE();
21
-
22
- // Add tier 1: alice as default reserve beneficiary.
23
- JB721TierConfig[] memory tier1Configs = new JB721TierConfig[](1);
24
- tier1Configs[0].price = 1 ether;
25
- tier1Configs[0].initialSupply = uint32(100);
26
- tier1Configs[0].category = uint24(1);
27
- tier1Configs[0].encodedIPFSUri = bytes32(uint256(0x1234));
28
- tier1Configs[0].reserveFrequency = 5;
29
- tier1Configs[0].reserveBeneficiary = alice;
30
- tier1Configs[0].flags.useReserveBeneficiaryAsDefault = true;
31
-
32
- vm.prank(address(testHook));
33
- uint256[] memory tier1Ids = hookStore.recordAddTiers(tier1Configs);
34
-
35
- // Verify alice is the default reserve beneficiary.
36
- assertEq(hookStore.defaultReserveBeneficiaryOf(address(testHook)), alice);
37
-
38
- // Verify tier 1 uses alice as its reserve beneficiary (via the default).
39
- assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier1Ids[0]), alice);
40
-
41
- // Add tier 2: no useReserveBeneficiaryAsDefault, with a per-tier beneficiary.
42
- JB721TierConfig[] memory tier2Configs = new JB721TierConfig[](1);
43
- tier2Configs[0].price = 2 ether;
44
- tier2Configs[0].initialSupply = uint32(100);
45
- tier2Configs[0].category = uint24(2);
46
- tier2Configs[0].encodedIPFSUri = bytes32(uint256(0x5678));
47
- tier2Configs[0].reserveFrequency = 5;
48
- tier2Configs[0].reserveBeneficiary = bob;
49
- tier2Configs[0].flags.useReserveBeneficiaryAsDefault = false;
50
-
51
- vm.prank(address(testHook));
52
- uint256[] memory tier2Ids = hookStore.recordAddTiers(tier2Configs);
53
-
54
- // Default should still be alice.
55
- assertEq(hookStore.defaultReserveBeneficiaryOf(address(testHook)), alice);
56
-
57
- // Tier 2 should use bob (tier-specific).
58
- assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier2Ids[0]), bob);
59
-
60
- // Now add tier 3: bob as the NEW default reserve beneficiary.
61
- // This should overwrite the default, affecting tier 1 which relies on the default.
62
- JB721TierConfig[] memory tier3Configs = new JB721TierConfig[](1);
63
- tier3Configs[0].price = 3 ether;
64
- tier3Configs[0].initialSupply = uint32(100);
65
- tier3Configs[0].category = uint24(3);
66
- tier3Configs[0].encodedIPFSUri = bytes32(uint256(0x9ABC));
67
- tier3Configs[0].reserveFrequency = 5;
68
- tier3Configs[0].reserveBeneficiary = bob;
69
- tier3Configs[0].flags.useReserveBeneficiaryAsDefault = true;
70
-
71
- vm.prank(address(testHook));
72
- uint256[] memory tier3Ids = hookStore.recordAddTiers(tier3Configs);
73
-
74
- // Default should now be bob.
75
- assertEq(hookStore.defaultReserveBeneficiaryOf(address(testHook)), bob);
76
-
77
- // Tier 1 should now resolve to bob (the new default), NOT alice.
78
- // This is the documented global overwrite behavior.
79
- assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier1Ids[0]), bob);
80
-
81
- // Tier 2 should still use bob (tier-specific, unaffected by default change).
82
- assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier2Ids[0]), bob);
83
-
84
- // Tier 3 should also resolve to bob (via the default).
85
- assertEq(hookStore.reserveBeneficiaryOf(address(testHook), tier3Ids[0]), bob);
86
- }
87
-
88
- /// @notice Verify that the SetDefaultReserveBeneficiary event is emitted when the default changes.
89
- function test_addTierWithDefaultBeneficiary_emitsEvent() public {
90
- ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
91
- IJB721TiersHookStore hookStore = testHook.STORE();
92
-
93
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
94
- tierConfigs[0].price = 1 ether;
95
- tierConfigs[0].initialSupply = uint32(100);
96
- tierConfigs[0].category = uint24(1);
97
- tierConfigs[0].encodedIPFSUri = bytes32(uint256(0x1234));
98
- tierConfigs[0].reserveFrequency = 5;
99
- tierConfigs[0].reserveBeneficiary = alice;
100
- tierConfigs[0].flags.useReserveBeneficiaryAsDefault = true;
101
-
102
- // Expect the SetDefaultReserveBeneficiary event.
103
- vm.expectEmit(true, true, false, true, address(hookStore));
104
- emit IJB721TiersHookStore.SetDefaultReserveBeneficiary({
105
- hook: address(testHook), newBeneficiary: alice, caller: address(testHook)
106
- });
107
-
108
- vm.prank(address(testHook));
109
- hookStore.recordAddTiers(tierConfigs);
110
- }
111
-
112
- /// @notice Verify that no event is emitted when the beneficiary is already the same.
113
- function test_addTierWithSameDefaultBeneficiary_noEvent() public {
114
- ForTest_JB721TiersHook testHook = _initializeForTestHook(0);
115
- IJB721TiersHookStore hookStore = testHook.STORE();
116
-
117
- // First, set alice as the default.
118
- JB721TierConfig[] memory tier1Configs = new JB721TierConfig[](1);
119
- tier1Configs[0].price = 1 ether;
120
- tier1Configs[0].initialSupply = uint32(100);
121
- tier1Configs[0].category = uint24(1);
122
- tier1Configs[0].encodedIPFSUri = bytes32(uint256(0x1234));
123
- tier1Configs[0].reserveFrequency = 5;
124
- tier1Configs[0].reserveBeneficiary = alice;
125
- tier1Configs[0].flags.useReserveBeneficiaryAsDefault = true;
126
-
127
- vm.prank(address(testHook));
128
- hookStore.recordAddTiers(tier1Configs);
129
-
130
- // Now add another tier with the same default — no event should be emitted.
131
- JB721TierConfig[] memory tier2Configs = new JB721TierConfig[](1);
132
- tier2Configs[0].price = 2 ether;
133
- tier2Configs[0].initialSupply = uint32(100);
134
- tier2Configs[0].category = uint24(2);
135
- tier2Configs[0].encodedIPFSUri = bytes32(uint256(0x5678));
136
- tier2Configs[0].reserveFrequency = 5;
137
- tier2Configs[0].reserveBeneficiary = alice;
138
- tier2Configs[0].flags.useReserveBeneficiaryAsDefault = true;
139
-
140
- // Record the logs to verify no SetDefaultReserveBeneficiary event is emitted.
141
- vm.recordLogs();
142
-
143
- vm.prank(address(testHook));
144
- hookStore.recordAddTiers(tier2Configs);
145
-
146
- Vm.Log[] memory logs = vm.getRecordedLogs();
147
- for (uint256 i; i < logs.length; i++) {
148
- // SetDefaultReserveBeneficiary event signature.
149
- assertTrue(
150
- logs[i].topics[0] != keccak256("SetDefaultReserveBeneficiary(address,address,address)"),
151
- "SetDefaultReserveBeneficiary should not be emitted when beneficiary unchanged"
152
- );
153
- }
154
- }
155
- }