@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,437 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- // forge-lint: disable-next-line(unaliased-plain-import)
5
- import "forge-std/Test.sol";
6
-
7
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
8
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
9
- import {IJBSplitHook} from "@bananapus/core-v6/src/interfaces/IJBSplitHook.sol";
10
- import {IJBOwnable} from "@bananapus/ownable-v6/src/interfaces/IJBOwnable.sol";
11
- import {IJB721Hook} from "@bananapus/721-hook-v6/src/interfaces/IJB721Hook.sol";
12
- import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
13
- import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
14
- import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
15
- import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
16
-
17
- import {CTPublisher} from "../src/CTPublisher.sol";
18
- import {CTAllowedPost} from "../src/structs/CTAllowedPost.sol";
19
- import {CTPost} from "../src/structs/CTPost.sol";
20
-
21
- /// @title CroptopAttacks
22
- /// @notice Adversarial security tests for CTPublisher focusing on mintFrom edge cases,
23
- /// allowlist bypasses, input validation, and split percent enforcement.
24
- contract CroptopAttacks is Test {
25
- CTPublisher publisher;
26
-
27
- IJBPermissions permissions = IJBPermissions(makeAddr("permissions"));
28
- IJBDirectory directory = IJBDirectory(makeAddr("directory"));
29
-
30
- address hookOwner = makeAddr("hookOwner");
31
- address hookAddr = makeAddr("hook");
32
- address hookStoreAddr = makeAddr("hookStore");
33
- address terminalAddr = makeAddr("terminal");
34
- address poster = makeAddr("poster");
35
- address unauthorized = makeAddr("unauthorized");
36
-
37
- uint256 feeProjectId = 1;
38
- uint256 hookProjectId = 42;
39
-
40
- function setUp() public {
41
- publisher = new CTPublisher(directory, permissions, feeProjectId, address(0));
42
-
43
- // Mock hook.owner().
44
- vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.owner.selector), abi.encode(hookOwner));
45
- // Mock hook.PROJECT_ID().
46
- vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721Hook.PROJECT_ID.selector), abi.encode(hookProjectId));
47
- // Mock hook.STORE().
48
- vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721TiersHook.STORE.selector), abi.encode(hookStoreAddr));
49
-
50
- // Mock permissions to return true by default.
51
- vm.mockCall(
52
- address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(true)
53
- );
54
-
55
- // Fund test accounts so they can send ETH with mintFrom.
56
- vm.deal(poster, 100 ether);
57
- vm.deal(unauthorized, 100 ether);
58
- }
59
-
60
- /// @dev Configure a standard category for testing.
61
- function _configureCategory(uint24 category, uint104 minPrice, uint32 minSupply, uint32 maxSupply) internal {
62
- CTAllowedPost[] memory posts = new CTAllowedPost[](1);
63
- posts[0] = CTAllowedPost({
64
- hook: hookAddr,
65
- category: category,
66
- minimumPrice: minPrice,
67
- minimumTotalSupply: minSupply,
68
- maximumTotalSupply: maxSupply,
69
- maximumSplitPercent: 0,
70
- allowedAddresses: new address[](0)
71
- });
72
-
73
- vm.prank(hookOwner);
74
- publisher.configurePostingCriteriaFor(posts);
75
- }
76
-
77
- /// @dev Configure a category with an allowlist.
78
- function _configureCategoryWithAllowlist(uint24 category, address[] memory allowed) internal {
79
- CTAllowedPost[] memory posts = new CTAllowedPost[](1);
80
- posts[0] = CTAllowedPost({
81
- hook: hookAddr,
82
- category: category,
83
- minimumPrice: 0,
84
- minimumTotalSupply: 1,
85
- maximumTotalSupply: 100,
86
- maximumSplitPercent: 0,
87
- allowedAddresses: allowed
88
- });
89
-
90
- vm.prank(hookOwner);
91
- publisher.configurePostingCriteriaFor(posts);
92
- }
93
-
94
- /// @dev Configure a category with a maximum split percent.
95
- function _configureCategoryWithSplits(
96
- uint24 category,
97
- uint104 minPrice,
98
- uint32 minSupply,
99
- uint32 maxSupply,
100
- uint32 maxSplitPercent
101
- )
102
- internal
103
- {
104
- CTAllowedPost[] memory posts = new CTAllowedPost[](1);
105
- posts[0] = CTAllowedPost({
106
- hook: hookAddr,
107
- category: category,
108
- minimumPrice: minPrice,
109
- minimumTotalSupply: minSupply,
110
- maximumTotalSupply: maxSupply,
111
- maximumSplitPercent: maxSplitPercent,
112
- allowedAddresses: new address[](0)
113
- });
114
-
115
- vm.prank(hookOwner);
116
- publisher.configurePostingCriteriaFor(posts);
117
- }
118
-
119
- /// @dev Set up mocks for mintFrom path.
120
- function _setupMintMocks() internal {
121
- vm.mockCall(
122
- hookStoreAddr, abi.encodeWithSelector(IJB721TiersHookStore.maxTierIdOf.selector), abi.encode(uint256(0))
123
- );
124
- vm.mockCall(hookAddr, abi.encodeWithSelector(IJB721TiersHook.adjustTiers.selector), abi.encode());
125
- // METADATA_ID_TARGET() selector.
126
- vm.mockCall(hookAddr, abi.encodeWithSelector(bytes4(keccak256("METADATA_ID_TARGET()"))), abi.encode(address(0)));
127
- vm.mockCall(
128
- address(directory),
129
- abi.encodeWithSelector(IJBDirectory.primaryTerminalOf.selector),
130
- abi.encode(terminalAddr)
131
- );
132
- vm.mockCall(terminalAddr, "", abi.encode(uint256(0)));
133
- }
134
-
135
- // =========================================================================
136
- // Test 1: Post to unconfigured category — should revert
137
- // =========================================================================
138
- function test_mintFrom_unconfiguredCategory_reverts() public {
139
- _setupMintMocks();
140
-
141
- CTPost[] memory posts = new CTPost[](1);
142
- posts[0] = CTPost({
143
- encodedIPFSUri: keccak256("test-content"),
144
- totalSupply: 10,
145
- price: 0.1 ether,
146
- category: 999,
147
- splitPercent: 0,
148
- splits: new JBSplit[](0)
149
- });
150
-
151
- vm.prank(poster);
152
- vm.expectRevert();
153
- publisher.mintFrom{value: 0.1 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
154
- }
155
-
156
- // =========================================================================
157
- // Test 2: Post with price below minimum — should revert
158
- // =========================================================================
159
- function test_mintFrom_belowMinPrice_reverts() public {
160
- _configureCategory(5, 0.1 ether, 1, 100);
161
- _setupMintMocks();
162
-
163
- CTPost[] memory posts = new CTPost[](1);
164
- posts[0] = CTPost({
165
- encodedIPFSUri: keccak256("cheap-content"),
166
- totalSupply: 10,
167
- price: 0.01 ether,
168
- category: 5,
169
- splitPercent: 0,
170
- splits: new JBSplit[](0)
171
- });
172
-
173
- vm.prank(poster);
174
- vm.expectRevert();
175
- publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
176
- }
177
-
178
- // =========================================================================
179
- // Test 3: Post with supply above maximum — should revert
180
- // =========================================================================
181
- function test_mintFrom_exceedsMaxSupply_reverts() public {
182
- _configureCategory(5, 0, 1, 50);
183
- _setupMintMocks();
184
-
185
- CTPost[] memory posts = new CTPost[](1);
186
- posts[0] = CTPost({
187
- encodedIPFSUri: keccak256("big-supply"),
188
- totalSupply: 100,
189
- price: 0.01 ether,
190
- category: 5,
191
- splitPercent: 0,
192
- splits: new JBSplit[](0)
193
- });
194
-
195
- vm.prank(poster);
196
- vm.expectRevert();
197
- publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
198
- }
199
-
200
- // =========================================================================
201
- // Test 4: Post with supply below minimum — should revert
202
- // =========================================================================
203
- function test_mintFrom_belowMinSupply_reverts() public {
204
- _configureCategory(5, 0, 10, 100);
205
- _setupMintMocks();
206
-
207
- CTPost[] memory posts = new CTPost[](1);
208
- posts[0] = CTPost({
209
- encodedIPFSUri: keccak256("small-supply"),
210
- totalSupply: 5,
211
- price: 0.01 ether,
212
- category: 5,
213
- splitPercent: 0,
214
- splits: new JBSplit[](0)
215
- });
216
-
217
- vm.prank(poster);
218
- vm.expectRevert();
219
- publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
220
- }
221
-
222
- // =========================================================================
223
- // Test 5: Allowlist bypass — non-allowed address posts to restricted category
224
- // =========================================================================
225
- function test_mintFrom_allowlistBypass_reverts() public {
226
- address[] memory allowed = new address[](1);
227
- allowed[0] = poster;
228
-
229
- _configureCategoryWithAllowlist(7, allowed);
230
- _setupMintMocks();
231
-
232
- CTPost[] memory posts = new CTPost[](1);
233
- posts[0] = CTPost({
234
- encodedIPFSUri: keccak256("sneaky-content"),
235
- totalSupply: 10,
236
- price: 0.01 ether,
237
- category: 7,
238
- splitPercent: 0,
239
- splits: new JBSplit[](0)
240
- });
241
-
242
- vm.prank(unauthorized);
243
- vm.expectRevert();
244
- publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, unauthorized, unauthorized, "", "");
245
- }
246
-
247
- // =========================================================================
248
- // Test 6: Zero IPFS URI — should revert
249
- // =========================================================================
250
- function test_mintFrom_zeroIPFSUri_reverts() public {
251
- _configureCategory(5, 0, 1, 100);
252
- _setupMintMocks();
253
-
254
- CTPost[] memory posts = new CTPost[](1);
255
- posts[0] = CTPost({
256
- encodedIPFSUri: bytes32(0),
257
- totalSupply: 10,
258
- price: 0.01 ether,
259
- category: 5,
260
- splitPercent: 0,
261
- splits: new JBSplit[](0)
262
- });
263
-
264
- vm.prank(poster);
265
- vm.expectRevert();
266
- publisher.mintFrom{value: 0.01 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
267
- }
268
-
269
- // =========================================================================
270
- // Test 7: Configure with zero minSupply reverts
271
- // =========================================================================
272
- function test_configure_zeroMinSupply_reverts() public {
273
- CTAllowedPost[] memory posts = new CTAllowedPost[](1);
274
- posts[0] = CTAllowedPost({
275
- hook: hookAddr,
276
- category: 5,
277
- minimumPrice: 0,
278
- minimumTotalSupply: 0,
279
- maximumTotalSupply: 100,
280
- maximumSplitPercent: 0,
281
- allowedAddresses: new address[](0)
282
- });
283
-
284
- vm.prank(hookOwner);
285
- vm.expectRevert(CTPublisher.CTPublisher_ZeroTotalSupply.selector);
286
- publisher.configurePostingCriteriaFor(posts);
287
- }
288
-
289
- // =========================================================================
290
- // Test 8: Configure without permission — should revert
291
- // =========================================================================
292
- function test_configure_noPermission_reverts() public {
293
- vm.mockCall(
294
- address(permissions), abi.encodeWithSelector(IJBPermissions.hasPermission.selector), abi.encode(false)
295
- );
296
-
297
- CTAllowedPost[] memory posts = new CTAllowedPost[](1);
298
- posts[0] = CTAllowedPost({
299
- hook: hookAddr,
300
- category: 1,
301
- minimumPrice: 0,
302
- minimumTotalSupply: 1,
303
- maximumTotalSupply: 100,
304
- maximumSplitPercent: 0,
305
- allowedAddresses: new address[](0)
306
- });
307
-
308
- vm.prank(unauthorized);
309
- vm.expectRevert();
310
- publisher.configurePostingCriteriaFor(posts);
311
- }
312
-
313
- // =========================================================================
314
- // Test 9: Split percent exceeds maximum — should revert
315
- // =========================================================================
316
- function test_mintFrom_splitPercentExceedsMaximum_reverts() public {
317
- _configureCategoryWithSplits(5, 0, 1, 100, 500_000_000); // 50% max
318
- _setupMintMocks();
319
-
320
- CTPost[] memory posts = new CTPost[](1);
321
- posts[0] = CTPost({
322
- encodedIPFSUri: keccak256("greedy-split"),
323
- totalSupply: 10,
324
- price: 0.1 ether,
325
- category: 5,
326
- splitPercent: 750_000_000, // 75% exceeds 50%
327
- splits: new JBSplit[](0)
328
- });
329
-
330
- vm.prank(poster);
331
- vm.expectRevert(
332
- abi.encodeWithSelector(
333
- CTPublisher.CTPublisher_SplitPercentExceedsMaximum.selector, 750_000_000, 500_000_000
334
- )
335
- );
336
- publisher.mintFrom{value: 0.2 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
337
- }
338
-
339
- // =========================================================================
340
- // Test 10: Split percent when splits disabled (max=0) — should revert
341
- // =========================================================================
342
- function test_mintFrom_splitPercentWhenDisabled_reverts() public {
343
- _configureCategoryWithSplits(5, 0, 1, 100, 0); // Splits disabled
344
- _setupMintMocks();
345
-
346
- JBSplit[] memory splits = new JBSplit[](1);
347
- splits[0] = JBSplit({
348
- percent: 100_000_000,
349
- projectId: 0,
350
- beneficiary: payable(poster),
351
- preferAddToBalance: false,
352
- lockedUntil: 0,
353
- hook: IJBSplitHook(address(0))
354
- });
355
-
356
- CTPost[] memory posts = new CTPost[](1);
357
- posts[0] = CTPost({
358
- encodedIPFSUri: keccak256("sneaky-split"),
359
- totalSupply: 10,
360
- price: 0.1 ether,
361
- category: 5,
362
- splitPercent: 100_000_000, // Any amount when disabled
363
- splits: splits
364
- });
365
-
366
- vm.prank(poster);
367
- vm.expectRevert(
368
- abi.encodeWithSelector(CTPublisher.CTPublisher_SplitPercentExceedsMaximum.selector, 100_000_000, 0)
369
- );
370
- publisher.mintFrom{value: 0.2 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
371
- }
372
-
373
- // =========================================================================
374
- // Test 11: Attacker re-configures to raise split percent
375
- // =========================================================================
376
- function test_reconfigure_raiseSplitPercent_requiresPermission() public {
377
- // Owner configures with 50% max split.
378
- _configureCategoryWithSplits(5, 0, 1, 100, 500_000_000);
379
-
380
- // Mock permissions to return false for unauthorized.
381
- vm.mockCall(
382
- address(permissions),
383
- abi.encodeWithSelector(IJBPermissions.hasPermission.selector, unauthorized),
384
- abi.encode(false)
385
- );
386
-
387
- // Attacker tries to reconfigure with 100% max split.
388
- CTAllowedPost[] memory posts = new CTAllowedPost[](1);
389
- posts[0] = CTAllowedPost({
390
- hook: hookAddr,
391
- category: 5,
392
- minimumPrice: 0,
393
- minimumTotalSupply: 1,
394
- maximumTotalSupply: 100,
395
- maximumSplitPercent: uint32(JBConstants.SPLITS_TOTAL_PERCENT),
396
- allowedAddresses: new address[](0)
397
- });
398
-
399
- vm.prank(unauthorized);
400
- vm.expectRevert();
401
- publisher.configurePostingCriteriaFor(posts);
402
- }
403
-
404
- // =========================================================================
405
- // Test 12: Multiple posts — first valid, second exceeds split
406
- // =========================================================================
407
- function test_mintFrom_batchWithOneExceedingSplit_reverts() public {
408
- _configureCategoryWithSplits(5, 0, 1, 100, 500_000_000);
409
- _setupMintMocks();
410
-
411
- CTPost[] memory posts = new CTPost[](2);
412
- posts[0] = CTPost({
413
- encodedIPFSUri: keccak256("post-ok"),
414
- totalSupply: 10,
415
- price: 0.1 ether,
416
- category: 5,
417
- splitPercent: 250_000_000, // 25% OK
418
- splits: new JBSplit[](0)
419
- });
420
- posts[1] = CTPost({
421
- encodedIPFSUri: keccak256("post-bad"),
422
- totalSupply: 10,
423
- price: 0.1 ether,
424
- category: 5,
425
- splitPercent: 999_000_000, // 99.9% exceeds
426
- splits: new JBSplit[](0)
427
- });
428
-
429
- vm.prank(poster);
430
- vm.expectRevert(
431
- abi.encodeWithSelector(
432
- CTPublisher.CTPublisher_SplitPercentExceedsMaximum.selector, 999_000_000, 500_000_000
433
- )
434
- );
435
- publisher.mintFrom{value: 0.4 ether}(IJB721TiersHook(hookAddr), posts, poster, poster, "", "");
436
- }
437
- }
package/test/Fork.t.sol DELETED
@@ -1,227 +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 {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
19
-
20
- // 721 hook — deploy fresh within fork.
21
- import {JB721TiersHookStore} from "@bananapus/721-hook-v6/src/JB721TiersHookStore.sol";
22
- import {JB721TiersHook} from "@bananapus/721-hook-v6/src/JB721TiersHook.sol";
23
- import {JB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/JB721TiersHookDeployer.sol";
24
- import {JB721CheckpointsDeployer} from "@bananapus/721-hook-v6/src/JB721CheckpointsDeployer.sol";
25
- import {JBAddressRegistry} from "@bananapus/address-registry-v6/src/JBAddressRegistry.sol";
26
-
27
- // Suckers — deploy fresh within fork.
28
- import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
29
- import {JBOptimismSuckerDeployer} from "@bananapus/suckers-v6/src/deployers/JBOptimismSuckerDeployer.sol";
30
- import {JBOptimismSucker} from "@bananapus/suckers-v6/src/JBOptimismSucker.sol";
31
-
32
- import {IOPMessenger} from "@bananapus/suckers-v6/src/interfaces/IOPMessenger.sol";
33
- import {IOPStandardBridge} from "@bananapus/suckers-v6/src/interfaces/IOPStandardBridge.sol";
34
- import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
35
- import {JBTokenMapping} from "@bananapus/suckers-v6/src/structs/JBTokenMapping.sol";
36
- import {IJBSuckerDeployer} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerDeployer.sol";
37
-
38
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
39
-
40
- // Croptop — wildcard import pulls in all structs (CTProjectConfig, CTDeployerAllowedPost, etc.).
41
- // forge-lint: disable-next-line(unaliased-plain-import)
42
- import "./../src/CTDeployer.sol";
43
- import {CTPublisher} from "./../src/CTPublisher.sol";
44
-
45
- /// @notice Fork tests for Croptop. Deploys all JB infrastructure fresh within a mainnet fork.
46
- contract ForkTest is Test {
47
- // ───────────────────────── Mainnet addresses
48
- // ──────────────────────────
49
-
50
- // OP L1 bridge contracts (exist on Ethereum mainnet).
51
- IOPMessenger constant OP_L1_MESSENGER = IOPMessenger(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1);
52
- IOPStandardBridge constant OP_L1_BRIDGE = IOPStandardBridge(0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1);
53
-
54
- // ───────────────────────── JB core (deployed fresh)
55
- // ───────────────────
56
-
57
- address multisig = address(0xBEEF);
58
- address trustedForwarder = address(0);
59
-
60
- JBPermissions jbPermissions;
61
- JBProjects jbProjects;
62
- JBDirectory jbDirectory;
63
- JBRulesets jbRulesets;
64
- JBTokens jbTokens;
65
- JBSplits jbSplits;
66
- JBPrices jbPrices;
67
- JBFundAccessLimits jbFundAccessLimits;
68
- JBController jbController;
69
-
70
- // ───────────────────────── 721 hook (deployed fresh)
71
- // ──────────────────
72
-
73
- JB721TiersHookDeployer hookDeployer;
74
-
75
- // ───────────────────────── Suckers (deployed fresh)
76
- // ───────────────────
77
-
78
- JBSuckerRegistry suckerRegistry;
79
- JBOptimismSuckerDeployer opSuckerDeployer;
80
-
81
- // ───────────────────────── Croptop
82
- // ────────────────────────────────────
83
-
84
- CTPublisher publisher;
85
- CTDeployer deployer;
86
-
87
- function setUp() public {
88
- // Fork ETH mainnet.
89
- vm.createSelectFork("ethereum");
90
-
91
- // Deploy all JB core contracts fresh within the fork.
92
- _deployJBCore();
93
-
94
- // Deploy the 721 hook infrastructure.
95
- _deploy721Hook();
96
-
97
- // Deploy the sucker infrastructure.
98
- _deploySuckers();
99
-
100
- // Deploy the croptop contracts.
101
- publisher = new CTPublisher(jbDirectory, jbPermissions, 1, trustedForwarder);
102
- deployer = new CTDeployer(jbPermissions, jbProjects, hookDeployer, publisher, suckerRegistry, trustedForwarder);
103
- }
104
-
105
- function testDeployProject(address owner) public {
106
- vm.assume(owner != address(0) && owner.code.length == 0);
107
-
108
- // Create the project config.
109
- CTProjectConfig memory config = CTProjectConfig({
110
- terminalConfigurations: new JBTerminalConfig[](0),
111
- projectUri: "https://croptop.eth.sucks/",
112
- allowedPosts: new CTDeployerAllowedPost[](0),
113
- contractUri: "https://croptop.eth.sucks/",
114
- name: "Croptop",
115
- symbol: "CROP",
116
- salt: bytes32(0)
117
- });
118
-
119
- CTSuckerDeploymentConfig memory suckerConfig =
120
- CTSuckerDeploymentConfig({deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32(0)});
121
-
122
- deployer.deployProjectFor(owner, config, suckerConfig, jbController);
123
- }
124
-
125
- function testDeployProjectWithSuckers(address owner, bytes32 salt, bytes32 suckerSalt) public {
126
- vm.assume(owner != address(0) && owner.code.length == 0);
127
- vm.assume(suckerSalt != bytes32(0));
128
-
129
- // Create the project config.
130
- CTProjectConfig memory config = CTProjectConfig({
131
- terminalConfigurations: new JBTerminalConfig[](0),
132
- projectUri: "https://croptop.eth.sucks/",
133
- allowedPosts: new CTDeployerAllowedPost[](0),
134
- contractUri: "https://croptop.eth.sucks/",
135
- name: "Croptop",
136
- symbol: "CROP",
137
- salt: salt
138
- });
139
-
140
- // Create the sucker config.
141
- JBTokenMapping[] memory tokens = new JBTokenMapping[](1);
142
- tokens[0] = JBTokenMapping({
143
- localToken: address(JBConstants.NATIVE_TOKEN),
144
- minGas: 200_000,
145
- remoteToken: bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN)))
146
- });
147
-
148
- JBSuckerDeployerConfig[] memory deployerConfigurations = new JBSuckerDeployerConfig[](1);
149
- deployerConfigurations[0] =
150
- JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(opSuckerDeployer)), mappings: tokens});
151
-
152
- CTSuckerDeploymentConfig memory suckerConfig =
153
- CTSuckerDeploymentConfig({deployerConfigurations: deployerConfigurations, salt: suckerSalt});
154
-
155
- // Deploy the project.
156
- (uint256 projectId,) = deployer.deployProjectFor(owner, config, suckerConfig, jbController);
157
-
158
- // Check that the projectId has a sucker.
159
- assertEq(suckerRegistry.suckersOf(projectId).length, deployerConfigurations.length);
160
- }
161
-
162
- // ───────────────────────── Internal deployment helpers
163
- // ────────────────
164
-
165
- // forge-lint: disable-next-line(mixed-case-function)
166
- function _deployJBCore() internal {
167
- jbPermissions = new JBPermissions(trustedForwarder);
168
- jbProjects = new JBProjects(multisig, address(0), trustedForwarder);
169
- jbDirectory = new JBDirectory(jbPermissions, jbProjects, multisig);
170
- JBERC20 jbErc20 = new JBERC20(jbPermissions, jbProjects);
171
- jbTokens = new JBTokens(jbDirectory, jbErc20);
172
- jbRulesets = new JBRulesets(jbDirectory);
173
- jbPrices = new JBPrices(jbDirectory, jbPermissions, jbProjects, multisig, trustedForwarder);
174
- jbSplits = new JBSplits(jbDirectory);
175
- jbFundAccessLimits = new JBFundAccessLimits(jbDirectory);
176
-
177
- jbController = new JBController(
178
- jbDirectory,
179
- jbFundAccessLimits,
180
- jbPermissions,
181
- jbPrices,
182
- jbProjects,
183
- jbRulesets,
184
- jbSplits,
185
- jbTokens,
186
- address(0), // omnichainRulesetOperator
187
- trustedForwarder
188
- );
189
-
190
- vm.prank(multisig);
191
- jbDirectory.setIsAllowedToSetFirstController(address(jbController), true);
192
- }
193
-
194
- function _deploy721Hook() internal {
195
- JB721TiersHookStore store = new JB721TiersHookStore();
196
- JBAddressRegistry addressRegistry = new JBAddressRegistry();
197
- JB721CheckpointsDeployer checkpointsDeployer = new JB721CheckpointsDeployer();
198
-
199
- JB721TiersHook hookImpl = new JB721TiersHook(
200
- jbDirectory, jbPermissions, jbPrices, jbRulesets, store, jbSplits, checkpointsDeployer, trustedForwarder
201
- );
202
-
203
- hookDeployer = new JB721TiersHookDeployer(hookImpl, store, addressRegistry, trustedForwarder);
204
- }
205
-
206
- function _deploySuckers() internal {
207
- suckerRegistry = new JBSuckerRegistry(jbDirectory, jbPermissions, multisig, trustedForwarder);
208
-
209
- // Deploy the OP sucker deployer with `multisig` as the configurator.
210
- opSuckerDeployer =
211
- new JBOptimismSuckerDeployer(jbDirectory, jbPermissions, jbTokens, multisig, trustedForwarder);
212
-
213
- // Configure the OP sucker deployer with L1 bridge addresses.
214
- vm.startPrank(multisig);
215
- opSuckerDeployer.setChainSpecificConstants(OP_L1_MESSENGER, OP_L1_BRIDGE);
216
-
217
- // Deploy and configure the singleton.
218
- JBOptimismSucker singleton = new JBOptimismSucker(
219
- opSuckerDeployer, jbDirectory, jbPermissions, jbTokens, 1, suckerRegistry, trustedForwarder
220
- );
221
- opSuckerDeployer.configureSingleton(singleton);
222
-
223
- // Allow the deployer in the registry.
224
- suckerRegistry.allowSuckerDeployer(address(opSuckerDeployer));
225
- vm.stopPrank();
226
- }
227
- }