@bananapus/721-hook-v6 0.0.28 → 0.0.30

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 (46) hide show
  1. package/ADMINISTRATION.md +38 -11
  2. package/ARCHITECTURE.md +53 -99
  3. package/AUDIT_INSTRUCTIONS.md +84 -383
  4. package/CHANGELOG.md +71 -0
  5. package/README.md +79 -225
  6. package/RISKS.md +28 -11
  7. package/SKILLS.md +29 -296
  8. package/STYLE_GUIDE.md +57 -18
  9. package/USER_JOURNEYS.md +57 -501
  10. package/package.json +1 -1
  11. package/references/operations.md +28 -0
  12. package/references/runtime.md +32 -0
  13. package/script/Deploy.s.sol +5 -4
  14. package/src/JB721TiersHook.sol +1 -1
  15. package/src/JB721TiersHookDeployer.sol +1 -1
  16. package/src/JB721TiersHookProjectDeployer.sol +1 -1
  17. package/src/JB721TiersHookStore.sol +23 -17
  18. package/src/libraries/JB721Constants.sol +1 -1
  19. package/src/libraries/JB721TiersRulesetMetadataResolver.sol +1 -1
  20. package/src/libraries/JBBitmap.sol +1 -1
  21. package/src/libraries/JBIpfsDecoder.sol +1 -1
  22. package/src/structs/JB721Tier.sol +5 -11
  23. package/src/structs/JB721TierConfig.sol +5 -20
  24. package/src/structs/JB721TierConfigFlags.sol +26 -0
  25. package/src/structs/JB721TierFlags.sol +17 -0
  26. package/test/721HookAttacks.t.sol +22 -17
  27. package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +19 -14
  28. package/test/Fork.t.sol +69 -54
  29. package/test/TestAuditGaps.sol +73 -56
  30. package/test/TestSafeTransferReentrancy.t.sol +4 -4
  31. package/test/TestVotingUnitsLifecycle.t.sol +11 -11
  32. package/test/audit/CodexPayCreditsBypassTierSplits.t.sol +10 -7
  33. package/test/audit/CodexSplitCreditsMismatch.t.sol +10 -7
  34. package/test/fork/ERC20CashOutFork.t.sol +37 -28
  35. package/test/fork/ERC20TierSplitFork.t.sol +28 -21
  36. package/test/fork/IssueTokensForSplitsFork.t.sol +10 -7
  37. package/test/invariants/handlers/TierLifecycleHandler.sol +10 -7
  38. package/test/invariants/handlers/TierStoreHandler.sol +10 -7
  39. package/test/regression/ProjectDeployerRulesets.t.sol +10 -7
  40. package/test/regression/ReserveBeneficiaryOverwrite.t.sol +6 -6
  41. package/test/unit/AuditFixes_Unit.t.sol +37 -28
  42. package/test/unit/adjustTier_Unit.t.sol +268 -202
  43. package/test/unit/getters_constructor_Unit.t.sol +20 -14
  44. package/test/unit/mintFor_mintReservesFor_Unit.t.sol +2 -2
  45. package/test/unit/pay_Unit.t.sol +1 -1
  46. package/CHANGE_LOG.md +0 -359
@@ -3,6 +3,8 @@ pragma solidity 0.8.28;
3
3
 
4
4
  // forge-lint: disable-next-line(unaliased-plain-import)
5
5
  import "../utils/UnitTestSetup.sol";
6
+ import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
7
+ import {JB721TierFlags} from "../../src/structs/JB721TierFlags.sol";
6
8
 
7
9
  contract Test_Getters_Constructor_Unit is UnitTestSetup {
8
10
  using stdStorage for StdStorage;
@@ -151,11 +153,13 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
151
153
  encodedIPFSUri: bytes32(0),
152
154
  category: uint24(100),
153
155
  discountPercent: uint8(0),
154
- allowOwnerMint: false,
155
- transfersPausable: false,
156
- cantBeRemoved: false,
157
- cantIncreaseDiscountPercent: false,
158
- cantBuyWithCredits: false,
156
+ flags: JB721TierFlags({
157
+ allowOwnerMint: false,
158
+ transfersPausable: false,
159
+ cantBeRemoved: false,
160
+ cantIncreaseDiscountPercent: false,
161
+ cantBuyWithCredits: false
162
+ }),
159
163
  splitPercent: 0,
160
164
  resolvedUri: ""
161
165
  })
