@croptop/core-v6 0.0.1

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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/SKILLS.md +88 -0
  4. package/deployments/croptop-core-v5/arbitrum/CTDeployer.json +1896 -0
  5. package/deployments/croptop-core-v5/arbitrum/CTProjectOwner.json +186 -0
  6. package/deployments/croptop-core-v5/arbitrum/CTPublisher.json +738 -0
  7. package/deployments/croptop-core-v5/arbitrum_sepolia/CTDeployer.json +1883 -0
  8. package/deployments/croptop-core-v5/arbitrum_sepolia/CTProjectOwner.json +186 -0
  9. package/deployments/croptop-core-v5/arbitrum_sepolia/CTPublisher.json +738 -0
  10. package/deployments/croptop-core-v5/base/CTDeployer.json +1908 -0
  11. package/deployments/croptop-core-v5/base/CTProjectOwner.json +190 -0
  12. package/deployments/croptop-core-v5/base/CTPublisher.json +741 -0
  13. package/deployments/croptop-core-v5/base_sepolia/CTDeployer.json +1894 -0
  14. package/deployments/croptop-core-v5/base_sepolia/CTProjectOwner.json +190 -0
  15. package/deployments/croptop-core-v5/base_sepolia/CTPublisher.json +741 -0
  16. package/deployments/croptop-core-v5/ethereum/CTDeployer.json +1894 -0
  17. package/deployments/croptop-core-v5/ethereum/CTProjectOwner.json +190 -0
  18. package/deployments/croptop-core-v5/ethereum/CTPublisher.json +741 -0
  19. package/deployments/croptop-core-v5/optimism/CTDeployer.json +1894 -0
  20. package/deployments/croptop-core-v5/optimism/CTProjectOwner.json +190 -0
  21. package/deployments/croptop-core-v5/optimism/CTPublisher.json +741 -0
  22. package/deployments/croptop-core-v5/optimism_sepolia/CTDeployer.json +1894 -0
  23. package/deployments/croptop-core-v5/optimism_sepolia/CTProjectOwner.json +190 -0
  24. package/deployments/croptop-core-v5/optimism_sepolia/CTPublisher.json +741 -0
  25. package/deployments/croptop-core-v5/sepolia/CTDeployer.json +1894 -0
  26. package/deployments/croptop-core-v5/sepolia/CTProjectOwner.json +190 -0
  27. package/deployments/croptop-core-v5/sepolia/CTPublisher.json +741 -0
  28. package/foundry.toml +25 -0
  29. package/package.json +31 -0
  30. package/remappings.txt +2 -0
  31. package/script/ConfigureFeeProject.s.sol +386 -0
  32. package/script/Deploy.s.sol +138 -0
  33. package/script/helpers/CroptopDeploymentLib.sol +75 -0
  34. package/slither-ci.config.json +10 -0
  35. package/sphinx.lock +507 -0
  36. package/src/CTDeployer.sol +425 -0
  37. package/src/CTProjectOwner.sol +78 -0
  38. package/src/CTPublisher.sol +540 -0
  39. package/src/interfaces/ICTDeployer.sol +56 -0
  40. package/src/interfaces/ICTProjectOwner.sol +24 -0
  41. package/src/interfaces/ICTPublisher.sol +91 -0
  42. package/src/structs/CTAllowedPost.sol +22 -0
  43. package/src/structs/CTDeployerAllowedPost.sol +20 -0
  44. package/src/structs/CTPost.sol +22 -0
  45. package/src/structs/CTProjectConfig.sol +22 -0
  46. package/src/structs/CTSuckerDeploymentConfig.sol +11 -0
  47. package/test/CTPublisher.t.sol +672 -0
  48. package/test/CroptopAttacks.t.sol +439 -0
  49. package/test/Fork.t.sol +114 -0
  50. package/test/Test_MetadataGeneration.t.sol +70 -0
