@croptop/core-v6 0.0.38 → 0.0.39

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 (40) hide show
  1. package/README.md +2 -2
  2. package/foundry.toml +2 -1
  3. package/package.json +25 -13
  4. package/script/ConfigureFeeProject.s.sol +8 -5
  5. package/src/CTDeployer.sol +52 -51
  6. package/src/interfaces/ICTDeployer.sol +2 -2
  7. package/ADMINISTRATION.md +0 -94
  8. package/ARCHITECTURE.md +0 -96
  9. package/AUDIT_INSTRUCTIONS.md +0 -88
  10. package/RISKS.md +0 -78
  11. package/SKILLS.md +0 -46
  12. package/STYLE_GUIDE.md +0 -610
  13. package/USER_JOURNEYS.md +0 -134
  14. package/foundry.lock +0 -11
  15. package/slither-ci.config.json +0 -10
  16. package/sphinx.lock +0 -507
  17. package/test/CTDeployer.t.sol +0 -616
  18. package/test/CTProjectOwner.t.sol +0 -185
  19. package/test/CTPublisher.t.sol +0 -869
  20. package/test/ClaimCollectionOwnership.t.sol +0 -315
  21. package/test/CroptopAttacks.t.sol +0 -437
  22. package/test/Fork.t.sol +0 -227
  23. package/test/TestAuditGaps.sol +0 -696
  24. package/test/Test_MetadataGeneration.t.sol +0 -79
  25. package/test/audit/CodexNemesisCroptopPublisherBoundary.t.sol +0 -329
  26. package/test/audit/CodexNemesisCurrencyPoCs.t.sol +0 -371
  27. package/test/audit/CodexNemesisFreshRound.t.sol +0 -395
  28. package/test/audit/CodexNemesisMetadataShadow.t.sol +0 -196
  29. package/test/audit/CodexNemesisPoCs.t.sol +0 -263
  30. package/test/audit/CodexNemesisPolicyReuse.t.sol +0 -168
  31. package/test/audit/CodexNemesisUriDrift.t.sol +0 -252
  32. package/test/audit/DeployerPermissionBypass.t.sol +0 -213
  33. package/test/audit/EmptyPostFeeBypass.t.sol +0 -53
  34. package/test/audit/FeeBeneficiaryReentrancy.t.sol +0 -247
  35. package/test/audit/FeeFallbackBlackhole.t.sol +0 -263
  36. package/test/audit/Pass12Fixes.t.sol +0 -388
  37. package/test/fork/PublishFork.t.sol +0 -440
  38. package/test/regression/DuplicateUriFeeEvasion.t.sol +0 -312
  39. package/test/regression/FeeEvasion.t.sol +0 -286
  40. package/test/regression/StaleTierIdMapping.t.sol +0 -228
