@bananapus/721-hook-v6 0.0.41 → 0.0.43

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 (77) 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 +60 -18
  6. package/src/JB721CheckpointsDeployer.sol +10 -5
  7. package/src/JB721TiersHook.sol +4 -1
  8. package/src/JB721TiersHookProjectDeployer.sol +68 -30
  9. package/src/JB721TiersHookStore.sol +1 -4
  10. package/src/interfaces/IJB721Checkpoints.sol +21 -14
  11. package/src/interfaces/IJB721CheckpointsDeployer.sol +6 -2
  12. package/src/interfaces/IJB721TiersHookProjectDeployer.sol +2 -0
  13. package/test/utils/AccessJBLib.sol +49 -0
  14. package/test/utils/ForTest_JB721TiersHook.sol +246 -0
  15. package/test/utils/TestBaseWorkflow.sol +213 -0
  16. package/test/utils/UnitTestSetup.sol +805 -0
  17. package/.gas-snapshot +0 -152
  18. package/ADMINISTRATION.md +0 -87
  19. package/ARCHITECTURE.md +0 -98
  20. package/AUDIT_INSTRUCTIONS.md +0 -77
  21. package/RISKS.md +0 -118
  22. package/SKILLS.md +0 -43
  23. package/STYLE_GUIDE.md +0 -610
  24. package/USER_JOURNEYS.md +0 -121
  25. package/assets/findings/nana-721-hook-v6-pashov-ai-audit-report-20260330-091257.md +0 -83
  26. package/slither-ci.config.json +0 -10
  27. package/test/721HookAttacks.t.sol +0 -408
  28. package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +0 -985
  29. package/test/Fork.t.sol +0 -2346
  30. package/test/TestAuditGaps.sol +0 -1075
  31. package/test/TestCheckpoints.t.sol +0 -341
  32. package/test/TestSafeTransferReentrancy.t.sol +0 -305
  33. package/test/TestVotingUnitsLifecycle.t.sol +0 -313
  34. package/test/audit/AuditRegressions.t.sol +0 -83
  35. package/test/audit/CrossCurrencySplitNoPrices.t.sol +0 -123
  36. package/test/audit/FreshAudit.t.sol +0 -197
  37. package/test/audit/FutureTierPoC.t.sol +0 -39
  38. package/test/audit/FutureTierRemoval.t.sol +0 -47
  39. package/test/audit/Pass12L18.t.sol +0 -80
  40. package/test/audit/PayCreditsBypassTierSplits.t.sol +0 -200
  41. package/test/audit/ProjectDeployerAuth.t.sol +0 -266
  42. package/test/audit/RepoFindings.t.sol +0 -195
  43. package/test/audit/ReserveActivation.t.sol +0 -87
  44. package/test/audit/RetroactiveReserveBeneficiaryDilution.t.sol +0 -149
  45. package/test/audit/SameCurrencyDecimalMismatch.t.sol +0 -249
  46. package/test/audit/SplitCreditsMismatch.t.sol +0 -219
  47. package/test/audit/SplitFailureRedistribution.t.sol +0 -143
  48. package/test/audit/USDTVoidReturnCompat.t.sol +0 -301
  49. package/test/fork/ERC20CashOutFork.t.sol +0 -633
  50. package/test/fork/ERC20TierSplitFork.t.sol +0 -596
  51. package/test/fork/IssueTokensForSplitsFork.t.sol +0 -516
  52. package/test/invariants/TierLifecycleInvariant.t.sol +0 -188
  53. package/test/invariants/TieredHookStoreInvariant.t.sol +0 -86
  54. package/test/invariants/handlers/TierLifecycleHandler.sol +0 -300
  55. package/test/invariants/handlers/TierStoreHandler.sol +0 -165
  56. package/test/regression/BrokenTerminalDoesNotDos.t.sol +0 -277
  57. package/test/regression/CacheTierLookup.t.sol +0 -190
  58. package/test/regression/ProjectDeployerRulesets.t.sol +0 -358
  59. package/test/regression/ReserveBeneficiaryOverwrite.t.sol +0 -155
  60. package/test/regression/SplitDistributionBugs.t.sol +0 -751
  61. package/test/regression/SplitNoBeneficiary.t.sol +0 -140
  62. package/test/unit/AuditFixes_Unit.t.sol +0 -624
  63. package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +0 -116
  64. package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +0 -144
  65. package/test/unit/JBBitmap.t.sol +0 -170
  66. package/test/unit/JBIpfsDecoder.t.sol +0 -136
  67. package/test/unit/TierSupplyReserveCheck.t.sol +0 -221
  68. package/test/unit/adjustTier_Unit.t.sol +0 -1942
  69. package/test/unit/deployer_Unit.t.sol +0 -114
  70. package/test/unit/getters_constructor_Unit.t.sol +0 -593
  71. package/test/unit/mintFor_mintReservesFor_Unit.t.sol +0 -452
  72. package/test/unit/pay_CrossCurrency_Unit.t.sol +0 -530
  73. package/test/unit/pay_Unit.t.sol +0 -1661
  74. package/test/unit/redeem_Unit.t.sol +0 -473
  75. package/test/unit/relayBeneficiary_Unit.t.sol +0 -182
  76. package/test/unit/splitHookDistribution_Unit.t.sol +0 -604
  77. 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
- }