@@ -265,7 +269,7 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
265
269
  votingUnits = bound(votingUnits, 1, type(uint32).max);
266
270
  balances = bound(balances, 1, type(uint32).max);
267
271
 
268
- defaultTierConfig.useVotingUnits = true;
272
+ defaultTierConfig.flags.useVotingUnits = true;
269
273
  // forge-lint: disable-next-line(unsafe-typecast)
270
274
  defaultTierConfig.votingUnits = uint32(votingUnits);
271
275
  ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
@@ -474,7 +478,7 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
474
478
  vm.assume(newOwner != trustedForwarder);
475
479
  vm.assume(previousOwner != trustedForwarder);
476
480
 
477
- defaultTierConfig.allowOwnerMint = true;
481
+ defaultTierConfig.flags.allowOwnerMint = true;
478
482
  defaultTierConfig.reserveFrequency = 0;
479
483
  ForTest_JB721TiersHook hook = _initializeForTestHook(10);
480
484
 
@@ -546,13 +550,15 @@ contract Test_Getters_Constructor_Unit is UnitTestSetup {
546
550
  encodedIPFSUri: tokenUris[0],
547
551
  category: uint24(100),
548
552
  discountPercent: uint8(0),
549
- allowOwnerMint: false,
550
- useReserveBeneficiaryAsDefault: false,
551
- transfersPausable: false,
552
- useVotingUnits: true,
553
- cantBeRemoved: false,
554
- cantIncreaseDiscountPercent: false,
555
- cantBuyWithCredits: false,
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
+ }),
556
562
  splitPercent: 0,
557
563
  splits: new JBSplit[](0)
558
564
  });