@@ -1,440 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.17;
3
-
4
- // forge-lint: disable-next-line(unaliased-plain-import)
5
- import "forge-std/Test.sol";
6
-
7
- // JB core — deploy fresh within fork.
8
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
9
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
10
- import {JBDirectory} from "@bananapus/core-v6/src/JBDirectory.sol";
11
- import {JBRulesets} from "@bananapus/core-v6/src/JBRulesets.sol";
12
- import {JBTokens} from "@bananapus/core-v6/src/JBTokens.sol";
13
- import {JBERC20} from "@bananapus/core-v6/src/JBERC20.sol";
14
- import {JBSplits} from "@bananapus/core-v6/src/JBSplits.sol";
15
- import {JBPrices} from "@bananapus/core-v6/src/JBPrices.sol";
16
- import {JBController} from "@bananapus/core-v6/src/JBController.sol";
17
- import {JBFundAccessLimits} from "@bananapus/core-v6/src/JBFundAccessLimits.sol";
18
- import {JBMultiTerminal} from "@bananapus/core-v6/src/JBMultiTerminal.sol";
19
- import {JBTerminalStore} from "@bananapus/core-v6/src/JBTerminalStore.sol";
20
- import {JBFeelessAddresses} from "@bananapus/core-v6/src/JBFeelessAddresses.sol";
21
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
22
- import {JBCurrencyIds} from "@bananapus/core-v6/src/libraries/JBCurrencyIds.sol";
23
- import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
24
-
25
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
26
- import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
27
- import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
28
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
29
- import {MockPriceFeed} from "@bananapus/core-v6/test/mock/MockPriceFeed.sol";
30
-
31
- // 721 hook — deploy fresh within fork.
32
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
33
- import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
34
- import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
35
- import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
36
- import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
37
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
38
- import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
39
-
40
- // Suckers — deploy fresh within fork.
41
- import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
42
- import {JBOptimismSuckerDeployer} from "@bananapus/suckers-v6/src/deployers/JBOptimismSuckerDeployer.sol";
43
- import {JBOptimismSucker} from "@bananapus/suckers-v6/src/JBOptimismSucker.sol";
44
- import {IOPMessenger} from "@bananapus/suckers-v6/src/interfaces/IOPMessenger.sol";
45
- import {IOPStandardBridge} from "@bananapus/suckers-v6/src/interfaces/IOPStandardBridge.sol";
46
- import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
47
-
48
- // Permit2
49
- import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
50
- import {DeployPermit2} from "@uniswap/permit2/test/utils/DeployPermit2.sol";
51
-
52
- // Croptop
53
- // forge-lint: disable-next-line(unaliased-plain-import)
54
- import "./../../src/CTDeployer.sol";
55
- import {CTPublisher} from "./../../src/CTPublisher.sol";
56
- import {CTPost} from "./../../src/structs/CTPost.sol";
57
-
58
- /// @notice Fork tests for CTPublisher.mintFrom(). Deploys all JB infrastructure fresh within a mainnet fork,
59
- /// then exercises the publish-and-mint flow end-to-end.
60
- contract PublishForkTest is Test, DeployPermit2 {
61
- // ───────────────────────── Mainnet addresses
62
- // ──────────────────────────
63
-
64
- IOPMessenger constant OP_L1_MESSENGER = IOPMessenger(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1);
65
- IOPStandardBridge constant OP_L1_BRIDGE = IOPStandardBridge(0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1);
66
-
67
- // ───────────────────────── JB core (deployed fresh)
68
- // ───────────────────
69
-
70
- address multisig = address(0xBEEF);
71
- address trustedForwarder = address(0);
72
-
73
- JBPermissions jbPermissions;
74
- JBProjects jbProjects;
75
- JBDirectory jbDirectory;
76
- JBRulesets jbRulesets;
77
- JBTokens jbTokens;
78
- JBSplits jbSplits;
79
- JBPrices jbPrices;
80
- JBFundAccessLimits jbFundAccessLimits;
81
- JBController jbController;
82
-
83
- // Terminal infrastructure.
84
- JBFeelessAddresses jbFeelessAddresses;
85
- JBTerminalStore jbTerminalStore;
86
- JBMultiTerminal jbMultiTerminal;
87
-
88
- // ───────────────────────── 721 hook (deployed fresh)
89
- // ──────────────────
90
-
91
- JB721TiersHookDeployer hookDeployer;
92
-
93
- // ───────────────────────── Suckers (deployed fresh)
94
- // ───────────────────
95
-
96
- JBSuckerRegistry suckerRegistry;
97
- JBOptimismSuckerDeployer opSuckerDeployer;
98
-
99
- // ───────────────────────── Croptop
100
- // ────────────────────────────────────
101
-
102
- CTPublisher publisher;
103
- CTDeployer deployer;
104
-
105
- // ───────────────────────── Test actors & state
106
- // ────────────────────────
107
-
108
- address projectOwner = address(0xA11CE);
109
- address poster = address(0xB0B);
110
- address nftBeneficiary = address(0xCAFE);
111
- address feeBeneficiary = address(0xFEE);
112
-
113
- uint256 feeProjectId; // project 1
114
- uint256 testProjectId;
115
- IJB721TiersHook testHook;
116
-
117
- // ───────────────────────── Constants
118
- // ──────────────────────────────────
119
-
120
- uint104 constant POST_PRICE = 0.1 ether;
121
- uint32 constant POST_SUPPLY = 100;
122
- uint24 constant POST_CATEGORY = 1;
123
- // forge-lint: disable-next-line(unsafe-typecast)
124
- bytes32 constant TEST_URI = bytes32("test_ipfs_uri");
125
- // forge-lint: disable-next-line(unsafe-typecast)
126
- bytes32 constant TEST_URI_2 = bytes32("test_ipfs_uri_2");
127
-
128
- // ───────────────────────── Setup
129
- // ─────────────────────────────────────
130
-
131
- function setUp() public {
132
- // Fork ETH mainnet at a pinned block to avoid RPC tip-of-chain flakiness.
133
- vm.createSelectFork("ethereum", 24_960_000);
134
-
135
- // Deploy all JB core contracts fresh within the fork.
136
- _deployJBCore();
137
-
138
- // CTDeployer hardcodes baseCurrency = JBCurrencyIds.ETH (1), but the accounting context
139
- // uses currency = uint32(uint160(NATIVE_TOKEN)) = 61166. Add an identity price feed
140
- // so JBPrices can convert between them.
141
- MockPriceFeed identityFeed = new MockPriceFeed(1e18, 18);
142
- vm.prank(multisig);
143
- jbPrices.addPriceFeedFor({
144
- projectId: 0,
145
- pricingCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
146
- unitCurrency: JBCurrencyIds.ETH,
147
- feed: identityFeed
148
- });
149
-
150
- // Deploy the terminal infrastructure.
151
- _deployTerminal();
152
-
153
- // Deploy the 721 hook infrastructure.
154
- _deploy721Hook();
155
-
156
- // Deploy the sucker infrastructure.
157
- _deploySuckers();
158
-
159
- // Deploy the croptop contracts.
160
- publisher = new CTPublisher(jbDirectory, jbPermissions, 1, trustedForwarder);
161
- deployer = new CTDeployer(jbPermissions, jbProjects, hookDeployer, publisher, suckerRegistry, trustedForwarder);
162
-
163
- // Launch the fee project (project 1) with a terminal that accepts ETH.
164
- feeProjectId = _launchFeeProject();
165
-
166
- // Launch a test project via CTDeployer with a terminal + allowed posts.
167
- (testProjectId, testHook) = _launchTestProject();
168
-
169
- // Fund the poster.
170
- vm.deal(poster, 10 ether);
171
- }
172
-
173
- // ───────────────────────── Tests
174
- // ─────────────────────────────────────
175
-
176
- /// @notice Verify that mintFrom() mints an NFT to the specified beneficiary.
177
- function testFork_MintFromPublishesNFT() public {
178
- // Build a valid post.
179
- CTPost[] memory posts = _singlePost(TEST_URI, POST_PRICE, POST_SUPPLY, POST_CATEGORY);
180
-
181
- // Calculate required msg.value: price + fee.
182
- uint256 fee = uint256(POST_PRICE) / 20;
183
- uint256 totalValue = uint256(POST_PRICE) + fee;
184
-
185
- // Check NFT balance before.
186
- uint256 balanceBefore = IERC721(address(testHook)).balanceOf(nftBeneficiary);
187
-
188
- // Mint.
189
- vm.prank(poster);
190
- publisher.mintFrom{value: totalValue}(testHook, posts, nftBeneficiary, feeBeneficiary, "", "");
191
-
192
- // Verify NFT was minted to the beneficiary.
193
- uint256 balanceAfter = IERC721(address(testHook)).balanceOf(nftBeneficiary);
194
- assertEq(balanceAfter, balanceBefore + 1, "NFT should be minted to beneficiary");
195
- }
196
-
197
- /// @notice Verify 5% fee is routed to fee project and the rest to the test project.
198
- function testFork_MintFromFeeDistribution() public {
199
- CTPost[] memory posts = _singlePost(TEST_URI, POST_PRICE, POST_SUPPLY, POST_CATEGORY);
200
-
201
- uint256 fee = uint256(POST_PRICE) / 20;
202
- uint256 totalValue = uint256(POST_PRICE) + fee;
203
-
204
- // Record terminal balances before minting.
205
- uint256 feeProjectBalanceBefore =
206
- jbTerminalStore.balanceOf(address(jbMultiTerminal), feeProjectId, JBConstants.NATIVE_TOKEN);
207
- uint256 testProjectBalanceBefore =
208
- jbTerminalStore.balanceOf(address(jbMultiTerminal), testProjectId, JBConstants.NATIVE_TOKEN);
209
-
210
- // Mint.
211
- vm.prank(poster);
212
- publisher.mintFrom{value: totalValue}(testHook, posts, nftBeneficiary, feeBeneficiary, "", "");
213
-
214
- // Verify fee project terminal balance increased by the fee amount.
215
- uint256 feeProjectBalanceAfter =
216
- jbTerminalStore.balanceOf(address(jbMultiTerminal), feeProjectId, JBConstants.NATIVE_TOKEN);
217
- assertEq(
218
- feeProjectBalanceAfter - feeProjectBalanceBefore,
219
- fee,
220
- "Fee project balance should increase by totalPrice / 20"
221
- );
222
-
223
- // Verify test project terminal balance increased by the post price.
224
- uint256 testProjectBalanceAfter =
225
- jbTerminalStore.balanceOf(address(jbMultiTerminal), testProjectId, JBConstants.NATIVE_TOKEN);
226
- assertEq(
227
- testProjectBalanceAfter - testProjectBalanceBefore,
228
- uint256(POST_PRICE),
229
- "Test project balance should increase by post price"
230
- );
231
- }
232
-
233
- /// @notice Verify that sending less ETH than required reverts.
234
- function testFork_MintFromInsufficientFeeReverts() public {
235
- CTPost[] memory posts = _singlePost(TEST_URI, POST_PRICE, POST_SUPPLY, POST_CATEGORY);
236
-
237
- // Send only the post price, not the post price + fee.
238
- uint256 insufficientValue = uint256(POST_PRICE);
239
-
240
- vm.prank(poster);
241
- vm.expectRevert();
242
- publisher.mintFrom{value: insufficientValue}(testHook, posts, nftBeneficiary, feeBeneficiary, "", "");
243
- }
244
-
245
- /// @notice Verify that minting the same encodedIPFSUri twice reuses the existing tier ID.
246
- function testFork_MintFromDuplicatePostReusesExistingTier() public {
247
- CTPost[] memory posts = _singlePost(TEST_URI, POST_PRICE, POST_SUPPLY, POST_CATEGORY);
248
-
249
- uint256 fee = uint256(POST_PRICE) / 20;
250
- uint256 totalValue = uint256(POST_PRICE) + fee;
251
-
252
- // First mint.
253
- vm.prank(poster);
254
- publisher.mintFrom{value: totalValue}(testHook, posts, nftBeneficiary, feeBeneficiary, "", "");
255
-
256
- // Record the tier ID assigned to this URI after the first mint.
257
- uint256 tierIdAfterFirst = publisher.tierIdForEncodedIPFSUriOf(address(testHook), TEST_URI);
258
- assertGt(tierIdAfterFirst, 0, "Tier ID should be non-zero after first mint");
259
-
260
- // Second mint with the same URI. The existing tier should be reused.
261
- vm.prank(poster);
262
- publisher.mintFrom{value: totalValue}(testHook, posts, nftBeneficiary, feeBeneficiary, "", "");
263
-
264
- // Verify the tier ID is unchanged — no new tier was created.
265
- uint256 tierIdAfterSecond = publisher.tierIdForEncodedIPFSUriOf(address(testHook), TEST_URI);
266
- assertEq(tierIdAfterFirst, tierIdAfterSecond, "Tier ID should be reused for duplicate encodedIPFSUri");
267
-
268
- // Verify two NFTs were minted total.
269
- assertEq(IERC721(address(testHook)).balanceOf(nftBeneficiary), 2, "Two NFTs should be minted across both calls");
270
- }
271
-
272
- // ───────────────────────── Internal deployment helpers
273
- // ────────────────
274
-
275
- // forge-lint: disable-next-line(mixed-case-function)
276
- function _deployJBCore() internal {
277
- jbPermissions = new JBPermissions(trustedForwarder);
278
- jbProjects = new JBProjects(multisig, address(0), trustedForwarder);
279
- jbDirectory = new JBDirectory(jbPermissions, jbProjects, multisig);
280
- JBERC20 jbErc20 = new JBERC20(jbPermissions, jbProjects);
281
- jbTokens = new JBTokens(jbDirectory, jbErc20);
282
- jbRulesets = new JBRulesets(jbDirectory);
283
- jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, multisig, trustedForwarder);
284
- jbSplits = new JBSplits(jbDirectory);
285
- jbFundAccessLimits = new JBFundAccessLimits(jbDirectory);
286
-
287
- jbController = new JBController(
288
- jbDirectory,
289
- jbFundAccessLimits,
290
- jbPermissions,
291
- jbPrices,
292
- jbProjects,
293
- jbRulesets,
294
- jbSplits,
295
- jbTokens,
296
- address(0), // omnichainRulesetOperator
297
- trustedForwarder
298
- );
299
-
300
- vm.prank(multisig);
301
- jbDirectory.setIsAllowedToSetFirstController(address(jbController), true);
302
- }
303
-
304
- function _deployTerminal() internal {
305
- jbFeelessAddresses = new JBFeelessAddresses(multisig);
306
- jbTerminalStore = new JBTerminalStore(jbDirectory, jbPrices, jbRulesets);
307
-
308
- address permit2 = deployPermit2();
309
-
310
- jbMultiTerminal = new JBMultiTerminal(
311
- jbFeelessAddresses,
312
- jbPermissions,
313
- jbProjects,
314
- jbSplits,
315
- jbTerminalStore,
316
- jbTokens,
317
- IPermit2(permit2),
318
- trustedForwarder
319
- );
320
- }
321
-
322
- function _deploy721Hook() internal {
323
- JB721TiersHookStore store = new JB721TiersHookStore();
324
- JBAddressRegistry addressRegistry = new JBAddressRegistry();
325
- JB721CheckpointsDeployer checkpointsDeployer = new JB721CheckpointsDeployer();
326
-
327
- JB721TiersHook hookImpl = new JB721TiersHook(
328
- jbDirectory, jbPermissions, jbPrices, jbRulesets, store, jbSplits, checkpointsDeployer, trustedForwarder
329
- );
330
-
331
- hookDeployer = new JB721TiersHookDeployer(hookImpl, store, addressRegistry, trustedForwarder);
332
- }
333
-
334
- function _deploySuckers() internal {
335
- suckerRegistry = new JBSuckerRegistry(jbDirectory, jbPermissions, multisig, trustedForwarder);
336
-
337
- opSuckerDeployer =
338
- new JBOptimismSuckerDeployer(jbDirectory, jbPermissions, jbTokens, multisig, trustedForwarder);
339
-
340
- vm.startPrank(multisig);
341
- opSuckerDeployer.setChainSpecificConstants(OP_L1_MESSENGER, OP_L1_BRIDGE);
342
-
343
- JBOptimismSucker singleton = new JBOptimismSucker(
344
- opSuckerDeployer, jbDirectory, jbPermissions, jbTokens, 1, suckerRegistry, trustedForwarder
345
- );
346
- opSuckerDeployer.configureSingleton(singleton);
347
-
348
- suckerRegistry.allowSuckerDeployer(address(opSuckerDeployer));
349
- vm.stopPrank();
350
- }
351
-
352
- /// @notice Launch fee project (project 1) with ETH terminal so it can receive fees.
353
- function _launchFeeProject() internal returns (uint256 projectId) {
354
- // Build terminal config accepting native ETH.
355
- JBTerminalConfig[] memory terminalConfigs = _ethTerminalConfig();
356
-
357
- // A simple ruleset with no special rules.
358
- JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
359
- rulesetConfigs[0].weight = 1_000_000 * (10 ** 18);
360
- rulesetConfigs[0].metadata.baseCurrency = JBCurrencyIds.ETH;
361
-
362
- projectId = jbController.launchProjectFor({
363
- owner: multisig,
364
- projectUri: "Fee Project",
365
- rulesetConfigurations: rulesetConfigs,
366
- terminalConfigurations: terminalConfigs,
367
- memo: "Fee project launch"
368
- });
369
-
370
- // Sanity check: fee project must be project 1.
371
- assertEq(projectId, 1, "Fee project must be project ID 1");
372
- }
373
-
374
- /// @notice Launch a test project via CTDeployer with ETH terminal and allowed posts.
375
- function _launchTestProject() internal returns (uint256 projectId, IJB721TiersHook hook) {
376
- // Build terminal config accepting native ETH.
377
- JBTerminalConfig[] memory terminalConfigs = _ethTerminalConfig();
378
-
379
- // Build allowed posts for the deployer.
380
- CTDeployerAllowedPost[] memory allowedPosts = new CTDeployerAllowedPost[](1);
381
- allowedPosts[0] = CTDeployerAllowedPost({
382
- category: POST_CATEGORY,
383
- minimumPrice: 0,
384
- minimumTotalSupply: 1,
385
- maximumTotalSupply: 10_000,
386
- maximumSplitPercent: 500_000_000, // 50%
387
- allowedAddresses: new address[](0) // anyone can post
388
- });
389
-
390
- CTProjectConfig memory config = CTProjectConfig({
391
- terminalConfigurations: terminalConfigs,
392
- projectUri: "https://test.croptop.eth/",
393
- allowedPosts: allowedPosts,
394
- contractUri: "https://test.croptop.eth/contract",
395
- name: "TestCrop",
396
- symbol: "TCROP",
397
- salt: bytes32(uint256(1))
398
- });
399
-
400
- CTSuckerDeploymentConfig memory suckerConfig =
401
- CTSuckerDeploymentConfig({deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32(0)});
402
-
403
- (projectId, hook) = deployer.deployProjectFor(projectOwner, config, suckerConfig, jbController);
404
- }
405
-
406
- /// @notice Build a JBTerminalConfig[] with a single entry for native ETH.
407
- function _ethTerminalConfig() internal view returns (JBTerminalConfig[] memory configs) {
408
- JBAccountingContext[] memory contexts = new JBAccountingContext[](1);
409
- contexts[0] = JBAccountingContext({
410
- token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
411
- });
412
-
413
- configs = new JBTerminalConfig[](1);
414
- configs[0] =
415
- JBTerminalConfig({terminal: IJBTerminal(address(jbMultiTerminal)), accountingContextsToAccept: contexts});
416
- }
417
-
418
- /// @notice Build a single-element CTPost array.
419
- function _singlePost(
420
- // forge-lint: disable-next-line(mixed-case-variable)
421
- bytes32 encodedIPFSUri,
422
- uint104 price,
423
- uint32 totalSupply,
424
- uint24 category
425
- )
426
- internal
427
- pure
428
- returns (CTPost[] memory posts)
429
- {
430
- posts = new CTPost[](1);
431
- posts[0] = CTPost({
432
- encodedIPFSUri: encodedIPFSUri,
433
- price: price,
434
- totalSupply: totalSupply,
435
- category: category,
436
- splitPercent: 0,
437
- splits: new JBSplit[](0)
438
- });
439
- }
440
- }