@@ -0,0 +1,439 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.23;
3
+
4
+ import "forge-std/Test.sol";
5
+
6
+ import {IJBPermissions} from "@bananapus/core-v5/src/interfaces/IJBPermissions.sol";
7
+ import {IJBDirectory} from "@bananapus/core-v5/src/interfaces/IJBDirectory.sol";
8
+ import {IJBTerminal} from "@bananapus/core-v5/src/interfaces/IJBTerminal.sol";
9
+ import {IJBSplitHook} from "@bananapus/core-v5/src/interfaces/IJBSplitHook.sol";
10
+ import {IJBOwnable} from "@bananapus/ownable-v5/src/interfaces/IJBOwnable.sol";
11
+ import {IJB721TiersHook} from "@bananapus/721-hook-v5/src/interfaces/IJB721TiersHook.sol";
12
+ import {IJB721TiersHookStore} from "@bananapus/721-hook-v5/src/interfaces/IJB721TiersHookStore.sol";
13
+ import {IJB721Hook} from "@bananapus/721-hook-v5/src/interfaces/IJB721Hook.sol";
14
+ import {JB721TierConfig} from "@bananapus/721-hook-v5/src/structs/JB721TierConfig.sol";
15
+ import {JBConstants} from "@bananapus/core-v5/src/libraries/JBConstants.sol";
16
+ import {JBSplit} from "@bananapus/core-v5/src/structs/JBSplit.sol";
17
+ import {JBPermissionIds} from "@bananapus/permission-ids-v5/src/JBPermissionIds.sol";
18
+
19
+ import {CTPublisher} from "../src/CTPublisher.sol";
20
+ import {CTAllowedPost} from "../src/structs/CTAllowedPost.sol";
21
+ import {CTPost} from "../src/structs/CTPost.sol";
22
+
23
+ /// @title CroptopAttacks
24
+ /// @notice Adversarial security tests for CTPublisher focusing on mintFrom edge cases,
25
+ /// allowlist bypasses, input validation, and split percent enforcement.
26
+ contract CroptopAttacks is Test {
27
+ CTPublisher publisher;
28
+
29
+ IJBPermissions permissions = IJBPermissions(makeAddr("permissions"));
30
+ IJBDirectory directory = IJBDirectory(makeAddr("directory"));
31
+
32
+ address hookOwner = makeAddr("hookOwner");
33
+ address hookAddr = makeAddr("hook");
34
+ address hookStoreAddr = makeAddr("hookStore");
35
+ address terminalAddr = makeAddr("terminal");
36
+ address poster = makeAddr("poster");
37
+ address unauthorized = makeAddr("unauthorized");
38
+
39
+ uint256 feeProjectId = 1;
40
+ uint256 hookProjectId = 42;
41
+
42
+ function setUp() public {
43
+ publisher = new CTPublisher(directory, permissions, feeProjectId, address(0));
44
+
45
+ // Mock hook.owner().
46
+ vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.owner.selector), abi.encode(hookOwner));
47
+ // Mock hook.PROJECT_ID().
48
+ vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(hookProjectId));
49
+ // Mock hook.STORE().
50
+ vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721TiersHook.STORE.selector), abi.encode(hookStoreAddr));
51
+
52
+ // Mock permissions to return true by default.
53
+ vm.mockCall(
54
+ address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(true)
55
+ );
56
+
57
+ // Fund test accounts so they can send ETH with mintFrom.
58
+ vm.deal(poster, 100 ether);
59
+ vm.deal(unauthorized, 100 ether);
60
+ }
61
+
62
+ /// @dev Configure a standard category for testing.
63
+ function _configureCategory(uint24 category, uint104 minPrice, uint32 minSupply, uint32 maxSupply) internal {
64
+ CTAllowedPost[] memory posts = new CTAllowedPost[](1);
65
+ posts[0] = CTAllowedPost({
66
+ hook: hookAddr,
67
+ category: category,
68
+ minimumPrice: minPrice,
69
+ minimumTotalSupply: minSupply,
70
+ maximumTotalSupply: maxSupply,
71
+ maximumSplitPercent: 0,
72
+ allowedAddresses: new address[](0)
73
+ });
74
+
75
+ vm.prank(hookOwner);
76
+ publisher.configurePostingCriteriaFor(posts);
77
+ }
78
+
79
+ /// @dev Configure a category with an allowlist.
80
+ function _configureCategoryWithAllowlist(uint24 category, address[] memory allowed) internal {
81
+ CTAllowedPost[] memory posts = new CTAllowedPost[](1);
82
+ posts[0] = CTAllowedPost({
83
+ hook: hookAddr,
84
+ category: category,
85
+ minimumPrice: 0,
86
+ minimumTotalSupply: 1,
87
+ maximumTotalSupply: 100,
88
+ maximumSplitPercent: 0,
89
+ allowedAddresses: allowed
90
+ });
91
+
92
+ vm.prank(hookOwner);
93
+ publisher.configurePostingCriteriaFor(posts);
94
+ }
95
+
96
+ /// @dev Configure a category with a maximum split percent.
97
+ function _configureCategoryWithSplits(
98
+ uint24 category,
99
+ uint104 minPrice,
100
+ uint32 minSupply,
101
+ uint32 maxSupply,
102
+ uint32 maxSplitPercent
103
+ )
104
+ internal
105
+ {
106
+ CTAllowedPost[] memory posts = new CTAllowedPost[](1);
107
+ posts[0] = CTAllowedPost({
108
+ hook: hookAddr,
109
+ category: category,
110
+ minimumPrice: minPrice,
111
+ minimumTotalSupply: minSupply,
112
+ maximumTotalSupply: maxSupply,
113
+ maximumSplitPercent: maxSplitPercent,
114
+ allowedAddresses: new address[](0)
115
+ });
116
+
117
+ vm.prank(hookOwner);
118
+ publisher.configurePostingCriteriaFor(posts);
119
+ }
120
+
121
+ /// @dev Set up mocks for mintFrom path.
122
+ function _setupMintMocks() internal {
123
+ vm.mockCall(
124
+ hookStoreAddr, abi.encodeWithSelector(IJB721TiersHookStore.maxTierIdOf.selector), abi.encode(uint256(0))
125
+ );
126
+ vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721TiersHook.adjustTiers.selector), abi.encode());
127
+ // METADATA_ID_TARGET() selector.
128
+ vm.mockCall(hookAddr, abi.encodeWithSelector(bytes4(keccak256("METADATA_ID_TARGET()"))), abi.encode(address(0)));
129
+ vm.mockCall(
130
+ address(directory),
131
+ abi.encodeWithSelector(IJBDirectory.primaryTerminalOf.selector),
132
+ abi.encode(terminalAddr)
133
+ );
134
+ vm.mockCall(terminalAddr, "", abi.encode(uint256(0)));
135
+ }
136
+
137
+ // =========================================================================
138
+ // Test 1: Post to unconfigured category — should revert
139
+ // =========================================================================
140
+ function test_mintFrom_unconfiguredCategory_reverts() public {
141
+ _setupMintMocks();
142
+
143
+ CTPost[] memory posts = new CTPost[](1);
144
+ posts[0] = CTPost({
145
+ encodedIPFSUri: keccak256("test-content"),
146
+ totalSupply: 10,
147
+ price: 0.1 ether,
148
+ category: 999,
149
+ splitPercent: 0,
150
+ splits: new JBSplit[](0)
151
+ });
152
+
153
+ vm.prank(poster);
154
+ vm.expectRevert();
155
+ publisher.mintFrom{value: 0.1 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
156
+ }
157
+
158
+ // =========================================================================
159
+ // Test 2: Post with price below minimum — should revert
160
+ // =========================================================================
161
+ function test_mintFrom_belowMinPrice_reverts() public {
162
+ _configureCategory(5, 0.1 ether, 1, 100);
163
+ _setupMintMocks();
164
+
165
+ CTPost[] memory posts = new CTPost[](1);
166
+ posts[0] = CTPost({
167
+ encodedIPFSUri: keccak256("cheap-content"),
168
+ totalSupply: 10,
169
+ price: 0.01 ether,
170
+ category: 5,
171
+ splitPercent: 0,
172
+ splits: new JBSplit[](0)
173
+ });
174
+
175
+ vm.prank(poster);
176
+ vm.expectRevert();
177
+ publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
178
+ }
179
+
180
+ // =========================================================================
181
+ // Test 3: Post with supply above maximum — should revert
182
+ // =========================================================================
183
+ function test_mintFrom_exceedsMaxSupply_reverts() public {
184
+ _configureCategory(5, 0, 1, 50);
185
+ _setupMintMocks();
186
+
187
+ CTPost[] memory posts = new CTPost[](1);
188
+ posts[0] = CTPost({
189
+ encodedIPFSUri: keccak256("big-supply"),
190
+ totalSupply: 100,
191
+ price: 0.01 ether,
192
+ category: 5,
193
+ splitPercent: 0,
194
+ splits: new JBSplit[](0)
195
+ });
196
+
197
+ vm.prank(poster);
198
+ vm.expectRevert();
199
+ publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
200
+ }
201
+
202
+ // =========================================================================
203
+ // Test 4: Post with supply below minimum — should revert
204
+ // =========================================================================
205
+ function test_mintFrom_belowMinSupply_reverts() public {
206
+ _configureCategory(5, 0, 10, 100);
207
+ _setupMintMocks();
208
+
209
+ CTPost[] memory posts = new CTPost[](1);
210
+ posts[0] = CTPost({
211
+ encodedIPFSUri: keccak256("small-supply"),
212
+ totalSupply: 5,
213
+ price: 0.01 ether,
214
+ category: 5,
215
+ splitPercent: 0,
216
+ splits: new JBSplit[](0)
217
+ });
218
+
219
+ vm.prank(poster);
220
+ vm.expectRevert();
221
+ publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
222
+ }
223
+
224
+ // =========================================================================
225
+ // Test 5: Allowlist bypass — non-allowed address posts to restricted category
226
+ // =========================================================================
227
+ function test_mintFrom_allowlistBypass_reverts() public {
228
+ address[] memory allowed = new address[](1);
229
+ allowed[0] = poster;
230
+
231
+ _configureCategoryWithAllowlist(7, allowed);
232
+ _setupMintMocks();
233
+
234
+ CTPost[] memory posts = new CTPost[](1);
235
+ posts[0] = CTPost({
236
+ encodedIPFSUri: keccak256("sneaky-content"),
237
+ totalSupply: 10,
238
+ price: 0.01 ether,
239
+ category: 7,
240
+ splitPercent: 0,
241
+ splits: new JBSplit[](0)
242
+ });
243
+
244
+ vm.prank(unauthorized);
245
+ vm.expectRevert();
246
+ publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, unauthorized, unauthorized, "", "");
247
+ }
248
+
249
+ // =========================================================================
250
+ // Test 6: Zero IPFS URI — should revert
251
+ // =========================================================================
252
+ function test_mintFrom_zeroIPFSUri_reverts() public {
253
+ _configureCategory(5, 0, 1, 100);
254
+ _setupMintMocks();
255
+
256
+ CTPost[] memory posts = new CTPost[](1);
257
+ posts[0] = CTPost({
258
+ encodedIPFSUri: bytes32(0),
259
+ totalSupply: 10,
260
+ price: 0.01 ether,
261
+ category: 5,
262
+ splitPercent: 0,
263
+ splits: new JBSplit[](0)
264
+ });
265
+
266
+ vm.prank(poster);
267
+ vm.expectRevert();
268
+ publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
269
+ }
270
+
271
+ // =========================================================================
272
+ // Test 7: Configure with zero minSupply reverts
273
+ // =========================================================================
274
+ function test_configure_zeroMinSupply_reverts() public {
275
+ CTAllowedPost[] memory posts = new CTAllowedPost[](1);
276
+ posts[0] = CTAllowedPost({
277
+ hook: hookAddr,
278
+ category: 5,
279
+ minimumPrice: 0,
280
+ minimumTotalSupply: 0,
281
+ maximumTotalSupply: 100,
282
+ maximumSplitPercent: 0,
283
+ allowedAddresses: new address[](0)
284
+ });
285
+
286
+ vm.prank(hookOwner);
287
+ vm.expectRevert(CTPublisher.CTPublisher_ZeroTotalSupply.selector);
288
+ publisher.configurePostingCriteriaFor(posts);
289
+ }
290
+
291
+ // =========================================================================
292
+ // Test 8: Configure without permission — should revert
293
+ // =========================================================================
294
+ function test_configure_noPermission_reverts() public {
295
+ vm.mockCall(
296
+ address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(false)
297
+ );
298
+
299
+ CTAllowedPost[] memory posts = new CTAllowedPost[](1);
300
+ posts[0] = CTAllowedPost({
301
+ hook: hookAddr,
302
+ category: 1,
303
+ minimumPrice: 0,
304
+ minimumTotalSupply: 1,
305
+ maximumTotalSupply: 100,
306
+ maximumSplitPercent: 0,
307
+ allowedAddresses: new address[](0)
308
+ });
309
+
310
+ vm.prank(unauthorized);
311
+ vm.expectRevert();
312
+ publisher.configurePostingCriteriaFor(posts);
313
+ }
314
+
315
+ // =========================================================================
316
+ // Test 9: Split percent exceeds maximum — should revert
317
+ // =========================================================================
318
+ function test_mintFrom_splitPercentExceedsMaximum_reverts() public {
319
+ _configureCategoryWithSplits(5, 0, 1, 100, 500_000_000); // 50% max
320
+ _setupMintMocks();
321
+
322
+ CTPost[] memory posts = new CTPost[](1);
323
+ posts[0] = CTPost({
324
+ encodedIPFSUri: keccak256("greedy-split"),
325
+ totalSupply: 10,
326
+ price: 0.1 ether,
327
+ category: 5,
328
+ splitPercent: 750_000_000, // 75% exceeds 50%
329
+ splits: new JBSplit[](0)
330
+ });
331
+
332
+ vm.prank(poster);
333
+ vm.expectRevert(
334
+ abi.encodeWithSelector(
335
+ CTPublisher.CTPublisher_SplitPercentExceedsMaximum.selector, 750_000_000, 500_000_000
336
+ )
337
+ );
338
+ publisher.mintFrom{value: 0.2 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
339
+ }
340
+
341
+ // =========================================================================
342
+ // Test 10: Split percent when splits disabled (max=0) — should revert
343
+ // =========================================================================
344
+ function test_mintFrom_splitPercentWhenDisabled_reverts() public {
345
+ _configureCategoryWithSplits(5, 0, 1, 100, 0); // Splits disabled
346
+ _setupMintMocks();
347
+
348
+ JBSplit[] memory splits = new JBSplit[](1);
349
+ splits[0] = JBSplit({
350
+ percent: 100_000_000,
351
+ projectId: 0,
352
+ beneficiary: payable(poster),
353
+ preferAddToBalance: false,
354
+ lockedUntil: 0,
355
+ hook: IJBSplitHook(address(0))
356
+ });
357
+
358
+ CTPost[] memory posts = new CTPost[](1);
359
+ posts[0] = CTPost({
360
+ encodedIPFSUri: keccak256("sneaky-split"),
361
+ totalSupply: 10,
362
+ price: 0.1 ether,
363
+ category: 5,
364
+ splitPercent: 100_000_000, // Any amount when disabled
365
+ splits: splits
366
+ });
367
+
368
+ vm.prank(poster);
369
+ vm.expectRevert(
370
+ abi.encodeWithSelector(CTPublisher.CTPublisher_SplitPercentExceedsMaximum.selector, 100_000_000, 0)
371
+ );
372
+ publisher.mintFrom{value: 0.2 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
373
+ }
374
+
375
+ // =========================================================================
376
+ // Test 11: Attacker re-configures to raise split percent
377
+ // =========================================================================
378
+ function test_reconfigure_raiseSplitPercent_requiresPermission() public {
379
+ // Owner configures with 50% max split.
380
+ _configureCategoryWithSplits(5, 0, 1, 100, 500_000_000);
381
+
382
+ // Mock permissions to return false for unauthorized.
383
+ vm.mockCall(
384
+ address(permissions),
385
+ abi.encodeWithSelector(IJBPermissions.hasPermission.selector, unauthorized),
386
+ abi.encode(false)
387
+ );
388
+
389
+ // Attacker tries to reconfigure with 100% max split.
390
+ CTAllowedPost[] memory posts = new CTAllowedPost[](1);
391
+ posts[0] = CTAllowedPost({
392
+ hook: hookAddr,
393
+ category: 5,
394
+ minimumPrice: 0,
395
+ minimumTotalSupply: 1,
396
+ maximumTotalSupply: 100,
397
+ maximumSplitPercent: uint32(JBConstants.SPLITS_TOTAL_PERCENT),
398
+ allowedAddresses: new address[](0)
399
+ });
400
+
401
+ vm.prank(unauthorized);
402
+ vm.expectRevert();
403
+ publisher.configurePostingCriteriaFor(posts);
404
+ }
405
+
406
+ // =========================================================================
407
+ // Test 12: Multiple posts — first valid, second exceeds split
408
+ // =========================================================================
409
+ function test_mintFrom_batchWithOneExceedingSplit_reverts() public {
410
+ _configureCategoryWithSplits(5, 0, 1, 100, 500_000_000);
411
+ _setupMintMocks();
412
+
413
+ CTPost[] memory posts = new CTPost[](2);
414
+ posts[0] = CTPost({
415
+ encodedIPFSUri: keccak256("post-ok"),
416
+ totalSupply: 10,
417
+ price: 0.1 ether,
418
+ category: 5,
419
+ splitPercent: 250_000_000, // 25% OK
420
+ splits: new JBSplit[](0)
421
+ });
422
+ posts[1] = CTPost({
423
+ encodedIPFSUri: keccak256("post-bad"),
424
+ totalSupply: 10,
425
+ price: 0.1 ether,
426
+ category: 5,
427
+ splitPercent: 999_000_000, // 99.9% exceeds
428
+ splits: new JBSplit[](0)
429
+ });
430
+
431
+ vm.prank(poster);
432
+ vm.expectRevert(
433
+ abi.encodeWithSelector(
434
+ CTPublisher.CTPublisher_SplitPercentExceedsMaximum.selector, 999_000_000, 500_000_000
435
+ )
436
+ );
437
+ publisher.mintFrom{value: 0.4 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
438
+ }
439
+ }
@@ -0,0 +1,114 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.17;
3
+
4
+ import "@bananapus/721-hook-v5/script/helpers/Hook721DeploymentLib.sol";
5
+ import "@bananapus/core-v5/script/helpers/CoreDeploymentLib.sol";
6
+ import "@bananapus/suckers-v5/script/helpers/SuckerDeploymentLib.sol";
7
+
8
+ import "./../src/CTDeployer.sol";
9
+ import {JBConstants} from "@bananapus/core-v5/src/libraries/JBConstants.sol";
10
+ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v5/src/structs/JBSuckerDeployerConfig.sol";
11
+ import {JBTokenMapping} from "@bananapus/suckers-v5/src/structs/JBTokenMapping.sol";
12
+ import {CTProjectOwner} from "./../src/CTProjectOwner.sol";
13
+ import {CTPublisher} from "./../src/CTPublisher.sol";
14
+
15
+ import "forge-std/Test.sol";
16
+
17
+ contract ForkTest is Test {
18
+ /// @notice tracks the deployment of the core contracts for the chain we are deploying to.
19
+ CoreDeployment core;
20
+ /// @notice tracks the deployment of the 721 hook contracts for the chain we are deploying to.
21
+ Hook721Deployment hook;
22
+ /// @notice tracks the deployment of the sucker contracts for the chain we are deploying to.
23
+ SuckerDeployment suckers;
24
+
25
+ CTPublisher publisher;
26
+ CTDeployer deployer;
27
+
28
+ address TRUSTED_FORWARDER;
29
+
30
+ function setUp() public {
31
+ // Fork ETH mainnet.
32
+ vm.createSelectFork(vm.rpcUrl("ethereum"), 22_432_742);
33
+
34
+ // Get the deployment addresses for the nana CORE for this chain.
35
+ // We want to do this outside of the `sphinx` modifier.
36
+ core = CoreDeploymentLib.getDeployment(
37
+ vm.envOr("NANA_CORE_DEPLOYMENT_PATH", string("node_modules/@bananapus/core-v5/deployments/"))
38
+ );
39
+ // Get the deployment addresses for the 721 hook contracts for this chain.
40
+ hook = Hook721DeploymentLib.getDeployment(
41
+ vm.envOr("NANA_721_DEPLOYMENT_PATH", string("node_modules/@bananapus/721-hook-v5/deployments/"))
42
+ );
43
+ // Get the deployment addresses for the suckers contracts for this chain.
44
+ suckers = SuckerDeploymentLib.getDeployment(
45
+ vm.envOr("NANA_SUCKERS_DEPLOYMENT_PATH", string("node_modules/@bananapus/suckers-v5/deployments/"))
46
+ );
47
+
48
+ // We use the same trusted forwarder as the core deployment.
49
+ TRUSTED_FORWARDER = core.controller.trustedForwarder();
50
+
51
+ // Deploy the croptop contracts.
52
+ publisher = new CTPublisher(core.directory, core.permissions, 1, TRUSTED_FORWARDER);
53
+ deployer = new CTDeployer(
54
+ core.permissions, core.projects, hook.hook_deployer, publisher, suckers.registry, TRUSTED_FORWARDER
55
+ );
56
+ }
57
+
58
+ function testDeployProject(address owner) public {
59
+ vm.assume(owner != address(0) && owner.code.length == 0);
60
+
61
+ // Create the project config.
62
+ CTProjectConfig memory config = CTProjectConfig({
63
+ terminalConfigurations: new JBTerminalConfig[](0),
64
+ projectUri: "https://croptop.eth.sucks/",
65
+ allowedPosts: new CTDeployerAllowedPost[](0),
66
+ contractUri: "https://croptop.eth.sucks/",
67
+ name: "Croptop",
68
+ symbol: "CROP",
69
+ salt: bytes32(0)
70
+ });
71
+
72
+ CTSuckerDeploymentConfig memory suckerConfig =
73
+ CTSuckerDeploymentConfig({deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32(0)});
74
+
75
+ deployer.deployProjectFor(owner, config, suckerConfig, core.controller);
76
+ }
77
+
78
+ function testDeployProjectWithSuckers(address owner, bytes32 salt, bytes32 suckerSalt) public {
79
+ vm.assume(owner != address(0) && owner.code.length == 0);
80
+ vm.assume(suckerSalt != bytes32(0));
81
+
82
+ // Create the project config.
83
+ CTProjectConfig memory config = CTProjectConfig({
84
+ terminalConfigurations: new JBTerminalConfig[](0),
85
+ projectUri: "https://croptop.eth.sucks/",
86
+ allowedPosts: new CTDeployerAllowedPost[](0),
87
+ contractUri: "https://croptop.eth.sucks/",
88
+ name: "Croptop",
89
+ symbol: "CROP",
90
+ salt: salt
91
+ });
92
+
93
+ // Create the sucker config.
94
+ JBTokenMapping[] memory tokens = new JBTokenMapping[](1);
95
+ tokens[0] = JBTokenMapping({
96
+ localToken: address(JBConstants.NATIVE_TOKEN),
97
+ minGas: 200_000,
98
+ remoteToken: address(JBConstants.NATIVE_TOKEN),
99
+ minBridgeAmount: 0.001 ether
100
+ });
101
+
102
+ JBSuckerDeployerConfig[] memory deployerConfigurations = new JBSuckerDeployerConfig[](1);
103
+ deployerConfigurations[0] = JBSuckerDeployerConfig({deployer: suckers.optimismDeployer, mappings: tokens});
104
+
105
+ CTSuckerDeploymentConfig memory suckerConfig =
106
+ CTSuckerDeploymentConfig({deployerConfigurations: deployerConfigurations, salt: suckerSalt});
107
+
108
+ // Deploy the project.
109
+ (uint256 projectId,) = deployer.deployProjectFor(owner, config, suckerConfig, core.controller);
110
+
111
+ // Check that the projectId has a sucker.
112
+ assertEq(suckers.registry.suckersOf(projectId).length, deployerConfigurations.length);
113
+ }
114
+ }
@@ -0,0 +1,70 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.17;
3
+
4
+ import "forge-std/Test.sol";
5
+ import {JBMetadataResolver} from "@bananapus/core-v5/src/libraries/JBMetadataResolver.sol";
6
+
7
+ import {MetadataResolverHelper} from "@bananapus/core-v5/test/helpers/MetadataResolverHelper.sol";
8
+
9
+ /// @notice Quick test to assert the creation of metadata while minting
10
+ /// @dev This test is not meant to be exhaustive, but to ensure that the metadata is valid.
11
+ /// It uses a mock contract which only returns a metadata following the logic
12
+ /// of the CroptopPublisher contract during mint. This external contract is used to recreate the same
13
+ contract Test_MetadataGeneration_Unit is Test {
14
+ /// @notice Create a new metadata from the _additionalPayMetadata and the datahook metadata (containing the tiers to
15
+ /// mint).
16
+ /// @dev Naming follows CroptopPublisher contract.
17
+ function test_metadataBuilding() public {
18
+ MetadataResolverHelper _resolverHelper = new MetadataResolverHelper();
19
+
20
+ // The intial metadata passed to the terminal
21
+ bytes4[] memory _ids = new bytes4[](10);
22
+ bytes[] memory _datas = new bytes[](10);
23
+
24
+ for (uint256 _i; _i < _ids.length; _i++) {
25
+ _ids[_i] = bytes4(uint32(_i + 1 * 1000));
26
+ _datas[_i] = abi.encode(
27
+ bytes1(uint8(_i + 1)), uint32(69), bytes2(uint16(_i + 69)), bytes32(uint256(type(uint256).max))
28
+ );
29
+ }
30
+
31
+ bytes memory _additionalPayMetadata = _resolverHelper.createMetadata(_ids, _datas);
32
+
33
+ // The referal to include in the first 32 bytes of the metadata
34
+ uint256 FEE_PROJECT_ID = 420;
35
+
36
+ // The additional metadata to include
37
+ bytes4 datahookId = bytes4(bytes20(address(0xdeadbeef)));
38
+ uint256[] memory tierIdsToMint = new uint256[](9);
39
+
40
+ for (uint256 i = 0; i < 9; i++) {
41
+ tierIdsToMint[i] = i + 1;
42
+ }
43
+
44
+ // Test: create the new metadata:
45
+ bytes memory mintMetadata = JBMetadataResolver.addToMetadata({
46
+ originalMetadata: _additionalPayMetadata, idToAdd: datahookId, dataToAdd: abi.encode(true, tierIdsToMint)
47
+ });
48
+
49
+ // Add the referal id in the first 32 bytes
50
+ assembly {
51
+ mstore(add(mintMetadata, 32), FEE_PROJECT_ID)
52
+ }
53
+
54
+ bytes memory targetData;
55
+ bool found;
56
+
57
+ // Check: both data are present and correct?
58
+ for (uint256 i = 0; i < _ids.length; i++) {
59
+ (found, targetData) = JBMetadataResolver.getDataFor(_ids[i], mintMetadata);
60
+ assertTrue(found, "metadata not found");
61
+ assertEq(targetData, _datas[i], "metadata not equal");
62
+ }
63
+
64
+ (found, targetData) = JBMetadataResolver.getDataFor(datahookId, mintMetadata);
65
+ assertTrue(found, "datahook metadata not found");
66
+ assertEq(targetData, abi.encode(true, tierIdsToMint), "datahook not equal");
67
+
68
+ assertEq(uint256(bytes32(mintMetadata)), FEE_PROJECT_ID, "referal id not equal");
69
+ }
70
+ }