@bananapus/721-hook-v6 0.0.1 → 0.0.2

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.
@@ -1,10 +1,13 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
+ import {IJBCashOutHook} from "@bananapus/core-v6/src/interfaces/IJBCashOutHook.sol";
5
+ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
6
+ import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
4
7
  import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
8
+ import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
5
9
  import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
6
10
 
7
- import {IJB721Hook} from "./IJB721Hook.sol";
8
11
  import {IJB721TiersHookStore} from "./IJB721TiersHookStore.sol";
9
12
  import {IJB721TokenUriResolver} from "./IJB721TokenUriResolver.sol";
10
13
  import {JB721InitTiersConfig} from "../structs/JB721InitTiersConfig.sol";
@@ -13,7 +16,7 @@ import {JB721TiersHookFlags} from "../structs/JB721TiersHookFlags.sol";
13
16
  import {JB721TiersMintReservesConfig} from "../structs/JB721TiersMintReservesConfig.sol";
14
17
  import {JB721TiersSetDiscountPercentConfig} from "../structs/JB721TiersSetDiscountPercentConfig.sol";
15
18
 
16
- interface IJB721TiersHook is IJB721Hook {
19
+ interface IJB721TiersHook is IJBRulesetDataHook, IJBPayHook, IJBCashOutHook {
17
20
  event AddPayCredits(
18
21
  uint256 indexed amount, uint256 indexed newTotalCredits, address indexed account, address caller
19
22
  );
@@ -36,6 +39,18 @@ interface IJB721TiersHook is IJB721Hook {
36
39
  uint256 indexed amount, uint256 indexed newTotalCredits, address indexed account, address caller
37
40
  );
38
41
 
42
+ /// @notice The directory of terminals and controllers for projects.
43
+ /// @return The directory contract.
44
+ function DIRECTORY() external view returns (IJBDirectory);
45
+
46
+ /// @notice The ID used when parsing metadata.
47
+ /// @return The address of the metadata ID target.
48
+ function METADATA_ID_TARGET() external view returns (address);
49
+
50
+ /// @notice The ID of the project that this contract is associated with.
51
+ /// @return The project ID.
52
+ function PROJECT_ID() external view returns (uint256);
53
+
39
54
  /// @notice The contract storing and managing project rulesets.
40
55
  /// @return The rulesets contract.
41
56
  function RULESETS() external view returns (IJBRulesets);
@@ -0,0 +1,336 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.23;
3
+
4
+ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
5
+ import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
6
+ import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
7
+ import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
8
+ import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
9
+ import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
10
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
11
+ import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
12
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
13
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
14
+ import {mulDiv} from "@prb/math/src/Common.sol";
15
+
16
+ import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
17
+
18
+ import {IJB721TiersHookStore} from "../interfaces/IJB721TiersHookStore.sol";
19
+ import {JB721TierConfig} from "../structs/JB721TierConfig.sol";
20
+
21
+ /// @notice External library for JB721TiersHook operations extracted to stay within the EIP-170 contract size limit.
22
+ /// @dev Handles tier adjustments, split calculations, price normalization, and split fund distribution.
23
+ library JB721TiersHookLib {
24
+ // Events mirrored from IJB721TiersHook (emitted via DELEGATECALL from the hook's context).
25
+ event AddTier(uint256 indexed tierId, JB721TierConfig tier, address caller);
26
+ event RemoveTier(uint256 indexed tierId, address caller);
27
+
28
+ /// @notice Handles the full tier adjustment logic: removes tiers, adds tiers, emits events, and sets splits.
29
+ /// @dev Called via DELEGATECALL from the hook, so events are emitted from the hook's address.
30
+ /// @param store The 721 tiers hook store.
31
+ /// @param directory The directory to look up controllers.
32
+ /// @param projectId The project ID.
33
+ /// @param hookAddress The hook address.
34
+ /// @param caller The msg.sender of the original call (for event emission).
35
+ /// @param tiersToAdd The tier configs to add.
36
+ /// @param tierIdsToRemove The tier IDs to remove.
37
+ function adjustTiersFor(
38
+ IJB721TiersHookStore store,
39
+ IJBDirectory directory,
40
+ uint256 projectId,
41
+ address hookAddress,
42
+ address caller,
43
+ JB721TierConfig[] calldata tiersToAdd,
44
+ uint256[] calldata tierIdsToRemove
45
+ )
46
+ external
47
+ {
48
+ // Remove tiers.
49
+ if (tierIdsToRemove.length != 0) {
50
+ for (uint256 i; i < tierIdsToRemove.length; i++) {
51
+ emit RemoveTier({tierId: tierIdsToRemove[i], caller: caller});
52
+ }
53
+ store.recordRemoveTierIds(tierIdsToRemove);
54
+ }
55
+
56
+ // Add tiers.
57
+ if (tiersToAdd.length != 0) {
58
+ uint256[] memory tierIdsAdded = store.recordAddTiers(tiersToAdd);
59
+
60
+ // slither-disable-next-line reentrancy-events
61
+ for (uint256 i; i < tiersToAdd.length; i++) {
62
+ emit AddTier({tierId: tierIdsAdded[i], tier: tiersToAdd[i], caller: caller});
63
+ }
64
+
65
+ // Set split groups for tiers that have splits configured.
66
+ _setSplitGroupsFor(directory, projectId, hookAddress, tiersToAdd, tierIdsAdded);
67
+ }
68
+ }
69
+
70
+ /// @notice Normalizes a payment value based on the packed pricing context.
71
+ /// @param packedPricingContext The packed pricing context (currency, decimals, prices address).
72
+ /// @param projectId The project ID.
73
+ /// @param amountValue The payment amount value.
74
+ /// @param amountCurrency The payment amount currency.
75
+ /// @param amountDecimals The payment amount decimals.
76
+ /// @return value The normalized value.
77
+ /// @return valid Whether the value is valid (false means no prices contract and currencies differ).
78
+ function normalizePaymentValue(
79
+ uint256 packedPricingContext,
80
+ uint256 projectId,
81
+ uint256 amountValue,
82
+ uint256 amountCurrency,
83
+ uint256 amountDecimals
84
+ )
85
+ external
86
+ view
87
+ returns (uint256 value, bool valid)
88
+ {
89
+ uint256 pricingCurrency = uint256(uint32(packedPricingContext));
90
+ if (amountCurrency == pricingCurrency) return (amountValue, true);
91
+
92
+ IJBPrices prices = IJBPrices(address(uint160(packedPricingContext >> 40)));
93
+ if (address(prices) == address(0)) return (0, false);
94
+
95
+ uint256 pricingDecimals = uint256(uint8(packedPricingContext >> 32));
96
+ value = mulDiv(
97
+ amountValue,
98
+ 10 ** pricingDecimals,
99
+ prices.pricePerUnitOf({
100
+ projectId: projectId,
101
+ pricingCurrency: amountCurrency,
102
+ unitCurrency: pricingCurrency,
103
+ decimals: amountDecimals
104
+ })
105
+ );
106
+ valid = true;
107
+ }
108
+
109
+ /// @notice Calculates per-tier split amounts for a pay event.
110
+ /// @param store The 721 tiers hook store.
111
+ /// @param hook The hook address.
112
+ /// @param metadataIdTarget The metadata ID target for resolving pay metadata.
113
+ /// @param metadata The payer metadata.
114
+ /// @return totalSplitAmount The total amount to forward for splits.
115
+ /// @return hookMetadata Encoded per-tier breakdown (tierIds, amounts) for afterPay.
116
+ function calculateSplitAmounts(
117
+ IJB721TiersHookStore store,
118
+ address hook,
119
+ address metadataIdTarget,
120
+ bytes calldata metadata
121
+ )
122
+ external
123
+ view
124
+ returns (uint256 totalSplitAmount, bytes memory hookMetadata)
125
+ {
126
+ bytes memory data;
127
+ {
128
+ bool found;
129
+ (found, data) = JBMetadataResolver.getDataFor(JBMetadataResolver.getId("pay", metadataIdTarget), metadata);
130
+ if (!found) return (0, bytes(""));
131
+ }
132
+
133
+ (, uint16[] memory tierIdsToMint) = abi.decode(data, (bool, uint16[]));
134
+ if (tierIdsToMint.length == 0) return (0, bytes(""));
135
+
136
+ uint16[] memory splitTierIds = new uint16[](tierIdsToMint.length);
137
+ uint256[] memory splitAmounts = new uint256[](tierIdsToMint.length);
138
+ uint256 splitTierCount;
139
+
140
+ for (uint256 i; i < tierIdsToMint.length; i++) {
141
+ // slither-disable-next-line calls-loop
142
+ uint256 splitPercent = store.tierOf(hook, tierIdsToMint[i], false).splitPercent;
143
+ if (splitPercent != 0) {
144
+ // slither-disable-next-line calls-loop
145
+ uint256 price = store.tierOf(hook, tierIdsToMint[i], false).price;
146
+ splitTierIds[splitTierCount] = tierIdsToMint[i];
147
+ splitAmounts[splitTierCount] = mulDiv(price, splitPercent, JBConstants.SPLITS_TOTAL_PERCENT);
148
+ totalSplitAmount += splitAmounts[splitTierCount];
149
+ splitTierCount++;
150
+ }
151
+ }
152
+
153
+ if (splitTierCount != 0) {
154
+ assembly {
155
+ mstore(splitTierIds, splitTierCount)
156
+ mstore(splitAmounts, splitTierCount)
157
+ }
158
+ hookMetadata = abi.encode(splitTierIds, splitAmounts);
159
+ }
160
+ }
161
+
162
+ /// @notice Sets split groups in JBSplits for tiers that have splits configured.
163
+ function _setSplitGroupsFor(
164
+ IJBDirectory directory,
165
+ uint256 projectId,
166
+ address hookAddress,
167
+ JB721TierConfig[] calldata tiersToAdd,
168
+ uint256[] memory tierIdsAdded
169
+ )
170
+ private
171
+ {
172
+ uint256 splitGroupCount;
173
+ for (uint256 i; i < tiersToAdd.length; i++) {
174
+ if (tiersToAdd[i].splits.length != 0) splitGroupCount++;
175
+ }
176
+ if (splitGroupCount == 0) return;
177
+
178
+ JBSplitGroup[] memory splitGroups = new JBSplitGroup[](splitGroupCount);
179
+ uint256 groupIndex;
180
+ for (uint256 i; i < tiersToAdd.length; i++) {
181
+ if (tiersToAdd[i].splits.length != 0) {
182
+ splitGroups[groupIndex] = JBSplitGroup({
183
+ groupId: uint256(uint160(hookAddress)) | (tierIdsAdded[i] << 160), splits: tiersToAdd[i].splits
184
+ });
185
+ groupIndex++;
186
+ }
187
+ }
188
+ IJBController(address(directory.controllerOf(projectId))).SPLITS().setSplitGroupsOf(projectId, 0, splitGroups);
189
+ }
190
+
191
+ /// @notice Distributes forwarded funds for all tiers in the hook metadata.
192
+ /// @param directory The directory to look up controllers and terminals.
193
+ /// @param projectId The project ID of the hook.
194
+ /// @param hookAddress The hook address (for computing split group IDs).
195
+ /// @param token The token being distributed.
196
+ /// @param encodedSplitData The encoded per-tier breakdown from hookMetadata.
197
+ function distributeAll(
198
+ IJBDirectory directory,
199
+ uint256 projectId,
200
+ address hookAddress,
201
+ address token,
202
+ bytes calldata encodedSplitData
203
+ )
204
+ external
205
+ {
206
+ (uint16[] memory tierIds, uint256[] memory amounts) = abi.decode(encodedSplitData, (uint16[], uint256[]));
207
+
208
+ IJBSplits splitsContract = IJBController(address(directory.controllerOf(projectId))).SPLITS();
209
+
210
+ for (uint256 i; i < tierIds.length; i++) {
211
+ if (amounts[i] == 0) continue;
212
+ uint256 groupId = uint256(uint160(hookAddress)) | (uint256(tierIds[i]) << 160);
213
+ _distributeSingleSplit(directory, splitsContract, projectId, token, groupId, amounts[i]);
214
+ }
215
+ }
216
+
217
+ /// @notice Distributes funds for a single tier's split group.
218
+ function _distributeSingleSplit(
219
+ IJBDirectory directory,
220
+ IJBSplits splitsContract,
221
+ uint256 projectId,
222
+ address token,
223
+ uint256 groupId,
224
+ uint256 amount
225
+ )
226
+ private
227
+ {
228
+ // slither-disable-next-line calls-loop
229
+ JBSplit[] memory tierSplits = splitsContract.splitsOf(projectId, 0, groupId);
230
+
231
+ bool isNativeToken = token == JBConstants.NATIVE_TOKEN;
232
+ uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;
233
+ uint256 leftoverAmount = amount;
234
+
235
+ for (uint256 j; j < tierSplits.length; j++) {
236
+ uint256 payoutAmount = mulDiv(amount, tierSplits[j].percent, leftoverPercentage);
237
+ if (payoutAmount != 0) {
238
+ _sendPayoutToSplit(directory, tierSplits[j], token, payoutAmount, isNativeToken);
239
+ unchecked {
240
+ leftoverAmount -= payoutAmount;
241
+ }
242
+ }
243
+ unchecked {
244
+ leftoverPercentage -= tierSplits[j].percent;
245
+ }
246
+ }
247
+
248
+ if (leftoverAmount != 0) {
249
+ _addToBalance(directory, projectId, token, leftoverAmount, isNativeToken);
250
+ }
251
+ }
252
+
253
+ function _sendPayoutToSplit(
254
+ IJBDirectory directory,
255
+ JBSplit memory split,
256
+ address token,
257
+ uint256 amount,
258
+ bool isNativeToken
259
+ )
260
+ private
261
+ {
262
+ if (split.projectId != 0) {
263
+ // slither-disable-next-line calls-loop
264
+ IJBTerminal terminal = directory.primaryTerminalOf(split.projectId, token);
265
+ if (address(terminal) == address(0)) return;
266
+
267
+ if (split.preferAddToBalance) {
268
+ _terminalAddToBalance(terminal, split.projectId, token, amount, isNativeToken);
269
+ } else {
270
+ _terminalPay(terminal, split.projectId, token, amount, split.beneficiary, isNativeToken);
271
+ }
272
+ } else if (split.beneficiary != address(0)) {
273
+ if (isNativeToken) {
274
+ // slither-disable-next-line arbitrary-send-eth,calls-loop
275
+ (bool success,) = split.beneficiary.call{value: amount}("");
276
+ if (!success) revert();
277
+ } else {
278
+ SafeERC20.safeTransfer(IERC20(token), split.beneficiary, amount);
279
+ }
280
+ }
281
+ }
282
+
283
+ function _addToBalance(
284
+ IJBDirectory directory,
285
+ uint256 projectId,
286
+ address token,
287
+ uint256 amount,
288
+ bool isNativeToken
289
+ )
290
+ private
291
+ {
292
+ // slither-disable-next-line calls-loop
293
+ IJBTerminal terminal = directory.primaryTerminalOf(projectId, token);
294
+ if (address(terminal) == address(0)) return;
295
+ _terminalAddToBalance(terminal, projectId, token, amount, isNativeToken);
296
+ }
297
+
298
+ function _terminalAddToBalance(
299
+ IJBTerminal terminal,
300
+ uint256 projectId,
301
+ address token,
302
+ uint256 amount,
303
+ bool isNativeToken
304
+ )
305
+ private
306
+ {
307
+ if (isNativeToken) {
308
+ // slither-disable-next-line arbitrary-send-eth,calls-loop
309
+ terminal.addToBalanceOf{value: amount}(projectId, token, amount, false, "", bytes(""));
310
+ } else {
311
+ SafeERC20.forceApprove(IERC20(token), address(terminal), amount);
312
+ // slither-disable-next-line calls-loop
313
+ terminal.addToBalanceOf(projectId, token, amount, false, "", bytes(""));
314
+ }
315
+ }
316
+
317
+ function _terminalPay(
318
+ IJBTerminal terminal,
319
+ uint256 projectId,
320
+ address token,
321
+ uint256 amount,
322
+ address beneficiary,
323
+ bool isNativeToken
324
+ )
325
+ private
326
+ {
327
+ if (isNativeToken) {
328
+ // slither-disable-next-line arbitrary-send-eth,unused-return,calls-loop
329
+ terminal.pay{value: amount}(projectId, token, amount, beneficiary, 0, "", bytes(""));
330
+ } else {
331
+ SafeERC20.forceApprove(IERC20(token), address(terminal), amount);
332
+ // slither-disable-next-line unused-return,calls-loop
333
+ terminal.pay(projectId, token, amount, beneficiary, 0, "", bytes(""));
334
+ }
335
+ }
336
+ }
@@ -18,6 +18,8 @@ pragma solidity ^0.8.0;
18
18
  /// @custom:member cannotBeRemoved A boolean indicating whether attempts to remove this tier will revert.
19
19
  /// @custom:member cannotIncreaseDiscountPercent If the tier cannot have its discount increased.
20
20
  /// @custom:member transfersPausable A boolean indicating whether transfers for NFTs in tier can be paused.
21
+ /// @custom:member splitPercent The percentage of the tier's price that gets routed to the project's split group when
22
+ /// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
21
23
  /// @custom:member resolvedUri A resolved token URI for NFTs in this tier. Only available if the NFT this tier belongs
22
24
  /// to has a resolver.
23
25
  struct JB721Tier {
@@ -35,5 +37,6 @@ struct JB721Tier {
35
37
  bool transfersPausable;
36
38
  bool cannotBeRemoved;
37
39
  bool cannotIncreaseDiscountPercent;
40
+ uint32 splitPercent;
38
41
  string resolvedUri;
39
42
  }
@@ -1,6 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.0;
3
3
 
4
+ import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
5
+
4
6
  /// @notice Config for a single NFT tier within a `JB721TiersHook`.
5
7
  /// @custom:member price The price to buy an NFT in this tier, in terms of the currency in its `JBInitTiersConfig`.
6
8
  /// @custom:member initialSupply The total number of NFTs which can be minted from this tier.
@@ -22,6 +24,10 @@ pragma solidity ^0.8.0;
22
24
  /// power. If `useVotingUnits` is false, voting power is based on the tier's price.
23
25
  /// @custom:member cannotBeRemoved If the tier cannot be removed once added.
24
26
  /// @custom:member cannotIncreaseDiscount If the tier cannot have its discount increased.
27
+ /// @custom:member splitPercent The percentage of the tier's price that gets routed to the tier's split group when
28
+ /// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
29
+ /// @custom:member splits The splits to use for this tier's split group. These define where the split portion of the
30
+ /// tier's price gets routed when an NFT from this tier is minted.
25
31
  struct JB721TierConfig {
26
32
  uint104 price;
27
33
  uint32 initialSupply;
@@ -37,4 +43,6 @@ struct JB721TierConfig {
37
43
  bool useVotingUnits;
38
44
  bool cannotBeRemoved;
39
45
  bool cannotIncreaseDiscountPercent;
46
+ uint32 splitPercent;
47
+ JBSplit[] splits;
40
48
  }
@@ -4,19 +4,20 @@ pragma solidity ^0.8.0;
4
4
  /// @custom:member price The price to buy an NFT in this tier, in terms of the currency in its `JBInitTiersConfig`.
5
5
  /// @custom:member remainingSupply The remaining number of NFTs which can be minted from this tier.
6
6
  /// @custom:member initialSupply The total number of NFTs which can be minted from this tier.
7
- /// @custom:member votingUnits The number of votes that each NFT in this tier gets.
7
+ /// @custom:member splitPercent The percentage of the tier's price that gets routed to the tier's split group when
8
+ /// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
8
9
  /// @custom:member category The category that NFTs in this tier belongs to. Used to group NFT tiers.
9
10
  /// @custom:member discountPercent The discount that should be applied to the tier.
10
11
  /// @custom:member reserveFrequency The frequency at which an extra NFT is minted for the `reserveBeneficiary` from this
11
12
  /// tier. With a `reserveFrequency` of 5, an extra NFT will be minted for the `reserveBeneficiary` for every 5 NFTs
12
13
  /// purchased.
13
- /// @custom:member packedBools A packed uint8 containing boolean flags: bit 0 = allowOwnerMint, bit 1 =
14
- /// transfersPausable, bit 2 = useVotingUnits, bit 3 = cannotBeRemoved, bit 4 = cannotIncreaseDiscountPercent.
14
+ /// @custom:member packedBools Packed boolean flags: allowOwnerMint, transfersPausable, useVotingUnits,
15
+ /// cannotBeRemoved, cannotIncreaseDiscountPercent.
15
16
  struct JBStored721Tier {
16
17
  uint104 price;
17
18
  uint32 remainingSupply;
18
19
  uint32 initialSupply;
19
- uint32 votingUnits;
20
+ uint32 splitPercent;
20
21
  uint24 category;
21
22
  uint8 discountPercent;
22
23
  uint16 reserveFrequency;
@@ -85,7 +85,9 @@ contract NFTHookAttacks is UnitTestSetup {
85
85
  transfersPausable: false,
86
86
  cannotBeRemoved: false,
87
87
  cannotIncreaseDiscountPercent: false,
88
- useVotingUnits: false
88
+ useVotingUnits: false,
89
+ splitPercent: 0,
90
+ splits: new JBSplit[](0)
89
91
  });
90
92
 
91
93
  vm.prank(owner);
@@ -370,7 +372,9 @@ contract NFTHookAttacks is UnitTestSetup {
370
372
  transfersPausable: false,
371
373
  cannotBeRemoved: false,
372
374
  cannotIncreaseDiscountPercent: false,
373
- useVotingUnits: false
375
+ useVotingUnits: false,
376
+ splitPercent: 0,
377
+ splits: new JBSplit[](0)
374
378
  });
375
379
 
376
380
  vm.prank(attacker);
@@ -778,7 +778,9 @@ contract Test_TiersHook_E2E is TestBaseWorkflow {
778
778
  transfersPausable: false,
779
779
  useVotingUnits: false,
780
780
  cannotBeRemoved: false,
781
- cannotIncreaseDiscountPercent: false
781
+ cannotIncreaseDiscountPercent: false,
782
+ splitPercent: 0,
783
+ splits: new JBSplit[](0)
782
784
  });
783
785
  }
784
786
  tiersHookConfig = JBDeploy721TiersHookConfig({
@@ -869,7 +871,9 @@ contract Test_TiersHook_E2E is TestBaseWorkflow {
869
871
  transfersPausable: false,
870
872
  useVotingUnits: false,
871
873
  cannotBeRemoved: false,
872
- cannotIncreaseDiscountPercent: false
874
+ cannotIncreaseDiscountPercent: false,
875
+ splitPercent: 0,
876
+ splits: new JBSplit[](0)
873
877
  });
874
878
 
875
879
  tiersHookConfig = JBDeploy721TiersHookConfig({
@@ -163,7 +163,9 @@ contract TierLifecycleHandler is Test {
163
163
  transfersPausable: false,
164
164
  useVotingUnits: false,
165
165
  cannotBeRemoved: false,
166
- cannotIncreaseDiscountPercent: false
166
+ cannotIncreaseDiscountPercent: false,
167
+ splitPercent: 0,
168
+ splits: new JBSplit[](0)
167
169
  });
168
170
 
169
171
  vm.prank(hookAddress);
@@ -8,6 +8,7 @@ import {StdUtils} from "forge-std/StdUtils.sol";
8
8
  import {JB721TiersHookStore} from "../../../src/JB721TiersHookStore.sol";
9
9
  import {JB721TierConfig} from "../../../src/structs/JB721TierConfig.sol";
10
10
  import {JB721TiersHookFlags} from "../../../src/structs/JB721TiersHookFlags.sol";
11
+ import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
11
12
 
12
13
  /// @notice Handler for JB721TiersHookStore invariant tests.
13
14
  /// @dev Acts as the "hook" address itself, so msg.sender (this) == hook in the store.
@@ -61,7 +62,9 @@ contract TierStoreHandler is CommonBase, StdCheats, StdUtils {
61
62
  transfersPausable: false,
62
63
  useVotingUnits: false,
63
64
  cannotBeRemoved: false,
64
- cannotIncreaseDiscountPercent: false
65
+ cannotIncreaseDiscountPercent: false,
66
+ splitPercent: 0,
67
+ splits: new JBSplit[](0)
65
68
  });
66
69
 
67
70
  try this._doAddTiers(configs) {