@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,395 +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 {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
8
- import {IJB721TiersHookDeployer} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookDeployer.sol";
9
- import {JBDeploy721TiersHookConfig} from "@bananapus/721-hook-v6/src/structs/JBDeploy721TiersHookConfig.sol";
10
- import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
11
- import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
12
- import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
13
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
14
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
15
- import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
16
- import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
17
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
18
- import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
19
- import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
20
- import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
21
- import {IJBSucker} from "@bananapus/suckers-v6/src/interfaces/IJBSucker.sol";
22
- import {IJBSuckerDeployer} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerDeployer.sol";
23
- import {IJBSuckerRegistry} from "@bananapus/suckers-v6/src/interfaces/IJBSuckerRegistry.sol";
24
- import {JBSuckerRegistry} from "@bananapus/suckers-v6/src/JBSuckerRegistry.sol";
25
- import {JBTokenMapping} from "@bananapus/suckers-v6/src/structs/JBTokenMapping.sol";
26
- import {JBDenominatedAmount} from "@bananapus/suckers-v6/src/structs/JBDenominatedAmount.sol";
27
- import {JBOutboxTree} from "@bananapus/suckers-v6/src/structs/JBOutboxTree.sol";
28
- import {JBClaim} from "@bananapus/suckers-v6/src/structs/JBClaim.sol";
29
- import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
30
- import {JBRemoteToken} from "@bananapus/suckers-v6/src/structs/JBRemoteToken.sol";
31
- import {JBSuckerState} from "@bananapus/suckers-v6/src/enums/JBSuckerState.sol";
32
- import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
33
-
34
- import {CTDeployer} from "../../src/CTDeployer.sol";
35
- import {CTPublisher} from "../../src/CTPublisher.sol";
36
- import {ICTPublisher} from "../../src/interfaces/ICTPublisher.sol";
37
- import {CTProjectConfig} from "../../src/structs/CTProjectConfig.sol";
38
- import {CTSuckerDeploymentConfig} from "../../src/structs/CTSuckerDeploymentConfig.sol";
39
- import {CTDeployerAllowedPost} from "../../src/structs/CTDeployerAllowedPost.sol";
40
-
41
- contract MockProjects {
42
- uint256 public countValue;
43
- mapping(uint256 => address) internal _ownerOf;
44
-
45
- function count() external view returns (uint256) {
46
- return countValue;
47
- }
48
-
49
- function ownerOf(uint256 projectId) external view returns (address) {
50
- return _ownerOf[projectId];
51
- }
52
-
53
- function mintFor(address owner, uint256 projectId) external {
54
- countValue = projectId;
55
- _ownerOf[projectId] = owner;
56
- }
57
-
58
- function transferFrom(address from, address to, uint256 tokenId) external {
59
- require(_ownerOf[tokenId] == from, "BAD_FROM");
60
- _ownerOf[tokenId] = to;
61
- }
62
- }
63
-
64
- contract MockDirectory is IJBDirectory {
65
- IJBProjects internal immutable _projects;
66
-
67
- constructor(IJBProjects projects_) {
68
- _projects = projects_;
69
- }
70
-
71
- function PROJECTS() external view returns (IJBProjects) {
72
- return _projects;
73
- }
74
-
75
- function controllerOf(uint256) external pure returns (IERC165) {
76
- revert("UNUSED");
77
- }
78
-
79
- function isAllowedToSetFirstController(address) external pure returns (bool) {
80
- revert("UNUSED");
81
- }
82
-
83
- function isTerminalOf(uint256, IJBTerminal) external pure returns (bool) {
84
- revert("UNUSED");
85
- }
86
-
87
- function primaryTerminalOf(uint256, address) external pure returns (IJBTerminal) {
88
- revert("UNUSED");
89
- }
90
-
91
- function setControllerOf(uint256, IERC165) external pure {
92
- revert("UNUSED");
93
- }
94
-
95
- function setIsAllowedToSetFirstController(address, bool) external pure {
96
- revert("UNUSED");
97
- }
98
-
99
- function setPrimaryTerminalOf(uint256, address, IJBTerminal) external pure {
100
- revert("UNUSED");
101
- }
102
-
103
- function setTerminalsOf(uint256, IJBTerminal[] calldata) external pure {
104
- revert("UNUSED");
105
- }
106
-
107
- function terminalsOf(uint256) external pure returns (IJBTerminal[] memory) {
108
- revert("UNUSED");
109
- }
110
- }
111
-
112
- contract MockController {
113
- MockProjects public immutable PROJECTS;
114
- uint256 public immutable nextProjectId;
115
-
116
- constructor(MockProjects projects_, uint256 nextProjectId_) {
117
- PROJECTS = projects_;
118
- nextProjectId = nextProjectId_;
119
- }
120
-
121
- function launchProjectFor(
122
- address owner,
123
- string calldata,
124
- JBRulesetConfig[] calldata,
125
- JBTerminalConfig[] calldata,
126
- string calldata
127
- )
128
- external
129
- returns (uint256)
130
- {
131
- PROJECTS.mintFor(owner, nextProjectId);
132
- return nextProjectId;
133
- }
134
- }
135
-
136
- contract MockHook {
137
- uint256 internal immutable _projectId;
138
-
139
- constructor(uint256 projectId_) {
140
- _projectId = projectId_;
141
- }
142
-
143
- function PROJECT_ID() external view returns (uint256) {
144
- return _projectId;
145
- }
146
- }
147
-
148
- contract MockHookDeployer {
149
- IJB721TiersHook public hook;
150
-
151
- function setHook(IJB721TiersHook hook_) external {
152
- hook = hook_;
153
- }
154
-
155
- function deployHookFor(
156
- uint256,
157
- JBDeploy721TiersHookConfig calldata,
158
- bytes32
159
- )
160
- external
161
- view
162
- returns (IJB721TiersHook)
163
- {
164
- return hook;
165
- }
166
- }
167
-
168
- contract RevertingSuckerRegistry {
169
- error DeploymentUnavailable();
170
-
171
- function deploySuckersFor(
172
- uint256,
173
- bytes32,
174
- JBSuckerDeployerConfig[] calldata
175
- )
176
- external
177
- pure
178
- returns (address[] memory)
179
- {
180
- revert DeploymentUnavailable();
181
- }
182
-
183
- function isSuckerOf(uint256, address) external pure returns (bool) {
184
- return false;
185
- }
186
- }
187
-
188
- contract PermissionedMockSucker is JBPermissioned {
189
- MockProjects internal immutable _projects;
190
- uint256 internal immutable _projectId;
191
- uint256 internal immutable _peerChainId;
192
-
193
- constructor(
194
- IJBPermissions permissions,
195
- MockProjects projects_,
196
- uint256 projectId_,
197
- uint256 peerChainId_
198
- )
199
- JBPermissioned(permissions)
200
- {
201
- _projects = projects_;
202
- _projectId = projectId_;
203
- _peerChainId = peerChainId_;
204
- }
205
-
206
- function peer() external pure returns (bytes32) {
207
- return bytes32(uint256(1));
208
- }
209
-
210
- function peerChainId() external view returns (uint256) {
211
- return _peerChainId;
212
- }
213
-
214
- function projectId() external view returns (uint256) {
215
- return _projectId;
216
- }
217
-
218
- function state() external pure returns (JBSuckerState) {
219
- return JBSuckerState.ENABLED;
220
- }
221
-
222
- function mapTokens(JBTokenMapping[] calldata) external payable {
223
- _requirePermissionFrom({
224
- account: _projects.ownerOf(_projectId),
225
- projectId: _projectId,
226
- permissionId: JBPermissionIds.MAP_SUCKER_TOKEN
227
- });
228
- }
229
-
230
- function outboxOf(address) external pure returns (JBOutboxTree memory) {
231
- revert("UNUSED");
232
- }
233
-
234
- function peerChainTotalSupply() external pure returns (uint256) {
235
- revert("UNUSED");
236
- }
237
-
238
- function peerChainBalanceOf(uint256, uint256) external pure returns (JBDenominatedAmount memory) {
239
- revert("UNUSED");
240
- }
241
-
242
- function peerChainSurplusOf(uint256, uint256) external pure returns (JBDenominatedAmount memory) {
243
- revert("UNUSED");
244
- }
245
-
246
- function remoteTokenFor(address) external pure returns (JBRemoteToken memory) {
247
- revert("UNUSED");
248
- }
249
-
250
- function claim(JBClaim[] calldata) external pure {
251
- revert("UNUSED");
252
- }
253
-
254
- function claim(JBClaim calldata) external pure {
255
- revert("UNUSED");
256
- }
257
-
258
- function mapToken(JBTokenMapping calldata) external payable {
259
- revert("UNUSED");
260
- }
261
-
262
- function prepare(uint256, bytes32, uint256, address) external payable {
263
- revert("UNUSED");
264
- }
265
-
266
- function toRemote(address, bytes calldata) external payable {
267
- revert("UNUSED");
268
- }
269
-
270
- function enableEmergencyHatchFor(address) external payable {
271
- revert("UNUSED");
272
- }
273
-
274
- function exitThroughEmergencyHatch(address, uint256, address payable) external {
275
- revert("UNUSED");
276
- }
277
-
278
- function setPeer(bytes32) external payable {
279
- revert("UNUSED");
280
- }
281
-
282
- function setPeerChainId(uint256) external payable {
283
- revert("UNUSED");
284
- }
285
-
286
- function setDeprecation(uint40) external payable {
287
- revert("UNUSED");
288
- }
289
- }
290
-
291
- contract MockSuckerDeployer {
292
- IJBPermissions internal immutable _permissions;
293
- MockProjects internal immutable _projects;
294
- uint256 internal immutable _peerChainId;
295
-
296
- constructor(IJBPermissions permissions_, MockProjects projects_, uint256 peerChainId_) {
297
- _permissions = permissions_;
298
- _projects = projects_;
299
- _peerChainId = peerChainId_;
300
- }
301
-
302
- function createForSender(uint256 localProjectId, bytes32) external returns (IJBSucker sucker) {
303
- sucker = IJBSucker(address(new PermissionedMockSucker(_permissions, _projects, localProjectId, _peerChainId)));
304
- }
305
- }
306
-
307
- contract CodexNemesisFreshRoundTest is Test {
308
- address internal owner = makeAddr("owner");
309
-
310
- function _emptyProjectConfig() internal pure returns (CTProjectConfig memory config) {
311
- config = CTProjectConfig({
312
- terminalConfigurations: new JBTerminalConfig[](0),
313
- projectUri: "ipfs://project",
314
- allowedPosts: new CTDeployerAllowedPost[](0),
315
- contractUri: "ipfs://contract",
316
- name: "Croptop",
317
- symbol: "CT",
318
- salt: bytes32(uint256(123))
319
- });
320
- }
321
-
322
- function test_deployProjectFor_revertsInsteadOfFailingOpenWhenSuckerDeploymentFails() public {
323
- JBPermissions permissions = new JBPermissions(address(0));
324
- MockProjects projects = new MockProjects();
325
- MockHookDeployer hookDeployer = new MockHookDeployer();
326
- MockHook hook = new MockHook(1);
327
- hookDeployer.setHook(IJB721TiersHook(address(hook)));
328
- MockController controller = new MockController(projects, 1);
329
- MockDirectory directory = new MockDirectory(IJBProjects(address(projects)));
330
- CTPublisher publisher = new CTPublisher(directory, permissions, 1, address(0));
331
- CTDeployer deployer = new CTDeployer(
332
- permissions,
333
- IJBProjects(address(projects)),
334
- IJB721TiersHookDeployer(address(hookDeployer)),
335
- ICTPublisher(address(publisher)),
336
- IJBSuckerRegistry(address(new RevertingSuckerRegistry())),
337
- address(0)
338
- );
339
-
340
- CTSuckerDeploymentConfig memory suckerConfig =
341
- CTSuckerDeploymentConfig({deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32("salt")});
342
-
343
- vm.expectRevert(RevertingSuckerRegistry.DeploymentUnavailable.selector);
344
- deployer.deployProjectFor(owner, _emptyProjectConfig(), suckerConfig, IJBController(address(controller)));
345
- }
346
-
347
- function test_directRegistryDeploymentAfterOwnershipTransferStillLacksMapPermission() public {
348
- JBPermissions permissions = new JBPermissions(address(0));
349
- MockProjects projects = new MockProjects();
350
- MockDirectory directory = new MockDirectory(IJBProjects(address(projects)));
351
- JBSuckerRegistry registry = new JBSuckerRegistry(directory, permissions, address(this), address(0));
352
- MockHookDeployer hookDeployer = new MockHookDeployer();
353
- MockHook hook = new MockHook(1);
354
- hookDeployer.setHook(IJB721TiersHook(address(hook)));
355
- MockController controller = new MockController(projects, 1);
356
- CTPublisher publisher = new CTPublisher(directory, permissions, 999, address(0));
357
- CTDeployer deployer = new CTDeployer(
358
- permissions,
359
- IJBProjects(address(projects)),
360
- IJB721TiersHookDeployer(address(hookDeployer)),
361
- ICTPublisher(address(publisher)),
362
- IJBSuckerRegistry(address(registry)),
363
- address(0)
364
- );
365
-
366
- address[] memory deployers = new address[](1);
367
- MockSuckerDeployer suckerDeployer = new MockSuckerDeployer(permissions, projects, 10);
368
- deployers[0] = address(suckerDeployer);
369
- registry.allowSuckerDeployers(deployers);
370
-
371
- CTSuckerDeploymentConfig memory emptySuckerConfig =
372
- CTSuckerDeploymentConfig({deployerConfigurations: new JBSuckerDeployerConfig[](0), salt: bytes32(0)});
373
- deployer.deployProjectFor(owner, _emptyProjectConfig(), emptySuckerConfig, IJBController(address(controller)));
374
-
375
- assertEq(projects.ownerOf(1), owner, "project ownership should leave CTDeployer after launch");
376
-
377
- JBSuckerDeployerConfig[] memory deployerConfigurations = new JBSuckerDeployerConfig[](1);
378
- JBTokenMapping[] memory mappings = new JBTokenMapping[](1);
379
- mappings[0] = JBTokenMapping({localToken: address(0xBEEF), minGas: 200_000, remoteToken: bytes32(uint256(1))});
380
- deployerConfigurations[0] =
381
- JBSuckerDeployerConfig({deployer: IJBSuckerDeployer(address(suckerDeployer)), mappings: mappings});
382
-
383
- vm.prank(owner);
384
- vm.expectRevert(
385
- abi.encodeWithSelector(
386
- JBPermissioned.JBPermissioned_Unauthorized.selector,
387
- owner,
388
- address(registry),
389
- 1,
390
- JBPermissionIds.MAP_SUCKER_TOKEN
391
- )
392
- );
393
- registry.deploySuckersFor(1, bytes32("later"), deployerConfigurations);
394
- }
395
- }
@@ -1,196 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
-
6
- import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
7
- import {IJB721TiersHookStore} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHookStore.sol";
8
- import {JB721Tier} from "@bananapus/721-hook-v6/src/structs/JB721Tier.sol";
9
- import {JB721TierConfig} from "@bananapus/721-hook-v6/src/structs/JB721TierConfig.sol";
10
- import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
11
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
12
- import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
13
- import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
14
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.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
- contract MetadataShadowPermissions is IJBPermissions {
22
- // forge-lint: disable-next-line(mixed-case-function)
23
- function WILDCARD_PROJECT_ID() external pure returns (uint256) {
24
- return 0;
25
- }
26
-
27
- function permissionsOf(address, address, uint256) external pure returns (uint256) {
28
- return 0;
29
- }
30
-
31
- function hasPermission(address, address, uint256, uint256, bool, bool) external pure returns (bool) {
32
- return true;
33
- }
34
-
35
- function hasPermissions(address, address, uint256, uint256[] calldata, bool, bool) external pure returns (bool) {
36
- return true;
37
- }
38
-
39
- function setPermissionsFor(address, JBPermissionsData calldata) external {}
40
- }
41
-
42
- contract MetadataShadowStore {
43
- function maxTierIdOf(address) external pure returns (uint256) {
44
- return 0;
45
- }
46
-
47
- function isTierRemoved(address, uint256) external pure returns (bool) {
48
- return false;
49
- }
50
-
51
- function tierOf(address, uint256, bool) external pure returns (JB721Tier memory tier) {
52
- return tier;
53
- }
54
- }
55
-
56
- contract MetadataShadowHook {
57
- uint256 public constant PROJECT_ID = 2;
58
- address public immutable METADATA_ID_TARGET;
59
- IJB721TiersHookStore public immutable STORE;
60
- address public immutable OWNER;
61
-
62
- constructor(IJB721TiersHookStore store_, address owner_, address metadataIdTarget_) {
63
- STORE = store_;
64
- OWNER = owner_;
65
- METADATA_ID_TARGET = metadataIdTarget_;
66
- }
67
-
68
- function adjustTiers(JB721TierConfig[] calldata, uint256[] calldata) external {}
69
-
70
- function owner() external view returns (address) {
71
- return OWNER;
72
- }
73
- }
74
-
75
- contract MetadataCapturingTerminal {
76
- address internal immutable METADATA_ID_TARGET;
77
- bool public found;
78
- bool public payerAllowsOverspending;
79
- uint256 public tierCount;
80
- uint16 public firstTierId;
81
- uint256 public totalReceived;
82
-
83
- constructor(address metadataIdTarget_) {
84
- METADATA_ID_TARGET = metadataIdTarget_;
85
- }
86
-
87
- function pay(
88
- uint256,
89
- address,
90
- uint256,
91
- address,
92
- uint256,
93
- string calldata,
94
- bytes calldata metadata
95
- )
96
- external
97
- payable
98
- returns (uint256)
99
- {
100
- totalReceived += msg.value;
101
- bytes4 id = JBMetadataResolver.getId({purpose: "pay", target: METADATA_ID_TARGET});
102
- bytes memory data;
103
- (found, data) = JBMetadataResolver.getDataFor({id: id, metadata: metadata});
104
- if (found) {
105
- uint16[] memory tierIds;
106
- (payerAllowsOverspending, tierIds) = abi.decode(data, (bool, uint16[]));
107
- tierCount = tierIds.length;
108
- if (tierIds.length != 0) firstTierId = tierIds[0];
109
- }
110
- return 0;
111
- }
112
- }
113
-
114
- contract MetadataNoopTerminal {
115
- function pay(
116
- uint256,
117
- address,
118
- uint256,
119
- address,
120
- uint256,
121
- string calldata,
122
- bytes calldata
123
- )
124
- external
125
- payable
126
- returns (uint256)
127
- {
128
- return 0;
129
- }
130
- }
131
-
132
- contract MetadataShadowDirectory {
133
- IJBTerminal public projectTerminal;
134
- IJBTerminal public feeTerminal;
135
-
136
- function setTerminals(IJBTerminal projectTerminal_, IJBTerminal feeTerminal_) external {
137
- projectTerminal = projectTerminal_;
138
- feeTerminal = feeTerminal_;
139
- }
140
-
141
- function primaryTerminalOf(uint256 projectId, address) external view returns (IJBTerminal) {
142
- return projectId == 1 ? feeTerminal : projectTerminal;
143
- }
144
- }
145
-
146
- contract CodexNemesisMetadataShadowTest is Test {
147
- function test_additionalPayMetadataCanShadowPublisherMintMetadata() public {
148
- address metadataIdTarget = address(0xBEEF);
149
- MetadataShadowPermissions permissions = new MetadataShadowPermissions();
150
- MetadataShadowDirectory directory = new MetadataShadowDirectory();
151
- MetadataShadowStore store = new MetadataShadowStore();
152
- MetadataShadowHook hook =
153
- new MetadataShadowHook(IJB721TiersHookStore(address(store)), address(this), metadataIdTarget);
154
- MetadataCapturingTerminal projectTerminal = new MetadataCapturingTerminal(metadataIdTarget);
155
- MetadataNoopTerminal feeTerminal = new MetadataNoopTerminal();
156
- directory.setTerminals(IJBTerminal(address(projectTerminal)), IJBTerminal(address(feeTerminal)));
157
-
158
- CTPublisher publisher = new CTPublisher(IJBDirectory(address(directory)), permissions, 1, address(0));
159
-
160
- CTAllowedPost[] memory allowedPosts = new CTAllowedPost[](1);
161
- allowedPosts[0] = CTAllowedPost({
162
- hook: address(hook),
163
- category: 1,
164
- minimumPrice: 100,
165
- minimumTotalSupply: 1,
166
- maximumTotalSupply: type(uint32).max,
167
- maximumSplitPercent: 0,
168
- allowedAddresses: new address[](0)
169
- });
170
- publisher.configurePostingCriteriaFor(allowedPosts);
171
-
172
- CTPost[] memory posts = new CTPost[](1);
173
- posts[0] = CTPost({
174
- encodedIPFSUri: keccak256("publisher-validated-post"),
175
- totalSupply: 1,
176
- price: 100,
177
- category: 1,
178
- splitPercent: 0,
179
- splits: new JBSplit[](0)
180
- });
181
-
182
- uint16[] memory forgedTierIds = new uint16[](1);
183
- forgedTierIds[0] = 2;
184
- bytes4[] memory ids = new bytes4[](1);
185
- bytes[] memory datas = new bytes[](1);
186
- ids[0] = JBMetadataResolver.getId({purpose: "pay", target: metadataIdTarget});
187
- datas[0] = abi.encode(true, forgedTierIds);
188
- bytes memory shadowingMetadata = JBMetadataResolver.createMetadata(ids, datas);
189
-
190
- // H-26 FIX: metadata shadow attack now reverts instead of succeeding.
191
- vm.expectRevert(CTPublisher.CTPublisher_DuplicatePayMetadata.selector);
192
- publisher.mintFrom{value: 105}(
193
- IJB721TiersHook(address(hook)), posts, address(this), address(this), shadowingMetadata, ""
194
- );
195
- }
196
- }