@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,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
- }