@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,985 +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 "@openzeppelin/contracts/token/ERC721/IERC721.sol";
6
- // forge-lint: disable-next-line(unaliased-plain-import)
7
- import "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
8
-
9
- // forge-lint: disable-next-line(unaliased-plain-import)
10
- import "../../src/JB721TiersHook.sol";
11
- // forge-lint: disable-next-line(unaliased-plain-import)
12
- import "../../src/JB721TiersHookProjectDeployer.sol";
13
- // forge-lint: disable-next-line(unaliased-plain-import)
14
- import "../../src/JB721TiersHookDeployer.sol";
15
- // forge-lint: disable-next-line(unaliased-plain-import)
16
- import "../../src/JB721TiersHookStore.sol";
17
-
18
- import {JB721CheckpointsDeployer} from "../../src/JB721CheckpointsDeployer.sol";
19
- import {IJB721CheckpointsDeployer} from "../../src/interfaces/IJB721CheckpointsDeployer.sol";
20
-
21
- // forge-lint: disable-next-line(unaliased-plain-import)
22
- import "../utils/TestBaseWorkflow.sol";
23
- // forge-lint: disable-next-line(unaliased-plain-import)
24
- import "../../src/interfaces/IJB721TiersHook.sol";
25
- import {MetadataResolverHelper} from "@bananapus/core-v6/test/helpers/MetadataResolverHelper.sol";
26
- import {JB721TierConfigFlags} from "../../src/structs/JB721TierConfigFlags.sol";
27
-
28
- contract Test_TiersHook_E2E is TestBaseWorkflow {
29
- using JBRulesetMetadataResolver for JBRuleset;
30
-
31
- uint256 totalSupplyAfterPay;
32
-
33
- address reserveBeneficiary = address(bytes20(keccak256("reserveBeneficiary")));
34
- address trustedForwarder = address(123_456);
35
-
36
- JB721TiersHook hook;
37
-
38
- MetadataResolverHelper metadataHelper;
39
-
40
- event Mint(
41
- uint256 indexed tokenId,
42
- uint256 indexed tierId,
43
- address indexed beneficiary,
44
- uint256 totalAmountPaid,
45
- address caller
46
- );
47
- event Burn(uint256 indexed tokenId, address owner, address caller);
48
-
49
- string name = "NAME";
50
- string symbol = "SYM";
51
- string baseUri = "http://www.null.com/";
52
- string contractUri = "ipfs://null";
53
- //QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz
54
- bytes32[] tokenUris = [
55
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
56
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
57
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
58
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
59
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
60
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
61
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
62
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
63
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89),
64
- bytes32(0x7D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89)
65
- ];
66
-
67
- JB721TiersHookProjectDeployer deployer;
68
- JB721TiersHookStore store;
69
- JBAddressRegistry addressRegistry;
70
-
71
- function setUp() public override {
72
- super.setUp();
73
- store = new JB721TiersHookStore();
74
- hook = new JB721TiersHook(
75
- jbDirectory,
76
- jbPermissions,
77
- jbPrices,
78
- jbRulesets,
79
- store,
80
- IJBSplits(address(jbSplits)),
81
- IJB721CheckpointsDeployer(address(new JB721CheckpointsDeployer())),
82
- trustedForwarder
83
- );
84
- addressRegistry = new JBAddressRegistry();
85
- JB721TiersHookDeployer hookDeployer = new JB721TiersHookDeployer(hook, store, addressRegistry, trustedForwarder);
86
- deployer = new JB721TiersHookProjectDeployer(
87
- IJBDirectory(jbDirectory), IJBPermissions(jbPermissions), hookDeployer, address(0)
88
- );
89
-
90
- metadataHelper = new MetadataResolverHelper();
91
- }
92
-
93
- function testLaunchProjectAndAddHookToRegistry(bytes32 salt) external {
94
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
95
- createData();
96
- (uint256 projectId, IJB721TiersHook _hook) =
97
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, bytes32(0));
98
- // Check: is the first project's ID 1?
99
- assertEq(projectId, 1);
100
- // Check: was the hook added to the address registry?
101
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
102
- assertEq(address(_hook), dataHook);
103
- assertEq(addressRegistry.deployerOf(dataHook), address(deployer.HOOK_DEPLOYER()));
104
-
105
- // Laucnh another project with a salt
106
- (projectId, _hook) =
107
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
108
- // Check: is the second project's ID 2?
109
- assertEq(projectId, 2);
110
- // Check: was the hook added to the address registry?
111
- dataHook = jbRulesets.currentOf(projectId).dataHook();
112
- assertEq(address(_hook), dataHook);
113
- assertEq(addressRegistry.deployerOf(dataHook), address(deployer.HOOK_DEPLOYER()));
114
-
115
- // Laucnh another project with no salt
116
- (projectId, _hook) =
117
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, bytes32(0));
118
-
119
- // Check: is the third project's ID 3?
120
- assertEq(projectId, 3);
121
-
122
- // Check: was the hook added to the address registry?
123
- dataHook = jbRulesets.currentOf(projectId).dataHook();
124
- assertEq(address(_hook), dataHook);
125
- assertEq(addressRegistry.deployerOf(dataHook), address(deployer.HOOK_DEPLOYER()));
126
- }
127
-
128
- function testMintOnPayIfOneTierIsPassed(uint256 valueSent, bytes32 salt) external {
129
- valueSent = bound(valueSent, 10, 2000);
130
- // Cap the highest tier ID possible to 10.
131
- uint256 highestTier = valueSent <= 100 ? (valueSent / 10) : 10;
132
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
133
- createData();
134
- (uint256 projectId, IJB721TiersHook _hook) =
135
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
136
-
137
- // Crafting the payment metadata: add the highest tier ID.
138
- uint16[] memory rawMetadata = new uint16[](1);
139
- // forge-lint: disable-next-line(unsafe-typecast)
140
- rawMetadata[0] = uint16(highestTier);
141
-
142
- // Build the metadata using the tiers to mint and the overspending flag.
143
- bytes[] memory data = new bytes[](1);
144
- data[0] = abi.encode(true, rawMetadata);
145
-
146
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
147
- assertEq(address(_hook), dataHook);
148
- // Pass the hook ID.
149
- bytes4[] memory ids = new bytes4[](1);
150
- ids[0] = JBMetadataResolver.getId("pay", address(hook));
151
-
152
- // Generate the metadata.
153
- bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
154
-
155
- // Check: was an NFT with the correct tier ID and token ID minted?
156
- vm.expectEmit(true, true, true, true);
157
- emit Mint(
158
- _generateTokenId(highestTier, 1),
159
- highestTier,
160
- beneficiary,
161
- valueSent,
162
- address(jbMultiTerminal) // msg.sender
163
- );
164
-
165
- // Pay the terminal to mint the NFTs.
166
- vm.prank(caller);
167
- jbMultiTerminal.pay{value: valueSent}({
168
- projectId: projectId,
169
- amount: 100,
170
- token: JBConstants.NATIVE_TOKEN,
171
- beneficiary: beneficiary,
172
- minReturnedTokens: 0,
173
- memo: "Take my money!",
174
- metadata: hookMetadata
175
- });
176
- uint256 tokenId = _generateTokenId(highestTier, 1);
177
- // Check: did the beneficiary receive the NFT?
178
- if (valueSent < 10) {
179
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 0);
180
- } else {
181
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 1);
182
- }
183
-
184
- // Check: is the beneficiary the first owner of the NFT?
185
- assertEq(IERC721(dataHook).ownerOf(tokenId), beneficiary);
186
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
187
-
188
- // Check: after a transfer, are the `firstOwnerOf` and `ownerOf` still correct?
189
- vm.prank(beneficiary);
190
- IERC721(dataHook).transferFrom(beneficiary, address(696_969_420), tokenId);
191
- assertEq(IERC721(dataHook).ownerOf(tokenId), address(696_969_420));
192
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
193
-
194
- // Check: is the same true after a second transfer?
195
- vm.prank(address(696_969_420));
196
- IERC721(dataHook).transferFrom(address(696_969_420), address(123_456_789), tokenId);
197
- assertEq(IERC721(dataHook).ownerOf(tokenId), address(123_456_789));
198
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
199
- }
200
-
201
- function testFuzzMintWithDiscountOnPayIfOneTierIsPassed(uint256 tierStartPrice, uint256 discountPercent) external {
202
- // Cap our fuzzed params
203
- tierStartPrice = bound(tierStartPrice, 1, type(uint208).max - 1);
204
- discountPercent = bound(discountPercent, 1, 200);
205
-
206
- {
207
- uint256 amountMinted = (tierStartPrice * 1000) / 2;
208
- totalSupplyAfterPay += amountMinted;
209
- }
210
-
211
- // Cap the highest tier ID.
212
- uint256 highestTier = 1;
213
-
214
- (
215
- JBDeploy721TiersHookConfig memory tiersHookConfig,
216
- JBLaunchProjectConfig memory launchProjectConfig
217
- // forge-lint: disable-next-line(unsafe-typecast)
218
- ) = createDiscountedData(tierStartPrice, uint8(discountPercent));
219
- (uint256 projectId, IJB721TiersHook _hook) =
220
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, bytes32(0));
221
-
222
- // Crafting the payment metadata: add the highest tier ID.
223
- uint16[] memory rawMetadata = new uint16[](1);
224
- // forge-lint: disable-next-line(unsafe-typecast)
225
- rawMetadata[0] = uint16(highestTier);
226
-
227
- // Build the metadata using the tiers to mint and the overspending flag.
228
- bytes[] memory data = new bytes[](1);
229
- data[0] = abi.encode(true, rawMetadata);
230
-
231
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
232
- assertEq(address(_hook), dataHook);
233
- bytes memory hookMetadata;
234
- {
235
- // Pass the hook ID.
236
- bytes4[] memory ids = new bytes4[](1);
237
- ids[0] = JBMetadataResolver.getId("pay", address(hook));
238
-
239
- // Generate the metadata.
240
- hookMetadata = metadataHelper.createMetadata(ids, data);
241
- }
242
-
243
- /* // Check: was an NFT with the correct tier ID and token ID minted?
244
- vm.expectEmit(true, true, true, true);
245
- emit Mint(
246
- _generateTokenId(highestTier, 1),
247
- highestTier,
248
- beneficiary,
249
- tierStartPrice,
250
- address(jbMultiTerminal) // msg.sender
251
- ); */
252
-
253
- if (totalSupplyAfterPay > type(uint208).max) {
254
- vm.expectRevert(
255
- abi.encodeWithSelector(JBTokens.JBTokens_OverflowAlert.selector, totalSupplyAfterPay, type(uint208).max)
256
- );
257
- }
258
-
259
- // Pay the terminal to mint the NFTs.
260
- vm.deal(caller, type(uint256).max);
261
- vm.prank(caller);
262
- jbMultiTerminal.pay{value: tierStartPrice}({
263
- projectId: projectId,
264
- amount: tierStartPrice,
265
- token: JBConstants.NATIVE_TOKEN,
266
- beneficiary: beneficiary,
267
- minReturnedTokens: 0,
268
- memo: "Take my money!",
269
- metadata: hookMetadata
270
- });
271
-
272
- if (totalSupplyAfterPay < type(uint208).max) {
273
- if (tierStartPrice > type(uint104).max) {
274
- uint256 expectedDiscount =
275
- // forge-lint: disable-next-line(unsafe-typecast)
276
- mulDiv(uint104(tierStartPrice), discountPercent, JB721Constants.DISCOUNT_DENOMINATOR);
277
- // forge-lint: disable-next-line(unsafe-typecast)
278
- uint256 paidForNft = uint104(tierStartPrice) - expectedDiscount;
279
-
280
- // Check: should be credited tierStartPrice minus what you paid for the NFT plus the discount
281
- assertEq(IJB721TiersHook(dataHook).payCreditsOf(beneficiary), tierStartPrice - paidForNft);
282
- } else {
283
- uint256 expectedCredits = mulDiv(tierStartPrice, discountPercent, JB721Constants.DISCOUNT_DENOMINATOR);
284
- assertEq(IJB721TiersHook(dataHook).payCreditsOf(beneficiary), expectedCredits);
285
- }
286
-
287
- {
288
- // Check: did the beneficiary receive the NFT?
289
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 1);
290
-
291
- uint256 tokenId = _generateTokenId(highestTier, 1);
292
-
293
- // Check: is the beneficiary the first owner of the NFT?
294
- assertEq(IERC721(dataHook).ownerOf(tokenId), beneficiary);
295
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
296
-
297
- // Check: after a transfer, are the `firstOwnerOf` and `ownerOf` still correct?
298
- vm.prank(beneficiary);
299
- IERC721(dataHook).transferFrom(beneficiary, address(696_969_420), tokenId);
300
- assertEq(IERC721(dataHook).ownerOf(tokenId), address(696_969_420));
301
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
302
-
303
- // Check: is the same true after a second transfer?
304
- vm.prank(address(696_969_420));
305
- IERC721(dataHook).transferFrom(address(696_969_420), address(123_456_789), tokenId);
306
- assertEq(IERC721(dataHook).ownerOf(tokenId), address(123_456_789));
307
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
308
- }
309
- }
310
- }
311
-
312
- function testMintOnPayIfMultipleTiersArePassed(bytes32 salt) external {
313
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
314
- createData();
315
- (uint256 projectId, IJB721TiersHook _hook) =
316
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
317
-
318
- // Prices of the first 5 tiers (10 * `tierId`)
319
- uint256 amountNeeded = 50 + 40 + 30 + 20 + 10;
320
- uint16[] memory rawMetadata = new uint16[](5);
321
-
322
- // Mint one NFT per tier from the first 5 tiers.
323
- for (uint256 i = 0; i < 5; i++) {
324
- // forge-lint: disable-next-line(unsafe-typecast)
325
- rawMetadata[i] = uint16(i + 1); // Start at `tierId` 1.
326
- // Check: correct tier IDs and token IDs?
327
- vm.expectEmit(true, true, true, true);
328
- emit Mint(
329
- _generateTokenId(i + 1, 1),
330
- i + 1,
331
- beneficiary,
332
- amountNeeded,
333
- address(jbMultiTerminal) // `msg.sender`
334
- );
335
- }
336
-
337
- // Build the metadata using the tiers to mint and the overspending flag.
338
- bytes[] memory data = new bytes[](1);
339
- data[0] = abi.encode(true, rawMetadata);
340
-
341
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
342
- assertEq(address(_hook), dataHook);
343
-
344
- // Pass the hook ID.
345
- bytes4[] memory ids = new bytes4[](1);
346
- ids[0] = JBMetadataResolver.getId("pay", address(hook));
347
-
348
- // Generate the metadata.
349
- bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
350
-
351
- // Pay the terminal to mint the NFTs.
352
- vm.prank(caller);
353
- jbMultiTerminal.pay{value: amountNeeded}({
354
- projectId: projectId,
355
- amount: amountNeeded,
356
- token: JBConstants.NATIVE_TOKEN,
357
- beneficiary: beneficiary,
358
- minReturnedTokens: 0,
359
- memo: "Take my money!",
360
- metadata: hookMetadata
361
- });
362
-
363
- // Check: were the NFTs actually received?
364
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 5);
365
- for (uint256 i = 1; i <= 5; i++) {
366
- uint256 tokenId = _generateTokenId(i, 1);
367
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
368
- // Check: are `firstOwnerOf` and `ownerOf` correct after a transfer?
369
- vm.prank(beneficiary);
370
- IERC721(dataHook).transferFrom(beneficiary, address(696_969_420), tokenId);
371
- assertEq(IERC721(dataHook).ownerOf(tokenId), address(696_969_420));
372
- assertEq(IJB721TiersHook(dataHook).firstOwnerOf(tokenId), beneficiary);
373
- }
374
- }
375
-
376
- function testNoMintOnPayWhenNotIncludingTierIds(uint256 valueSent, bytes32 salt) external {
377
- valueSent = bound(valueSent, 10, 2000);
378
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
379
- createData();
380
- (uint256 projectId, IJB721TiersHook _hook) =
381
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
382
-
383
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
384
- assertEq(address(_hook), dataHook);
385
-
386
- // Build the metadata with no tiers specified and the overspending flag.
387
- bool allowOverspending = true;
388
- uint16[] memory rawMetadata = new uint16[](0);
389
- bytes memory metadata =
390
- abi.encode(bytes32(0), bytes32(0), type(IJB721TiersHook).interfaceId, allowOverspending, rawMetadata);
391
-
392
- // Pay the terminal and pass the metadata.
393
- vm.prank(caller);
394
- jbMultiTerminal.pay{value: valueSent}({
395
- projectId: projectId,
396
- amount: 100,
397
- token: JBConstants.NATIVE_TOKEN,
398
- beneficiary: beneficiary,
399
- minReturnedTokens: 0,
400
- memo: "Take my money!",
401
- metadata: metadata
402
- });
403
-
404
- // Ensure that no NFT was minted.
405
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 0);
406
-
407
- // Ensure the beneficiary received pay credits (since no NFTs were minted).
408
- assertEq(IJB721TiersHook(dataHook).payCreditsOf(beneficiary), valueSent);
409
- }
410
-
411
- function testNoMintOnPayWhenNotIncludingMetadata(uint256 valueSent, bytes32 salt) external {
412
- valueSent = bound(valueSent, 10, 2000);
413
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
414
- createData();
415
- (uint256 projectId, IJB721TiersHook _hook) =
416
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
417
-
418
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
419
- assertEq(address(_hook), dataHook);
420
-
421
- // Pay the terminal with empty metadata (`bytes(0)`).
422
- vm.prank(caller);
423
- jbMultiTerminal.pay{value: valueSent}({
424
- projectId: projectId,
425
- amount: 100,
426
- token: JBConstants.NATIVE_TOKEN,
427
- beneficiary: beneficiary,
428
- minReturnedTokens: 0,
429
- memo: "Take my money!",
430
- metadata: new bytes(0)
431
- });
432
-
433
- // Ensure that no NFTs were minted.
434
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 0);
435
-
436
- // Ensure that the beneficiary received pay credits (since no NFTs were minted).
437
- assertEq(IJB721TiersHook(dataHook).payCreditsOf(beneficiary), valueSent);
438
- }
439
-
440
- function testMintReservedNft(uint256 valueSent, bytes32 salt) external {
441
- // cheapest tier is worth 10
442
- valueSent = bound(valueSent, 10, 20 ether);
443
-
444
- // Cap the highest tier ID possible to 10.
445
- uint256 highestTier = valueSent <= 100 ? valueSent / 10 : 10;
446
-
447
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
448
- createData();
449
- (uint256 projectId, IJB721TiersHook _hook) =
450
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
451
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
452
- assertEq(address(_hook), dataHook);
453
-
454
- // Check: Ensure no pending reserves at start (since no minting has happened).
455
- assertEq(IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, highestTier), 0);
456
-
457
- // Check: cannot mint pending reserves (since none should be pending)?
458
- vm.expectRevert(
459
- abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientPendingReserves.selector, 1, 0)
460
- );
461
- vm.prank(projectOwner);
462
- IJB721TiersHook(dataHook).mintPendingReservesFor(highestTier, 1);
463
-
464
- // Crafting the payment metadata: add the highest tier ID.
465
- uint16[] memory rawMetadata = new uint16[](1);
466
- // forge-lint: disable-next-line(unsafe-typecast)
467
- rawMetadata[0] = uint16(highestTier);
468
-
469
- // Build the metadata using the tiers to mint and the overspending flag.
470
- bytes[] memory data = new bytes[](1);
471
- data[0] = abi.encode(true, rawMetadata);
472
-
473
- // Pass the hook ID.
474
- bytes4[] memory ids = new bytes4[](1);
475
- ids[0] = JBMetadataResolver.getId("pay", address(hook));
476
-
477
- // Generate the metadata.
478
- bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
479
-
480
- // Check: were an NFT with the correct tier ID and token ID minted?
481
- vm.expectEmit(true, true, true, true);
482
- emit Mint(
483
- _generateTokenId(highestTier, 1), // First one
484
- highestTier,
485
- beneficiary,
486
- valueSent,
487
- address(jbMultiTerminal) // msg.sender
488
- );
489
-
490
- // Pay the terminal to mint the NFTs.
491
- vm.prank(caller);
492
- jbMultiTerminal.pay{value: valueSent}({
493
- projectId: projectId,
494
- amount: 100,
495
- token: JBConstants.NATIVE_TOKEN,
496
- beneficiary: beneficiary,
497
- minReturnedTokens: 0,
498
- memo: "Take my money!",
499
- metadata: hookMetadata
500
- });
501
-
502
- // Check: is there now 1 pending reserve? 1 mint should yield 1 pending reserve, due to rounding up.
503
- assertEq(IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, highestTier), 1);
504
-
505
- JB721Tier memory tierBeforeMintingReserves =
506
- JB721TiersHook(dataHook).STORE().tierOf(dataHook, highestTier, false);
507
-
508
- // Mint the pending reserve NFT.
509
- vm.prank(projectOwner);
510
- IJB721TiersHook(dataHook).mintPendingReservesFor(highestTier, 1);
511
- // Check: did the reserve beneficiary receive the NFT?
512
- assertEq(IERC721(dataHook).balanceOf(reserveBeneficiary), 1);
513
-
514
- JB721Tier memory tierAfterMintingReserves =
515
- JB721TiersHook(dataHook).STORE().tierOf(dataHook, highestTier, false);
516
- // The tier's remaining supply should have decreased by 1.
517
- assertLt(tierAfterMintingReserves.remainingSupply, tierBeforeMintingReserves.remainingSupply);
518
-
519
- // Check: there should now be 0 pending reserves.
520
- assertEq(IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, highestTier), 0);
521
- // Check: it should not be possible to mint pending reserves now (since there are none left).
522
- vm.expectRevert(
523
- abi.encodeWithSelector(JB721TiersHookStore.JB721TiersHookStore_InsufficientPendingReserves.selector, 1, 0)
524
- );
525
- vm.prank(projectOwner);
526
- IJB721TiersHook(dataHook).mintPendingReservesFor(highestTier, 1);
527
- }
528
-
529
- // - Mint an NFT.
530
- // - Check the number of pending reserve mints available within that NFT's tier, which should be non-zero due to
531
- // rounding up.
532
- // - Burn an NFT from that tier.
533
- // - Check the number of pending reserve mints available within the NFT's tier again.
534
- // This number should be back to 0, since the NFT was burned.
535
- function testCashOutToken(uint256 valueSent, bytes32 salt) external {
536
- valueSent = bound(valueSent, 10, 2000);
537
-
538
- // Cap the highest tier ID possible to 10.
539
- uint256 highestTier = valueSent <= 100 ? (valueSent / 10) : 10;
540
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
541
- createData();
542
-
543
- // rr of 1.
544
- tiersHookConfig.tiersConfig.tiers[highestTier - 1].reserveFrequency = 1;
545
-
546
- (uint256 projectId, IJB721TiersHook _hook) =
547
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
548
-
549
- // Craft the metadata: buy 1 NFT from the highest tier.
550
- bytes memory hookMetadata;
551
- bytes[] memory data;
552
- bytes4[] memory ids;
553
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
554
- assertEq(address(_hook), dataHook);
555
- {
556
- uint16[] memory rawMetadata = new uint16[](1);
557
- // forge-lint: disable-next-line(unsafe-typecast)
558
- rawMetadata[0] = uint16(highestTier);
559
-
560
- // Build the metadata using the tiers to mint and the overspending flag.
561
- data = new bytes[](1);
562
- data[0] = abi.encode(true, rawMetadata);
563
-
564
- // Pass the hook ID.
565
- ids = new bytes4[](1);
566
- ids[0] = metadataHelper.getId("pay", address(hook));
567
-
568
- // Generate the metadata.
569
- hookMetadata = metadataHelper.createMetadata(ids, data);
570
- }
571
-
572
- // Pay the terminal to mint the NFTs.
573
- vm.prank(caller);
574
- jbMultiTerminal.pay{value: valueSent}({
575
- projectId: projectId,
576
- amount: 100,
577
- token: JBConstants.NATIVE_TOKEN,
578
- beneficiary: beneficiary,
579
- minReturnedTokens: 0,
580
- memo: "Take my money!",
581
- metadata: hookMetadata
582
- });
583
-
584
- {
585
- // Get the token ID of the NFT that was minted.
586
- uint256 tokenId = _generateTokenId(highestTier, 1);
587
-
588
- // Craft the metadata: cash out the `tokenId` which was minted.
589
- uint256[] memory cashOutId = new uint256[](1);
590
- cashOutId[0] = tokenId;
591
-
592
- // Build the metadata with the tiers to cash out.
593
- data[0] = abi.encode(cashOutId);
594
-
595
- // Pass the hook ID.
596
- ids[0] = metadataHelper.getId("cashOut", address(hook));
597
-
598
- // Generate the metadata.
599
- hookMetadata = metadataHelper.createMetadata(ids, data);
600
- }
601
-
602
- // Check: was the beneficiary's NFT balance decreased by 1?
603
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 1);
604
-
605
- // Cash out the NFT.
606
- vm.prank(beneficiary);
607
- jbMultiTerminal.cashOutTokensOf({
608
- holder: beneficiary,
609
- projectId: projectId,
610
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
611
- cashOutCount: 0,
612
- minTokensReclaimed: 0,
613
- beneficiary: payable(beneficiary),
614
- metadata: hookMetadata
615
- });
616
-
617
- // Check: was the beneficiary's NFT balance decreased by 1?
618
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 0);
619
-
620
- // Check: was the burn accounted for in the store?
621
- assertEq(IJB721TiersHook(dataHook).STORE().numberOfBurnedFor(dataHook, highestTier), 1);
622
-
623
- // Check: the number of pending reserves should be equal to the calculated figure which accounts for rounding.
624
- assertEq(IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, highestTier), 1);
625
-
626
- {
627
- uint16[] memory rawMetadata = new uint16[](1);
628
- // forge-lint: disable-next-line(unsafe-typecast)
629
- rawMetadata[0] = uint16(highestTier);
630
-
631
- // Build the metadata using the tiers to mint and the overspending flag.
632
- data = new bytes[](1);
633
- data[0] = abi.encode(true, rawMetadata);
634
-
635
- // Pass the hook ID.
636
- ids = new bytes4[](1);
637
- ids[0] = metadataHelper.getId("pay", address(hook));
638
-
639
- // Generate the metadata.
640
- hookMetadata = metadataHelper.createMetadata(ids, data);
641
- }
642
-
643
- // Pay the terminal to mint one more NFT.
644
- vm.prank(caller);
645
- jbMultiTerminal.pay{value: valueSent}({
646
- projectId: projectId,
647
- amount: 100,
648
- token: JBConstants.NATIVE_TOKEN,
649
- beneficiary: beneficiary,
650
- minReturnedTokens: 0,
651
- memo: "Take my money!",
652
- metadata: hookMetadata
653
- });
654
-
655
- // Check: was the beneficiary's NFT balance is 1.
656
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 1);
657
-
658
- // Check: the number of pending reserves shouldn't have changed.
659
- assertEq(IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, highestTier), 2);
660
- }
661
-
662
- // - Mint 5 NFTs from a tier.
663
- // - Check the remaining supply within that NFT's tier. (highest tier == 10, reserved percent is maximum -> 5)
664
- // - Cash out all of the corresponding token from that tier
665
- function testCashOutAll(bytes32 salt) external {
666
- (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig) =
667
- createData();
668
- uint256 tier = 10;
669
- uint256 tierPrice = tiersHookConfig.tiersConfig.tiers[tier - 1].price;
670
- (uint256 projectId, IJB721TiersHook _hook) =
671
- deployer.launchProjectFor(projectOwner, tiersHookConfig, launchProjectConfig, jbController, salt);
672
-
673
- // Craft the metadata: buy 5 NFTs from tier 10.
674
- uint16[] memory rawMetadata = new uint16[](5);
675
- for (uint256 i; i < rawMetadata.length; i++) {
676
- // forge-lint: disable-next-line(unsafe-typecast)
677
- rawMetadata[i] = uint16(tier);
678
- }
679
-
680
- // Build the metadata using the tiers to mint and the overspending flag.
681
- bytes[] memory data = new bytes[](1);
682
- data[0] = abi.encode(true, rawMetadata);
683
-
684
- address dataHook = jbRulesets.currentOf(projectId).dataHook();
685
- assertEq(address(_hook), dataHook);
686
-
687
- // Pass the hook ID.
688
- bytes4[] memory ids = new bytes4[](1);
689
- ids[0] = metadataHelper.getId("pay", address(hook));
690
-
691
- // Generate the metadata.
692
- bytes memory hookMetadata = metadataHelper.createMetadata(ids, data);
693
-
694
- // Pay the terminal to mint the NFTs.
695
- vm.prank(caller);
696
- jbMultiTerminal.pay{value: tierPrice * rawMetadata.length}({
697
- projectId: projectId,
698
- amount: 100,
699
- token: JBConstants.NATIVE_TOKEN,
700
- beneficiary: beneficiary,
701
- minReturnedTokens: 0,
702
- memo: "Take my money!",
703
- metadata: hookMetadata
704
- });
705
-
706
- // Get the beneficiary's new NFT balance.
707
- uint256 nftBalance = IERC721(dataHook).balanceOf(beneficiary);
708
- // Check: are the NFT balance and pending reserves correct?
709
- assertEq(rawMetadata.length, nftBalance);
710
- // Add 1 to the pending reserves check, as we round up for non-null values.
711
- assertEq(
712
- IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, tier),
713
- (nftBalance / tiersHookConfig.tiersConfig.tiers[tier - 1].reserveFrequency) + 1
714
- );
715
- // Craft the metadata to cash out the `tokenId`s.
716
- uint256[] memory cashOutId = new uint256[](5);
717
- for (uint256 i; i < rawMetadata.length; i++) {
718
- uint256 tokenId = _generateTokenId(tier, i + 1);
719
- cashOutId[i] = tokenId;
720
- }
721
-
722
- // Build the metadata with the tiers to cash out.
723
- data[0] = abi.encode(cashOutId);
724
-
725
- // Pass the hook ID.
726
- ids[0] = metadataHelper.getId("cashOut", address(hook));
727
-
728
- // Generate the metadata.
729
- hookMetadata = metadataHelper.createMetadata(ids, data);
730
-
731
- // Cash out the NFTs.
732
- vm.prank(beneficiary);
733
- jbMultiTerminal.cashOutTokensOf({
734
- holder: beneficiary,
735
- projectId: projectId,
736
- tokenToReclaim: JBConstants.NATIVE_TOKEN,
737
- cashOutCount: 0,
738
- minTokensReclaimed: 0,
739
- beneficiary: payable(beneficiary),
740
- metadata: hookMetadata
741
- });
742
-
743
- // Check: did the beneficiary's NFT balance decrease by 5 (to 0)?
744
- assertEq(IERC721(dataHook).balanceOf(beneficiary), 0);
745
- // Check: were the NFT burns accounted for in the store?
746
- assertEq(IJB721TiersHook(dataHook).STORE().numberOfBurnedFor(dataHook, tier), 5);
747
- // Check: did the number of pending reserves didnt change due to the burn.
748
- assertEq(
749
- IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, tier),
750
- (nftBalance / tiersHookConfig.tiersConfig.tiers[tier - 1].reserveFrequency) + 1
751
- );
752
-
753
- // Craft the metadata: buy *1* NFT from tier 10.
754
- uint16[] memory rawMetadata2 = new uint16[](1);
755
- for (uint256 i; i < rawMetadata2.length; i++) {
756
- // forge-lint: disable-next-line(unsafe-typecast)
757
- rawMetadata2[i] = uint16(tier);
758
- }
759
-
760
- // Build the metadata using the tiers to mint and the overspending flag.
761
- data[0] = abi.encode(true, rawMetadata2);
762
-
763
- // Pass the hook ID.
764
- ids[0] = metadataHelper.getId("pay", address(hook));
765
-
766
- // Generate the metadata.
767
- hookMetadata = metadataHelper.createMetadata(ids, data);
768
-
769
- // Check: can more NFTs be minted (now that the previous ones were burned)?
770
- vm.prank(caller);
771
- jbMultiTerminal.pay{value: tierPrice * rawMetadata2.length}({
772
- projectId: projectId,
773
- amount: 100,
774
- token: JBConstants.NATIVE_TOKEN,
775
- beneficiary: beneficiary,
776
- minReturnedTokens: 0,
777
- memo: "Take my money!",
778
- metadata: hookMetadata
779
- });
780
-
781
- // Get the new NFT balance.
782
- nftBalance = IERC721(dataHook).balanceOf(beneficiary);
783
- // Check: are the NFT balance and pending reserves correct?
784
- assertEq(rawMetadata2.length, nftBalance);
785
- // Add 1 to the pending reserves check, as we round up for non-null values.
786
- assertEq(
787
- IJB721TiersHook(dataHook).STORE().numberOfPendingReservesFor(dataHook, tier),
788
- (nftBalance / tiersHookConfig.tiersConfig.tiers[tier - 1].reserveFrequency) + 1
789
- );
790
- }
791
-
792
- // ----- internal helpers ------
793
- // Creates a `launchProjectFor(...)` payload.
794
- function createData()
795
- internal
796
- view
797
- returns (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig)
798
- {
799
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](10);
800
- for (uint256 i; i < 10; i++) {
801
- tierConfigs[i] = JB721TierConfig({
802
- price: uint104((i + 1) * 10),
803
- initialSupply: uint32(10),
804
- votingUnits: uint32((i + 1) * 10),
805
- reserveFrequency: 10,
806
- reserveBeneficiary: reserveBeneficiary,
807
- encodedIPFSUri: tokenUris[i],
808
- category: uint24(100),
809
- discountPercent: uint8(0),
810
- flags: JB721TierConfigFlags({
811
- allowOwnerMint: false,
812
- useReserveBeneficiaryAsDefault: false,
813
- transfersPausable: false,
814
- useVotingUnits: false,
815
- cantBeRemoved: false,
816
- cantIncreaseDiscountPercent: false,
817
- cantBuyWithCredits: false
818
- }),
819
- splitPercent: 0,
820
- splits: new JBSplit[](0)
821
- });
822
- }
823
- tiersHookConfig = JBDeploy721TiersHookConfig({
824
- name: name,
825
- symbol: symbol,
826
- baseUri: baseUri,
827
- tokenUriResolver: IJB721TokenUriResolver(address(0)),
828
- contractUri: contractUri,
829
- tiersConfig: JB721InitTiersConfig({
830
- tiers: tierConfigs, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
831
- }),
832
- flags: JB721TiersHookFlags({
833
- preventOverspending: false,
834
- issueTokensForSplits: false,
835
- noNewTiersWithReserves: false,
836
- noNewTiersWithVotes: false,
837
- noNewTiersWithOwnerMinting: true
838
- })
839
- });
840
-
841
- JBPayDataHookRulesetMetadata memory metadata = JBPayDataHookRulesetMetadata({
842
- reservedPercent: 5000, //50%
843
- cashOutTaxRate: 5000, //50%
844
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
845
- pausePay: false,
846
- pauseCreditTransfers: false,
847
- allowOwnerMinting: true,
848
- allowSetCustomToken: false,
849
- allowTerminalMigration: false,
850
- allowSetTerminals: false,
851
- allowSetController: false,
852
- ownerMustSendPayouts: false,
853
- allowAddAccountingContext: false,
854
- allowAddPriceFeed: false,
855
- holdFees: false,
856
- useTotalSurplusForCashOuts: false,
857
- useDataHookForCashOut: true,
858
- metadata: 0x00
859
- });
860
-
861
- JBPayDataHookRulesetConfig[] memory rulesetConfigurations = new JBPayDataHookRulesetConfig[](1);
862
- // Package up the ruleset configuration.
863
- rulesetConfigurations[0].mustStartAtOrAfter = 0;
864
- rulesetConfigurations[0].duration = 14;
865
- rulesetConfigurations[0].weight = 1000 * 10 ** 18;
866
- rulesetConfigurations[0].weightCutPercent = 450_000_000;
867
- rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
868
- rulesetConfigurations[0].metadata = metadata;
869
-
870
- JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
871
- JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
872
- accountingContextsToAccept[0] = JBAccountingContext({
873
- token: JBConstants.NATIVE_TOKEN, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
874
- });
875
- terminalConfigurations[0] =
876
- JBTerminalConfig({terminal: jbMultiTerminal, accountingContextsToAccept: accountingContextsToAccept});
877
-
878
- launchProjectConfig = JBLaunchProjectConfig({
879
- projectUri: projectUri,
880
- rulesetConfigurations: rulesetConfigurations,
881
- terminalConfigurations: terminalConfigurations,
882
- memo: ""
883
- });
884
- }
885
-
886
- function createDiscountedData(
887
- uint256 _price,
888
- uint8 _discountPercent
889
- )
890
- internal
891
- view
892
- returns (JBDeploy721TiersHookConfig memory tiersHookConfig, JBLaunchProjectConfig memory launchProjectConfig)
893
- {
894
- JB721TierConfig[] memory tierConfigs = new JB721TierConfig[](1);
895
- tierConfigs[0] = JB721TierConfig({
896
- // forge-lint: disable-next-line(unsafe-typecast)
897
- price: uint104(_price),
898
- initialSupply: uint32(10),
899
- votingUnits: uint32(10),
900
- reserveFrequency: 10,
901
- reserveBeneficiary: reserveBeneficiary,
902
- encodedIPFSUri: tokenUris[0],
903
- category: uint24(100),
904
- discountPercent: _discountPercent,
905
- flags: JB721TierConfigFlags({
906
- allowOwnerMint: false,
907
- useReserveBeneficiaryAsDefault: false,
908
- transfersPausable: false,
909
- useVotingUnits: false,
910
- cantBeRemoved: false,
911
- cantIncreaseDiscountPercent: false,
912
- cantBuyWithCredits: false
913
- }),
914
- splitPercent: 0,
915
- splits: new JBSplit[](0)
916
- });
917
-
918
- tiersHookConfig = JBDeploy721TiersHookConfig({
919
- name: name,
920
- symbol: symbol,
921
- baseUri: baseUri,
922
- tokenUriResolver: IJB721TokenUriResolver(address(0)),
923
- contractUri: contractUri,
924
- tiersConfig: JB721InitTiersConfig({
925
- tiers: tierConfigs, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
926
- }),
927
- flags: JB721TiersHookFlags({
928
- preventOverspending: false,
929
- issueTokensForSplits: false,
930
- noNewTiersWithReserves: false,
931
- noNewTiersWithVotes: false,
932
- noNewTiersWithOwnerMinting: true
933
- })
934
- });
935
-
936
- JBPayDataHookRulesetMetadata memory metadata = JBPayDataHookRulesetMetadata({
937
- reservedPercent: 5000, //50%
938
- cashOutTaxRate: 5000, //50%
939
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
940
- pausePay: false,
941
- pauseCreditTransfers: false,
942
- allowOwnerMinting: true,
943
- allowSetCustomToken: false,
944
- allowTerminalMigration: false,
945
- allowSetTerminals: false,
946
- allowSetController: false,
947
- ownerMustSendPayouts: false,
948
- allowAddAccountingContext: false,
949
- allowAddPriceFeed: false,
950
- holdFees: false,
951
- useTotalSurplusForCashOuts: false,
952
- useDataHookForCashOut: true,
953
- metadata: 0x00
954
- });
955
-
956
- JBPayDataHookRulesetConfig[] memory rulesetConfigurations = new JBPayDataHookRulesetConfig[](1);
957
- // Package up the ruleset configuration.
958
- rulesetConfigurations[0].mustStartAtOrAfter = 0;
959
- rulesetConfigurations[0].duration = 14;
960
- rulesetConfigurations[0].weight = 1000 * 10 ** 18;
961
- rulesetConfigurations[0].weightCutPercent = 450_000_000;
962
- rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
963
- rulesetConfigurations[0].metadata = metadata;
964
-
965
- JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
966
- JBAccountingContext[] memory accountingContextsToAccept = new JBAccountingContext[](1);
967
- accountingContextsToAccept[0] = JBAccountingContext({
968
- token: JBConstants.NATIVE_TOKEN, currency: uint32(uint160(JBConstants.NATIVE_TOKEN)), decimals: 18
969
- });
970
- terminalConfigurations[0] =
971
- JBTerminalConfig({terminal: jbMultiTerminal, accountingContextsToAccept: accountingContextsToAccept});
972
-
973
- launchProjectConfig = JBLaunchProjectConfig({
974
- projectUri: projectUri,
975
- rulesetConfigurations: rulesetConfigurations,
976
- terminalConfigurations: terminalConfigurations,
977
- memo: ""
978
- });
979
- }
980
-
981
- // Generate `tokenId`s based on the tier ID and token number provided.
982
- function _generateTokenId(uint256 tierId, uint256 tokenNumber) internal pure returns (uint256) {
983
- return (tierId * 1_000_000_000) + tokenNumber;
984
- }
985
- }