@@ -414,7 +414,7 @@ contract Test_mintFor_mintReservesFor_Unit is UnitTestSetup {
414
414
  function test_mintFor_mintArrayOfTiers() public {
415
415
  uint256 numberOfTiers = 3;
416
416
 
417
- defaultTierConfig.allowOwnerMint = true;
417
+ defaultTierConfig.flags.allowOwnerMint = true;
418
418
  defaultTierConfig.reserveFrequency = 0;
419
419
  ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
420
420
 
@@ -454,7 +454,7 @@ contract Test_mintFor_mintReservesFor_Unit is UnitTestSetup {
454
454
  }
455
455
 
456
456
  // Set the `allowOwnerMint` flag to false and initialize the hook.
457
- defaultTierConfig.allowOwnerMint = false;
457
+ defaultTierConfig.flags.allowOwnerMint = false;
458
458
  ForTest_JB721TiersHook hook = _initializeForTestHook(numberOfTiers);
459
459
 
460
460
  vm.prank(owner);
@@ -1309,7 +1309,7 @@ contract Test_afterPayRecorded_Unit is UnitTestSetup {
1309
1309
  // as long as the `transfersPausable` flag must be true.
1310
1310
  // Transfers involving the zero address (minting and burning) are not affected.
1311
1311
  function test_transferFrom_revertTransferIfPausedInRuleset() public {
1312
- defaultTierConfig.transfersPausable = true;
1312
+ defaultTierConfig.flags.transfersPausable = true;
1313
1313
  JB721TiersHook hook = _initHookDefaultTiers(10);
1314
1314
 
1315
1315
  // Mock the directory call.
package/CHANGE_LOG.md DELETED
@@ -1,359 +0,0 @@
1
- # nana-721-hook-v6 Changelog (v5 → v6)
2
-
3
- This document describes all changes between `nana-721-hook` (v5) and `nana-721-hook-v6` (v6).
4
-
5
- ## Summary
6
-
7
- - **Tier splits system**: Each NFT tier can now route a percentage of payments to split recipients instead of the project treasury, with full DOS protection via try-catch on all external calls.
8
- - **`votingUnits` replaced by `splitPercent`** in packed tier storage — voting units moved to a separate mapping to free the storage slot for the more widely-needed split feature.
9
- - **New `JB721TiersHookLib` library**: Code extracted from the hook to stay within the EIP-170 contract size limit (24KB).
10
- - **Mutable collection metadata**: `name` and `symbol` can now be changed post-deployment via `setMetadata()`.
11
- - **Prices moved to constructor immutable**: `IJBPrices` is now a constructor parameter instead of being packed in tier config.
12
-
13
- ---
14
-
15
- ## 1. Breaking Changes
16
-
17
- ### 1.1 `IJB721TiersHook` — Changed Function Signatures
18
-
19
- | Function | v5 Signature | v6 Signature | Notes |
20
- |----------|-------------|-------------|-------|
21
- | `pricingContext()` | `returns (uint256, uint256, IJBPrices)` | `returns (uint256 currency, uint256 decimals)` | `IJBPrices` removed from return; now a separate `PRICES()` getter |
22
- | `setMetadata(...)` | `(string baseUri, string contractMetadataUri, IJB721TokenUriResolver, uint256, bytes32)` | `(string name, string symbol, string baseUri, string contractUri, IJB721TokenUriResolver, uint256, bytes32)` | Added `name` and `symbol` parameters; `contractMetadataUri` renamed to `contractUri` |
23
-
24
- ### 1.2 `JB721Hook` (abstract) — Changed Function Signatures
25
-
26
- | Function | v5 Signature | v6 Signature | Notes |
27
- |----------|-------------|-------------|-------|
28
- | `cashOutWeightOf(...)` | `(uint256[] memory, JBBeforeCashOutRecordedContext calldata) returns (uint256)` | `(uint256[] memory) returns (uint256)` | Removed `JBBeforeCashOutRecordedContext` parameter |
29
- | `totalCashOutWeight(...)` | `(JBBeforeCashOutRecordedContext calldata) returns (uint256)` | `() returns (uint256)` | Removed `JBBeforeCashOutRecordedContext` parameter |
30
- | `afterPayRecordedWith(...)` | Reverts if `msg.value != 0` | No `msg.value` check | v6 removes the `msg.value != 0` revert condition from pay hook validation |
31
-
32
- ### 1.3 `JB721TiersHook` — Changed Constructor
33
-
34
- | Parameter | v5 | v6 | Notes |
35
- |-----------|----|----|-------|
36
- | `prices` | Not a parameter (packed in `_packedPricingContext`) | `IJBPrices prices` | Now an immutable constructor parameter |
37
- | `splits` | Not present | `IJBSplits splits` | New immutable for tier split distribution |
38
-
39
- ### 1.4 `JB721InitTiersConfig` — Removed Field
40
-
41
- | Field | v5 | v6 | Notes |
42
- |-------|----|----|-------|
43
- | `prices` | `IJBPrices prices` | Removed | Prices contract moved to `JB721TiersHook` constructor as an immutable |
44
-
45
- ### 1.5 `JB721Constants` — Renamed Constant
46
-
47
- | v5 | v6 | Notes |
48
- |----|----|-------|
49
- | `MAX_DISCOUNT_PERCENT` | `DISCOUNT_DENOMINATOR` | Same value (`200`), renamed for clarity |
50
-
51
- ### 1.6 `JBStored721Tier` — Replaced Field
52
-
53
- | Field | v5 | v6 | Notes |
54
- |-------|----|----|-------|
55
- | `votingUnits` (`uint32`) | Present | Removed | Replaced by `splitPercent` |
56
- | `splitPercent` (`uint32`) | Not present | Added | Percentage of tier price routed to splits |
57
-
58
- > **Why this change**: On-chain voting units were rarely used by projects, while per-tier payment splits were the most requested feature. Reusing the same `uint32` storage slot avoided expanding the packed struct and kept gas costs unchanged. Voting units are still available via the `_tierVotingUnitsOf` mapping.
59
-
60
- ### 1.7 Error Signature Changes (Store)
61
-
62
- Several store errors gained a `tierId` parameter for better debugging:
63
-
64
- | Error | v5 | v6 |
65
- |-------|----|----|
66
- | `CantMintManually` | `()` | `(uint256 tierId)` |
67
- | `CantRemoveTier` | `()` | `(uint256 tierId)` |
68
- | `InsufficientSupplyRemaining` | `()` | `(uint256 tierId)` |
69
- | `ManualMintingNotAllowed` | `()` | `(uint256 tierId)` |
70
- | `ReserveFrequencyNotAllowed` | `()` | `(uint256 tierId)` |
71
- | `UnrecognizedTier` | `()` | `(uint256 tierId)` |
72
- | `VotingUnitsNotAllowed` | `()` | `(uint256 tierId)` |
73
- | `ZeroInitialSupply` | `()` | `(uint256 tierId)` |
74
-
75
- ### 1.8 Solidity Version Change
76
-
77
- All contracts upgraded from `pragma solidity 0.8.23` to `pragma solidity 0.8.28`.
78
-
79
- ### 1.9 Cross-Repo Impact
80
-
81
- > The tier splits system affects `revnet-core-v6`, `croptop-core-v6`, and `nana-omnichain-deployers-v6`, which all deploy 721 hooks and must account for the new `splitPercent`/`splits` fields in `JB721TierConfig`. The `issueTokensForSplits` flag in `JB721TiersHookFlags` is force-set to `false` by `revnet-core-v6` to prevent dilution.
82
-
83
- ---
84
-
85
- ## 2. New Features
86
-
87
- ### 2.1 Tier Splits System
88
-
89
- The largest new feature in v6. Each tier can now define a `splitPercent` and a set of `JBSplit[]` recipients. When an NFT is minted from a tier with splits configured, a portion of the payment is routed to the tier's split group recipients instead of entering the project's balance.
90
-
91
- **New immutables on `JB721TiersHook`:**
92
-
93
- | Name | Type | Description |
94
- |------|------|-------------|
95
- | `PRICES()` | `IJBPrices` | Price feed contract (was previously packed in `_packedPricingContext`) |
96
- | `SPLITS()` | `IJBSplits` | Splits contract for reading/writing tier split groups |
97
-
98
- **New library: `JB721TiersHookLib`** (entirely new in v6)
99
-
100
- Contains functions extracted from `JB721TiersHook` to stay within the EIP-170 contract size limit:
101
-
102
- | Function | Description |
103
- |----------|-------------|
104
- | `adjustTiersFor(...)` | Handles tier removal, addition, event emission, and split group setting |
105
- | `recordAddTiersFor(...)` | Records new tiers and sets their split groups (used during initialization) |
106
- | `normalizePaymentValue(...)` | Normalizes payment value based on pricing context |
107
- | `calculateSplitAmounts(...)` | Calculates per-tier split amounts for a pay event |
108
- | `convertAndCapSplitAmounts(...)` | Converts split amounts between currencies, capped at payment value |
109
- | `calculateWeight(...)` | Adjusts minting weight to account for split amounts |
110
- | `distributeAll(...)` | Pulls tokens and distributes forwarded funds to tier split recipients |
111
- | `resolveTokenURI(...)` | Resolves the token URI (moved IPFS decoding out of hook) |
112
-
113
- **New flag on `JB721TiersHookFlags`:**
114
-
115
- | Field | Type | Description |
116
- |-------|------|-------------|
117
- | `issueTokensForSplits` | `bool` | When `true`, payers receive full token credit even for the portion routed to splits. When `false` (default), weight is reduced proportionally. |
118
-
119
- ### 2.2 Mutable Collection Name and Symbol
120
-
121
- The ERC721 collection `name` and `symbol` can now be changed after initialization via `setMetadata(...)`.
122
-
123
- **New internal functions on `ERC721` (abstract):**
124
-
125
- | Function | Description |
126
- |----------|-------------|
127
- | `_setName(string memory)` | Updates the token collection name |
128
- | `_setSymbol(string memory)` | Updates the token collection symbol |
129
-
130
- ### 2.3 `split.hook` Support in Tier Distribution
131
-
132
- Tier split payouts now support the `split.hook` field (`IJBSplitHook`). The distribution priority follows the same order as `JBMultiTerminal`: hook > projectId > beneficiary. In v5, tier splits did not exist. In v6, this ensures full parity with core split behavior.
133
-
134
- ### 2.4 DOS Protection on Split Distribution
135
-
136
- All external calls during tier split distribution (split hooks, terminal `pay`/`addToBalanceOf`, and leftover distribution) are wrapped in try-catch. A reverting split recipient or terminal cannot brick payments to the project. On failure, the failed amount is accumulated separately and routed to the project balance after the distribution loop completes. This proportional redistribution ensures that later split recipients receive only their configured share, not an inflated share from the failed recipient's funds. A `SplitPayoutReverted` or `AddToBalanceReverted` event is emitted with the revert reason. For ERC-20 split hook failures, tokens are transferred first and the hook callback is best-effort; for ERC-20 terminal failures, the approval is reset to zero.
137
-
138
- ### 2.5 `splitPercent` Bounds Validation
139
-
140
- `JB721TiersHookStore.recordAddTiers` now validates that each tier's `splitPercent` does not exceed `JBConstants.SPLITS_TOTAL_PERCENT`. If it does, it reverts with `JB721TiersHookStore_SplitPercentExceedsBounds(uint256 percent, uint256 limit)`.
141
-
142
- ### 2.6 `beforePayRecordedWith` Override in `JB721TiersHook`
143
-
144
- v6 overrides `beforePayRecordedWith` in `JB721TiersHook` (not just the base `JB721Hook`). The override calculates per-tier split amounts and adjusts the minting weight so the terminal only mints project tokens for the portion of the payment that actually enters the project. It also sets the `JBPayHookSpecification.amount` to the total split amount so the terminal forwards those funds to the hook for distribution.
145
-
146
- ### 2.7 Pricing Decimals Validation
147
-
148
- `initialize(...)` now reverts with `JB721TiersHook_InvalidPricingDecimals` if `tiersConfig.decimals > 18`.
149
-
150
- ### 2.8 `allowSetCustomToken` Pass-Through
151
-
152
- The `JBPayDataHookRulesetMetadata` struct gained an `allowSetCustomToken` field. In v5, the project deployer hardcoded this to `false`. In v6, it passes through the value from the config.
153
-
154
- ---
155
-
156
- ## 3. Event Changes
157
-
158
- ### 3.1 New Events on `IJB721TiersHook`
159
-
160
- | Event | Signature | Description |
161
- |-------|-----------|-------------|
162
- | `AddToBalanceReverted` | `AddToBalanceReverted(uint256 indexed projectId, address token, uint256 amount, bytes reason)` | Emitted when leftover distribution's `addToBalanceOf` call fails. Funds remain in the hook contract. |
163
- | `SetName` | `SetName(string indexed name, address caller)` | Emitted when the collection name is changed |
164
- | `SetSymbol` | `SetSymbol(string indexed symbol, address caller)` | Emitted when the collection symbol is changed |
165
- | `SplitPayoutReverted` | `SplitPayoutReverted(uint256 indexed projectId, JBSplit split, uint256 amount, bytes reason, address caller)` | Emitted when a split payout reverts during distribution. The failed split's funds route to the project balance. |
166
-
167
- ### 3.2 New Event on `IJB721TiersHookStore`
168
-
169
- | Event | Signature | Description |
170
- |-------|-----------|-------------|
171
- | `SetDefaultReserveBeneficiary` | `SetDefaultReserveBeneficiary(address indexed hook, address indexed newBeneficiary, address caller)` | Emitted when the global default reserve beneficiary is changed via `useReserveBeneficiaryAsDefault` |
172
-
173
- ### 3.3 Events Unchanged
174
-
175
- All other events (`AddPayCredits`, `AddTier`, `Mint`, `MintReservedNft`, `RemoveTier`, `SetBaseUri`, `SetContractUri`, `SetDiscountPercent`, `SetEncodedIPFSUri`, `SetTokenUriResolver`, `UsePayCredits`, `CleanTiers`, `HookDeployed`) remain unchanged.
176
-
177
- ---
178
-
179
- ## 4. Error Changes
180
-
181
- ### 4.1 New Errors on `JB721TiersHook`
182
-
183
- | Error | Signature | Description |
184
- |-------|-----------|-------------|
185
- | `JB721TiersHook_CurrencyMismatch` | `(uint256 paymentCurrency, uint256 tierCurrency)` | Reserved for currency mismatch detection |
186
- | `JB721TiersHook_InvalidPricingDecimals` | `(uint256 decimals)` | Reverts when `tiersConfig.decimals > 18` during initialization |
187
-
188
- ### 4.2 New Error on `JB721TiersHookStore`
189
-
190
- | Error | Signature | Description |
191
- |-------|-----------|-------------|
192
- | `JB721TiersHookStore_SplitPercentExceedsBounds` | `(uint256 percent, uint256 limit)` | Reverts when a tier's `splitPercent` exceeds `JBConstants.SPLITS_TOTAL_PERCENT` during `recordAddTiers` |
193
-
194
- ### 4.3 Store Errors with Added `tierId` Parameter
195
-
196
- See Section 1.7 above. Eight store errors gained a `tierId` parameter for improved debuggability.
197
-
198
- ### 4.4 Errors Unchanged
199
-
200
- All other errors (`JB721Hook_InvalidPay`, `JB721Hook_InvalidCashOut`, `JB721Hook_UnauthorizedToken`, `JB721Hook_UnexpectedTokenCashedOut`, `JB721TiersHook_AlreadyInitialized`, `JB721TiersHook_NoProjectId`, `JB721TiersHook_Overspending`, `JB721TiersHook_MintReserveNftsPaused`, `JB721TiersHook_TierTransfersPaused`) remain unchanged.
201
-
202
- ---
203
-
204
- ## 5. Struct Changes
205
-
206
- ### 5.1 `JB721Tier` — Added Field
207
-
208
- | Field | Type | Description |
209
- |-------|------|-------------|
210
- | `splitPercent` | `uint32` | Percentage of the tier's price routed to the tier's split group (out of `JBConstants.SPLITS_TOTAL_PERCENT`). Inserted between `cannotIncreaseDiscountPercent` and `resolvedUri`. |
211
-
212
- ### 5.2 `JB721TierConfig` — Added Fields
213
-
214
- | Field | Type | Description |
215
- |-------|------|-------------|
216
- | `splitPercent` | `uint32` | Percentage of payment routed to splits when minting from this tier |
217
- | `splits` | `JBSplit[]` | The split recipients for this tier's split group |
218
-
219
- ### 5.3 `JBStored721Tier` — Replaced Field
220
-
221
- The `votingUnits` (`uint32`) field was replaced by `splitPercent` (`uint32`) in the storage layout. Voting units are still tracked but no longer stored in the packed tier struct (they use the existing `_tierVotingUnitsOf` mapping).
222
-
223
- ### 5.4 `JB721InitTiersConfig` — Removed Field
224
-
225
- | Field | Type | Description |
226
- |-------|------|-------------|
227
- | `prices` | `IJBPrices` | Removed. The prices contract is now an immutable on the hook itself. |
228
-
229
- ### 5.5 `JB721TiersHookFlags` — Added Field
230
-
231
- | Field | Type | Description |
232
- |-------|------|-------------|
233
- | `issueTokensForSplits` | `bool` | Whether payers receive token credit for the split portion of their payment |
234
-
235
- ### 5.6 `JBPayDataHookRulesetMetadata` — Added Field
236
-
237
- | Field | Type | Description |
238
- |-------|------|-------------|
239
- | `allowSetCustomToken` | `bool` | Whether the project owner can set a custom ERC-20 token. Inserted between `allowOwnerMinting` and `allowTerminalMigration`. |
240
-
241
- ### 5.7 Structs Unchanged
242
-
243
- `JB721TiersMintReservesConfig`, `JB721TiersSetDiscountPercentConfig`, `JB721TiersRulesetMetadata`, `JBBitmapWord`, `JBDeploy721TiersHookConfig`, `JBLaunchProjectConfig`, `JBLaunchRulesetsConfig`, `JBQueueRulesetsConfig`, `JBPayDataHookRulesetConfig` remain structurally identical (import paths updated from core-v5 to core-v6).
244
-
245
- ---
246
-
247
- ## 6. Implementation Changes (Non-Interface)
248
-
249
- ### 6.1 `JB721TiersHook._processPayment` — Split Distribution
250
-
251
- After minting NFTs and updating pay credits, v6 checks `context.hookMetadata` and `context.forwardedAmount.value`. If both are non-zero, it calls `JB721TiersHookLib.distributeAll(...)` to distribute the forwarded funds to tier split recipients. This is the core of the tier splits flow.
252
-
253
- ### 6.2 `JB721TiersHook.adjustTiers` — Delegated to Library
254
-
255
- In v5, `adjustTiers` directly called `STORE.recordRemoveTierIds()` and `STORE.recordAddTiers()` with inline event emission. In v6, the entire operation is delegated to `JB721TiersHookLib.adjustTiersFor(...)`, which also handles setting split groups for newly added tiers in `JBSplits`.
256
-
257
- ### 6.3 `JB721TiersHook.initialize` — Split Group Setup
258
-
259
- During initialization, v6 uses `JB721TiersHookLib.recordAddTiersFor(...)` instead of calling `STORE.recordAddTiers()` directly. This also sets up split groups for tiers that have splits configured.
260
-
261
- ### 6.4 `JB721TiersHook.tokenURI` — Delegated to Library
262
-
263
- In v5, `tokenURI` resolved the URI inline (checking the resolver, then falling back to IPFS decoding). In v6, this is delegated to `JB721TiersHookLib.resolveTokenURI(...)` to reduce contract bytecode size.
264
-
265
- ### 6.5 `JB721TiersHook._processPayment` — Payment Normalization Delegated
266
-
267
- In v5, the payment value normalization (currency conversion via `IJBPrices`) was done inline. In v6, it is delegated to `JB721TiersHookLib.normalizePaymentValue(...)`.
268
-
269
- ### 6.6 `JB721TiersHook._packedPricingContext` — Reduced Packing
270
-
271
- In v5, `_packedPricingContext` packed three values: currency (bits 0-31), decimals (bits 32-39), and prices contract address (bits 40-199). In v6, it only packs currency and decimals (bits 0-39) since the prices contract is now a separate immutable.
272
-
273
- ### 6.7 `JB721Hook.afterPayRecordedWith` — Removed `msg.value` Check
274
-
275
- v5 reverted if `msg.value != 0` in `afterPayRecordedWith`. v6 removes this check because the hook now accepts forwarded native token payments for tier split distribution.
276
-
277
- ### 6.8 `JB721TiersHookLib._sendPayoutToSplit` — `split.hook` Priority
278
-
279
- Split payouts follow the same priority order as `JBMultiTerminal`: if `split.hook` is set, funds are sent to the hook via `processSplitWith`; otherwise if `split.projectId` is set, funds go to the project's primary terminal; otherwise funds go to `split.beneficiary`. For ERC-20 payments to hooks, tokens are transferred first and the hook callback is best-effort (failure does not revert).
280
-
281
- ### 6.9 `JB721TiersHookLib` — Try-Catch on All External Calls
282
-
283
- All external calls during split distribution (split hooks, terminal `pay`, terminal `addToBalanceOf`, and leftover `addToBalanceOf`) are wrapped in try-catch. On failure: the failed amount is accumulated separately and routed to the project balance after the distribution loop, ensuring proportional (not equal) redistribution among remaining recipients. Native token calls return false (funds stay in the hook), ERC-20 terminal call approvals are reset to zero, and `SplitPayoutReverted` or `AddToBalanceReverted` events are emitted with the revert reason.
284
-
285
- ### 6.10 `JB721TiersHookLib.calculateSplitAmounts` — Discount Applied Before Splits
286
-
287
- Split amounts are calculated on the discount-adjusted (effective) tier price rather than the base price. This ensures the split amount matches the actual price the minter pays when a tier discount is active.
288
-
289
- ### 6.11 `JB721TiersHookStore.recordAddTiers` — Validates `splitPercent`
290
-
291
- `recordAddTiers` now reverts with `JB721TiersHookStore_SplitPercentExceedsBounds` if a tier's `splitPercent` exceeds `JBConstants.SPLITS_TOTAL_PERCENT`.
292
-
293
- ### 6.12 `JB721TiersHookStore.recordAddTiers` — Stores `splitPercent`
294
-
295
- The store now stores `splitPercent` in the `JBStored721Tier` packed struct (replacing the `votingUnits` field in storage). The `votingUnits` value continues to be stored in the `_tierVotingUnitsOf` mapping.
296
-
297
- ### 6.13 `JB721TiersHookStore.recordAddTiers` — Emits `SetDefaultReserveBeneficiary`
298
-
299
- When a tier config has `useReserveBeneficiaryAsDefault` set and the beneficiary differs from the current default, v6 emits the new `SetDefaultReserveBeneficiary` event.
300
-
301
- ### 6.14 `JB721TiersHookProjectDeployer` — `allowSetCustomToken` Pass-Through
302
-
303
- In v5, the project deployer hardcoded `allowSetCustomToken: false` when constructing `JBRulesetMetadata`. In v6, it passes through `payDataRulesetConfig.metadata.allowSetCustomToken`.
304
-
305
- ---
306
-
307
- ## 7. Migration Table
308
-
309
- ### 7.1 Interfaces
310
-
311
- | v5 | v6 | Notes |
312
- |----|----|-------|
313
- | `IJB721Hook` | `IJB721Hook` | Unchanged (import path updated) |
314
- | `IJB721TiersHook` | `IJB721TiersHook` | `pricingContext()` return changed; `setMetadata()` signature changed; added `PRICES()`, `SPLITS()`, `SetName`, `SetSymbol`, `SplitPayoutReverted`, `AddToBalanceReverted` |
315
- | `IJB721TiersHookDeployer` | `IJB721TiersHookDeployer` | Unchanged |
316
- | `IJB721TiersHookProjectDeployer` | `IJB721TiersHookProjectDeployer` | Unchanged |
317
- | `IJB721TiersHookStore` | `IJB721TiersHookStore` | Added `SetDefaultReserveBeneficiary` event; NatSpec added |
318
- | `IJB721TokenUriResolver` | `IJB721TokenUriResolver` | Unchanged |
319
-
320
- ### 7.2 Contracts
321
-
322
- | v5 | v6 | Notes |
323
- |----|----|-------|
324
- | `JB721TiersHook` | `JB721TiersHook` | Constructor gains `prices` and `splits`; tier splits system; code extracted to library |
325
- | `JB721TiersHookDeployer` | `JB721TiersHookDeployer` | Unchanged (import paths updated) |
326
- | `JB721TiersHookProjectDeployer` | `JB721TiersHookProjectDeployer` | `allowSetCustomToken` pass-through |
327
- | `JB721TiersHookStore` | `JB721TiersHookStore` | `splitPercent` storage and validation; error params added; `SplitPercentExceedsBounds` error; `SetDefaultReserveBeneficiary` event |
328
- | `JB721Hook` (abstract) | `JB721Hook` (abstract) | `cashOutWeightOf`/`totalCashOutWeight` signatures simplified; `msg.value` check removed from `afterPayRecordedWith` |
329
- | `ERC721` (abstract) | `ERC721` (abstract) | Added `_setName()` and `_setSymbol()` |
330
-
331
- ### 7.3 Libraries
332
-
333
- | v5 | v6 | Notes |
334
- |----|----|-------|
335
- | `JB721Constants` | `JB721Constants` | `MAX_DISCOUNT_PERCENT` renamed to `DISCOUNT_DENOMINATOR` |
336
- | `JB721TiersRulesetMetadataResolver` | `JB721TiersRulesetMetadataResolver` | Unchanged |
337
- | `JBBitmap` | `JBBitmap` | Unchanged |
338
- | `JBIpfsDecoder` | `JBIpfsDecoder` | Unchanged |
339
- | _(not present)_ | `JB721TiersHookLib` | New library for tier splits, payment normalization, weight calculation, and token URI resolution |
340
-
341
- ### 7.4 Structs
342
-
343
- | v5 | v6 | Notes |
344
- |----|----|-------|
345
- | `JB721Tier` | `JB721Tier` | Added `splitPercent` field |
346
- | `JB721TierConfig` | `JB721TierConfig` | Added `splitPercent` and `splits` fields |
347
- | `JB721TiersHookFlags` | `JB721TiersHookFlags` | Added `issueTokensForSplits` field |
348
- | `JB721InitTiersConfig` | `JB721InitTiersConfig` | Removed `prices` field |
349
- | `JBStored721Tier` | `JBStored721Tier` | `votingUnits` replaced by `splitPercent` |
350
- | `JBPayDataHookRulesetMetadata` | `JBPayDataHookRulesetMetadata` | Added `allowSetCustomToken` field |
351
- | `JB721TiersMintReservesConfig` | `JB721TiersMintReservesConfig` | Unchanged |
352
- | `JB721TiersSetDiscountPercentConfig` | `JB721TiersSetDiscountPercentConfig` | Unchanged |
353
- | `JB721TiersRulesetMetadata` | `JB721TiersRulesetMetadata` | Unchanged |
354
- | `JBBitmapWord` | `JBBitmapWord` | Unchanged |
355
- | `JBDeploy721TiersHookConfig` | `JBDeploy721TiersHookConfig` | Unchanged |
356
- | `JBLaunchProjectConfig` | `JBLaunchProjectConfig` | Unchanged |
357
- | `JBLaunchRulesetsConfig` | `JBLaunchRulesetsConfig` | Unchanged |
358
- | `JBQueueRulesetsConfig` | `JBQueueRulesetsConfig` | Unchanged |
359
- | `JBPayDataHookRulesetConfig` | `JBPayDataHookRulesetConfig` | Unchanged |