@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,516 +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 "forge-std/Test.sol";
6
-
7
- // forge-lint: disable-next-line(unaliased-plain-import)
8
- import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
9
-
10
- // forge-lint: disable-next-line(unaliased-plain-import)
11
- import "@bananapus/core-v6/src/JBController.sol";
12
- // forge-lint: disable-next-line(unaliased-plain-import)
13
- import "@bananapus/core-v6/src/JBDirectory.sol";
14
- // forge-lint: disable-next-line(unaliased-plain-import)
15
- import "@bananapus/core-v6/src/JBMultiTerminal.sol";
16
- // forge-lint: disable-next-line(unaliased-plain-import)
17
- import "@bananapus/core-v6/src/JBFundAccessLimits.sol";
18
- // forge-lint: disable-next-line(unaliased-plain-import)
19
- import "@bananapus/core-v6/src/JBFeelessAddresses.sol";
20
- // forge-lint: disable-next-line(unaliased-plain-import)
21
- import "@bananapus/core-v6/src/JBTerminalStore.sol";
22
- // forge-lint: disable-next-line(unaliased-plain-import)
23
- import "@bananapus/core-v6/src/JBRulesets.sol";
24
- // forge-lint: disable-next-line(unaliased-plain-import)
25
- import "@bananapus/core-v6/src/JBPermissions.sol";
26
- // forge-lint: disable-next-line(unaliased-plain-import)
27
- import "@bananapus/core-v6/src/JBPrices.sol";
28
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
29
- // forge-lint: disable-next-line(unaliased-plain-import)
30
- import "@bananapus/core-v6/src/JBSplits.sol";
31
- // forge-lint: disable-next-line(unaliased-plain-import)
32
- import "@bananapus/core-v6/src/JBERC20.sol";
33
- // forge-lint: disable-next-line(unaliased-plain-import)
34
- import "@bananapus/core-v6/src/JBTokens.sol";
35
- // forge-lint: disable-next-line(unaliased-plain-import)
36
- import "@bananapus/core-v6/src/libraries/JBConstants.sol";
37
- // forge-lint: disable-next-line(unaliased-plain-import)
38
- import "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
39
- // forge-lint: disable-next-line(unaliased-plain-import)
40
- import "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
41
- // forge-lint: disable-next-line(unaliased-plain-import)
42
- import "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
43
- // forge-lint: disable-next-line(unaliased-plain-import)
44
- import "@bananapus/core-v6/src/structs/JBSplit.sol";
45
- // forge-lint: disable-next-line(unaliased-plain-import)
46
- import "@bananapus/core-v6/src/structs/JBFundAccessLimitGroup.sol";
47
- // forge-lint: disable-next-line(unaliased-plain-import)
48
- import "@bananapus/core-v6/src/interfaces/IJBRulesetApprovalHook.sol";
49
- // forge-lint: disable-next-line(unaliased-plain-import)
50
- import "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
51
- // forge-lint: disable-next-line(unaliased-plain-import)
52
- import "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
53
- // forge-lint: disable-next-line(unaliased-plain-import)
54
- import "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
55
- import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
56
- import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
57
- // forge-lint: disable-next-line(unused-import)
58
- import {mulDiv} from "@prb/math/src/Common.sol";
59
-
60
- // forge-lint: disable-next-line(unaliased-plain-import)
61
- import "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
62
-
63
- // forge-lint: disable-next-line(unaliased-plain-import)
64
- import "../../src/JB721TiersHook.sol";
65
- // forge-lint: disable-next-line(unaliased-plain-import)
66
- import "../../src/JB721TiersHookDeployer.sol";
67
- // forge-lint: disable-next-line(unaliased-plain-import)
68
- import "../../src/JB721TiersHookProjectDeployer.sol";
69
- // forge-lint: disable-next-line(unaliased-plain-import)
70
- import "../../src/JB721TiersHookStore.sol";
71
- import {JB721CheckpointsDeployer} from "../../src/JB721CheckpointsDeployer.sol";
72
- import {IJB721CheckpointsDeployer} from "../../src/interfaces/IJB721CheckpointsDeployer.sol";
73
- // forge-lint: disable-next-line(unaliased-plain-import)
74
- import "../../src/interfaces/IJB721TiersHook.sol";
75
- // forge-lint: disable-next-line(unaliased-plain-import)
76
- import "../../src/structs/JBDeploy721TiersHookConfig.sol";
77
- // forge-lint: disable-next-line(unaliased-plain-import)
78
- import "../../src/structs/JBLaunchProjectConfig.sol";
79
- // forge-lint: disable-next-line(unaliased-plain-import)
80
- import "../../src/structs/JBPayDataHookRulesetConfig.sol";
81
- // forge-lint: disable-next-line(unaliased-plain-import)
82
- import "../../src/structs/JBPayDataHookRulesetMetadata.sol";
83
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
84
-
85
- /// @title IssueTokensForSplitsFork
86
- /// @notice Fork tests for the issueTokensForSplits flag in JB721TiersHookFlags.
87
- /// @dev When `issueTokensForSplits = true`, the payer gets tokens at full weight regardless of split routing.
88
- /// When `false`, weight is scaled by `(amountValue - totalSplitAmount) / amountValue`.
89
- /// @dev Run with: forge test --match-contract IssueTokensForSplitsFork -vvv --fork-url $RPC
90
- contract IssueTokensForSplitsFork is Test {
91
- using JBRulesetMetadataResolver for JBRuleset;
92
-
93
- // =========================================================================
94
- // Constants
95
- // =========================================================================
96
-
97
- address constant NATIVE_TOKEN = JBConstants.NATIVE_TOKEN;
98
-
99
- // =========================================================================
100
- // Actors
101
- // =========================================================================
102
-
103
- address multisig = address(0xBEEF);
104
- address payer = makeAddr("payer");
105
- address beneficiary = makeAddr("beneficiary");
106
- address reserveBeneficiary = makeAddr("reserveBeneficiary");
107
- address splitBeneficiary = makeAddr("splitBeneficiary");
108
-
109
- // =========================================================================
110
- // JB Core
111
- // =========================================================================
112
-
113
- JBPermissions jbPermissions;
114
- JBProjects jbProjects;
115
- JBDirectory jbDirectory;
116
- JBRulesets jbRulesets;
117
- JBTokens jbTokens;
118
- JBSplits jbSplits;
119
- JBFundAccessLimits jbFundAccessLimits;
120
- JBFeelessAddresses jbFeelessAddresses;
121
- JBPrices jbPrices;
122
- JBController jbController;
123
- JBTerminalStore jbTerminalStore;
124
- JBMultiTerminal jbMultiTerminal;
125
-
126
- // =========================================================================
127
- // 721 Hook
128
- // =========================================================================
129
-
130
- JB721TiersHookStore store;
131
- JB721TiersHook hookImpl;
132
- JB721TiersHookDeployer hookDeployer;
133
- JB721TiersHookProjectDeployer projectDeployer;
134
- MetadataResolverHelper metadataHelper;
135
- JBAddressRegistry addressRegistry;
136
-
137
- // =========================================================================
138
- // Setup
139
- // =========================================================================
140
-
141
- receive() external payable {}
142
-
143
- function setUp() public {
144
- vm.createSelectFork("ethereum");
145
-
146
- _deployJBCore();
147
- _deploy721Hook();
148
-
149
- vm.deal(payer, 1000 ether);
150
- vm.deal(beneficiary, 100 ether);
151
- vm.deal(multisig, 100 ether);
152
-
153
- vm.label(multisig, "multisig");
154
- vm.label(payer, "payer");
155
- vm.label(beneficiary, "beneficiary");
156
- vm.label(reserveBeneficiary, "reserveBeneficiary");
157
- vm.label(splitBeneficiary, "splitBeneficiary");
158
- }
159
-
160
- // forge-lint: disable-next-line(mixed-case-function)
161
- function _deployJBCore() internal {
162
- jbPermissions = new JBPermissions(address(0));
163
- jbProjects = new JBProjects(multisig, address(0), address(0));
164
- jbDirectory = new JBDirectory(jbPermissions, jbProjects, multisig);
165
- JBERC20 jbErc20 = new JBERC20(jbPermissions, jbProjects);
166
- jbTokens = new JBTokens(jbDirectory, jbErc20);
167
- jbRulesets = new JBRulesets(jbDirectory);
168
- jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, multisig, address(0));
169
- jbSplits = new JBSplits(jbDirectory);
170
- jbFundAccessLimits = new JBFundAccessLimits(jbDirectory);
171
- jbFeelessAddresses = new JBFeelessAddresses(multisig);
172
-
173
- jbController = new JBController(
174
- jbDirectory,
175
- jbFundAccessLimits,
176
- jbPermissions,
177
- jbPrices,
178
- jbProjects,
179
- jbRulesets,
180
- jbSplits,
181
- jbTokens,
182
- address(0),
183
- address(0)
184
- );
185
-
186
- vm.prank(multisig);
187
- jbDirectory.setIsAllowedToSetFirstController(address(jbController), true);
188
-
189
- jbTerminalStore = new JBTerminalStore(jbDirectory, jbPrices, jbRulesets);
190
-
191
- jbMultiTerminal = new JBMultiTerminal(
192
- jbFeelessAddresses,
193
- jbPermissions,
194
- jbProjects,
195
- jbSplits,
196
- jbTerminalStore,
197
- jbTokens,
198
- IPermit2(address(0)),
199
- address(0)
200
- );
201
-
202
- vm.label(address(jbPermissions), "JBPermissions");
203
- vm.label(address(jbProjects), "JBProjects");
204
- vm.label(address(jbDirectory), "JBDirectory");
205
- vm.label(address(jbController), "JBController");
206
- vm.label(address(jbMultiTerminal), "JBMultiTerminal");
207
- vm.label(address(jbTerminalStore), "JBTerminalStore");
208
- }
209
-
210
- function _deploy721Hook() internal {
211
- store = new JB721TiersHookStore();
212
- hookImpl = new JB721TiersHook(
213
- jbDirectory,
214
- jbPermissions,
215
- jbPrices,
216
- jbRulesets,
217
- store,
218
- IJBSplits(address(jbSplits)),
219
- IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
220
- address(0)
221
- );
222
- addressRegistry = new JBAddressRegistry();
223
- hookDeployer = new JB721TiersHookDeployer(hookImpl, store, addressRegistry, address(0));
224
- projectDeployer = new JB721TiersHookProjectDeployer(
225
- IJBDirectory(jbDirectory), IJBPermissions(jbPermissions), hookDeployer, address(0)
226
- );
227
- metadataHelper = new MetadataResolverHelper();
228
-
229
- vm.label(address(store), "JB721TiersHookStore");
230
- vm.label(address(hookImpl), "JB721TiersHook_impl");
231
- vm.label(address(projectDeployer), "JB721TiersHookProjectDeployer");
232
- }
233
-
234
- // =========================================================================
235
- // Launch Helper
236
- // =========================================================================
237
-
238
- /// @dev Launch an ETH-denominated project with configurable issueTokensForSplits flag.
239
- // forge-lint: disable-next-line(mixed-case-function)
240
- function _launchProjectWithFlag(
241
- JB721TierConfig[] memory tierConfigs,
242
- bool issueTokensForSplits
243
- )
244
- internal
245
- returns (uint256 projectId, address dataHook)
246
- {
247
- JBDeploy721TiersHookConfig memory hookConfig = JBDeploy721TiersHookConfig({
248
- name: "TestNFT",
249
- symbol: "TNFT",
250
- baseUri: "ipfs://base/",
251
- tokenUriResolver: IJB721TokenUriResolver(address(0)),
252
- contractUri: "ipfs://contract",
253
- tiersConfig: JB721InitTiersConfig({
254
- tiers: tierConfigs,
255
- // forge-lint: disable-next-line(unsafe-typecast)
256
- currency: uint32(uint160(NATIVE_TOKEN)),
257
- decimals: 18
258
- }),
259
- flags: JB721TiersHookFlags({
260
- preventOverspending: false,
261
- issueTokensForSplits: issueTokensForSplits,
262
- noNewTiersWithReserves: false,
263
- noNewTiersWithVotes: false,
264
- noNewTiersWithOwnerMinting: false
265
- })
266
- });
267
-
268
- JBPayDataHookRulesetMetadata memory rulesetMetadata = JBPayDataHookRulesetMetadata({
269
- reservedPercent: 0,
270
- cashOutTaxRate: 0,
271
- // forge-lint: disable-next-line(unsafe-typecast)
272
- baseCurrency: uint32(uint160(NATIVE_TOKEN)),
273
- pausePay: false,
274
- pauseCreditTransfers: false,
275
- allowOwnerMinting: true,
276
- allowSetCustomToken: false,
277
- allowTerminalMigration: false,
278
- allowSetTerminals: false,
279
- allowSetController: false,
280
- allowAddAccountingContext: false,
281
- allowAddPriceFeed: false,
282
- ownerMustSendPayouts: false,
283
- holdFees: false,
284
- useTotalSurplusForCashOuts: false,
285
- useDataHookForCashOut: true,
286
- metadata: 0x00
287
- });
288
-
289
- JBPayDataHookRulesetConfig[] memory rulesetConfigs = new JBPayDataHookRulesetConfig[](1);
290
- rulesetConfigs[0].mustStartAtOrAfter = 0;
291
- rulesetConfigs[0].duration = 0;
292
- rulesetConfigs[0].weight = 1_000_000e18; // 1M tokens per ETH
293
- rulesetConfigs[0].weightCutPercent = 0;
294
- rulesetConfigs[0].approvalHook = IJBRulesetApprovalHook(address(0));
295
- rulesetConfigs[0].metadata = rulesetMetadata;
296
-
297
- JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
298
- accountingContexts[0] =
299
- // forge-lint: disable-next-line(unsafe-typecast)
300
- JBAccountingContext({token: NATIVE_TOKEN, currency: uint32(uint160(NATIVE_TOKEN)), decimals: 18});
301
-
302
- JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
303
- terminalConfigs[0] =
304
- JBTerminalConfig({terminal: jbMultiTerminal, accountingContextsToAccept: accountingContexts});
305
-
306
- JBLaunchProjectConfig memory launchConfig = JBLaunchProjectConfig({
307
- projectUri: "test-issue-tokens-splits",
308
- rulesetConfigurations: rulesetConfigs,
309
- terminalConfigurations: terminalConfigs,
310
- memo: ""
311
- });
312
-
313
- IJB721TiersHook hookInstance;
314
- (projectId, hookInstance) =
315
- projectDeployer.launchProjectFor(multisig, hookConfig, launchConfig, jbController, bytes32(0));
316
-
317
- dataHook = address(hookInstance);
318
- vm.label(dataHook, "hook_clone");
319
- }
320
-
321
- // =========================================================================
322
- // Metadata & Token ID Helpers
323
- // =========================================================================
324
-
325
- function _buildPayMetadata(uint16[] memory tierIds, bool allowOverspending) internal view returns (bytes memory) {
326
- bytes[] memory data = new bytes[](1);
327
- data[0] = abi.encode(allowOverspending, tierIds);
328
- bytes4[] memory ids = new bytes4[](1);
329
- ids[0] = JBMetadataResolver.getId("pay", address(hookImpl));
330
- return metadataHelper.createMetadata(ids, data);
331
- }
332
-
333
- function _tokenId(uint256 tierId, uint256 mintNumber) internal pure returns (uint256) {
334
- return tierId * 1_000_000_000 + mintNumber;
335
- }
336
-
337
- // =========================================================================
338
- // Helper: create a tier with 50% split to splitBeneficiary
339
- // =========================================================================
340
-
341
- function _makeTierWithSplit(uint104 price, uint32 splitPercent) internal view returns (JB721TierConfig memory) {
342
- JBSplit[] memory splits = new JBSplit[](1);
343
- splits[0] = JBSplit({
344
- percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT),
345
- projectId: 0,
346
- beneficiary: payable(splitBeneficiary),
347
- preferAddToBalance: false,
348
- lockedUntil: 0,
349
- hook: IJBSplitHook(address(0))
350
- });
351
-
352
- return JB721TierConfig({
353
- price: price,
354
- initialSupply: 100,
355
- votingUnits: 0,
356
- reserveFrequency: 0,
357
- reserveBeneficiary: address(0),
358
- // forge-lint: disable-next-line(unsafe-typecast)
359
- encodedIPFSUri: bytes32("tier1"),
360
- category: 1,
361
- discountPercent: 0,
362
- flags: JB721TierConfigFlags({
363
- allowOwnerMint: false,
364
- useReserveBeneficiaryAsDefault: false,
365
- transfersPausable: false,
366
- useVotingUnits: false,
367
- cantBeRemoved: false,
368
- cantIncreaseDiscountPercent: false,
369
- cantBuyWithCredits: false
370
- }),
371
- splitPercent: splitPercent,
372
- splits: splits
373
- });
374
- }
375
-
376
- // =========================================================================
377
- // Test 1: issueTokensForSplits = true -> payer gets tokens at full weight
378
- // =========================================================================
379
-
380
- /// @notice When issueTokensForSplits = true and tier has splits, payer gets tokens on full amount.
381
- function testFork_IssueTokensForSplitsTrueFullWeight() public {
382
- // Tier: 1 ETH, 50% split to splitBeneficiary.
383
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
384
- tierConfigs[0] = _makeTierWithSplit(1 ether, 500_000_000); // 50% split
385
-
386
- (uint256 projectId,) = _launchProjectWithFlag(tierConfigs, true);
387
-
388
- // Pay 1 ETH to mint tier 1 NFT.
389
- uint16[] memory tierIds = new uint16[](1);
390
- tierIds[0] = 1;
391
- bytes memory meta = _buildPayMetadata(tierIds, true);
392
-
393
- vm.prank(payer);
394
- uint256 tokenCount = jbMultiTerminal.pay{value: 1 ether}({
395
- projectId: projectId,
396
- amount: 1 ether,
397
- token: NATIVE_TOKEN,
398
- beneficiary: beneficiary,
399
- minReturnedTokens: 0,
400
- memo: "",
401
- metadata: meta
402
- });
403
-
404
- // With issueTokensForSplits = true, weight = contextWeight (full).
405
- // weight = 1_000_000e18, payment = 1 ETH => tokens = 1_000_000e18 * 1e18 / 1e18 = 1_000_000e18
406
- // tokenCount is the number of tokens minted to the beneficiary.
407
- uint256 balance = jbTokens.totalBalanceOf(beneficiary, projectId);
408
-
409
- // Full weight: beneficiary should receive tokens based on the full 1 ETH amount.
410
- // With weight = 1_000_000e18 and payment = 1 ETH: expected = 1_000_000e18.
411
- assertEq(balance, 1_000_000e18, "issueTokensForSplits=true: full weight tokens minted");
412
- assertEq(tokenCount, 1_000_000e18, "pay() return value should match full token count");
413
-
414
- // Split beneficiary should have received 0.5 ETH.
415
- assertGt(splitBeneficiary.balance, 0, "split beneficiary should have received ETH");
416
- }
417
-
418
- // =========================================================================
419
- // Test 2: issueTokensForSplits = false -> payer gets scaled tokens
420
- // =========================================================================
421
-
422
- /// @notice When issueTokensForSplits = false and tier has splits, payer gets scaled tokens.
423
- function testFork_IssueTokensForSplitsFalseScaledWeight() public {
424
- // Tier: 1 ETH, 50% split to splitBeneficiary.
425
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
426
- tierConfigs[0] = _makeTierWithSplit(1 ether, 500_000_000); // 50% split
427
-
428
- (uint256 projectId,) = _launchProjectWithFlag(tierConfigs, false);
429
-
430
- // Pay 1 ETH to mint tier 1 NFT.
431
- uint16[] memory tierIds = new uint16[](1);
432
- tierIds[0] = 1;
433
- bytes memory meta = _buildPayMetadata(tierIds, true);
434
-
435
- vm.prank(payer);
436
- uint256 tokenCount = jbMultiTerminal.pay{value: 1 ether}({
437
- projectId: projectId,
438
- amount: 1 ether,
439
- token: NATIVE_TOKEN,
440
- beneficiary: beneficiary,
441
- minReturnedTokens: 0,
442
- memo: "",
443
- metadata: meta
444
- });
445
-
446
- // With issueTokensForSplits = false, weight = contextWeight * (amount - splitAmount) / amount.
447
- // splitAmount = 50% of 1 ETH = 0.5 ETH.
448
- // weight = 1_000_000e18 * (1e18 - 0.5e18) / 1e18 = 500_000e18.
449
- // tokens = 500_000e18 * 1e18 / 1e18 = 500_000e18.
450
- uint256 balance = jbTokens.totalBalanceOf(beneficiary, projectId);
451
-
452
- // Scaled weight: beneficiary should receive tokens based on 50% of the payment.
453
- assertEq(balance, 500_000e18, "issueTokensForSplits=false: scaled weight tokens minted");
454
- assertEq(tokenCount, 500_000e18, "pay() return value should match scaled token count");
455
-
456
- // Split beneficiary should have received 0.5 ETH.
457
- assertGt(splitBeneficiary.balance, 0, "split beneficiary should have received ETH");
458
- }
459
-
460
- // =========================================================================
461
- // Test 3: 100% to splits edge case
462
- // =========================================================================
463
-
464
- /// @notice 100% to splits: flag true -> tokens still minted at full weight; flag false -> weight = 0, no tokens.
465
- function testFork_IssueTokensForSplitsEdgeCaseAllSplits() public {
466
- // Tier: 1 ETH, 100% split to splitBeneficiary (SPLITS_TOTAL_PERCENT = 1e9).
467
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
468
- tierConfigs[0] = _makeTierWithSplit(1 ether, 1_000_000_000); // 100% split
469
-
470
- // --- Part A: issueTokensForSplits = true ---
471
- (uint256 projectIdTrue,) = _launchProjectWithFlag(tierConfigs, true);
472
-
473
- uint16[] memory tierIds = new uint16[](1);
474
- tierIds[0] = 1;
475
- bytes memory meta = _buildPayMetadata(tierIds, true);
476
-
477
- vm.prank(payer);
478
- uint256 tokenCountTrue = jbMultiTerminal.pay{value: 1 ether}({
479
- projectId: projectIdTrue,
480
- amount: 1 ether,
481
- token: NATIVE_TOKEN,
482
- beneficiary: beneficiary,
483
- minReturnedTokens: 0,
484
- memo: "",
485
- metadata: meta
486
- });
487
-
488
- uint256 balanceTrue = jbTokens.totalBalanceOf(beneficiary, projectIdTrue);
489
-
490
- // issueTokensForSplits=true: full weight even though 100% goes to splits.
491
- assertEq(balanceTrue, 1_000_000e18, "100% splits + flag=true: full weight tokens minted");
492
- assertEq(tokenCountTrue, 1_000_000e18, "pay() return value: full tokens with flag=true");
493
-
494
- // --- Part B: issueTokensForSplits = false ---
495
- (uint256 projectIdFalse,) = _launchProjectWithFlag(tierConfigs, false);
496
-
497
- meta = _buildPayMetadata(tierIds, true);
498
-
499
- vm.prank(payer);
500
- uint256 tokenCountFalse = jbMultiTerminal.pay{value: 1 ether}({
501
- projectId: projectIdFalse,
502
- amount: 1 ether,
503
- token: NATIVE_TOKEN,
504
- beneficiary: beneficiary,
505
- minReturnedTokens: 0,
506
- memo: "",
507
- metadata: meta
508
- });
509
-
510
- uint256 balanceFalse = jbTokens.totalBalanceOf(beneficiary, projectIdFalse);
511
-
512
- // issueTokensForSplits=false + 100% splits: weight = 0, no tokens minted.
513
- assertEq(balanceFalse, 0, "100% splits + flag=false: zero tokens minted");
514
- assertEq(tokenCountFalse, 0, "pay() return value: zero tokens with flag=false");
515
- }
516
- }