@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.
- package/README.md +2 -2
- package/foundry.toml +2 -1
- package/package.json +25 -13
- package/script/ConfigureFeeProject.s.sol +8 -5
- package/src/CTDeployer.sol +52 -51
- package/src/interfaces/ICTDeployer.sol +2 -2
- package/ADMINISTRATION.md +0 -94
- package/ARCHITECTURE.md +0 -96
- package/AUDIT_INSTRUCTIONS.md +0 -88
- package/RISKS.md +0 -78
- package/SKILLS.md +0 -46
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -134
- package/foundry.lock +0 -11
- package/slither-ci.config.json +0 -10
- package/sphinx.lock +0 -507
- package/test/CTDeployer.t.sol +0 -616
- package/test/CTProjectOwner.t.sol +0 -185
- package/test/CTPublisher.t.sol +0 -869
- package/test/ClaimCollectionOwnership.t.sol +0 -315
- package/test/CroptopAttacks.t.sol +0 -437
- package/test/Fork.t.sol +0 -227
- package/test/TestAuditGaps.sol +0 -696
- package/test/Test_MetadataGeneration.t.sol +0 -79
- package/test/audit/CodexNemesisCroptopPublisherBoundary.t.sol +0 -329
- package/test/audit/CodexNemesisCurrencyPoCs.t.sol +0 -371
- package/test/audit/CodexNemesisFreshRound.t.sol +0 -395
- package/test/audit/CodexNemesisMetadataShadow.t.sol +0 -196
- package/test/audit/CodexNemesisPoCs.t.sol +0 -263
- package/test/audit/CodexNemesisPolicyReuse.t.sol +0 -168
- package/test/audit/CodexNemesisUriDrift.t.sol +0 -252
- package/test/audit/DeployerPermissionBypass.t.sol +0 -213
- package/test/audit/EmptyPostFeeBypass.t.sol +0 -53
- package/test/audit/FeeBeneficiaryReentrancy.t.sol +0 -247
- package/test/audit/FeeFallbackBlackhole.t.sol +0 -263
- package/test/audit/Pass12Fixes.t.sol +0 -388
- package/test/fork/PublishFork.t.sol +0 -440
- package/test/regression/DuplicateUriFeeEvasion.t.sol +0 -312
- package/test/regression/FeeEvasion.t.sol +0 -286
- 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
|
-
}
|