@croptop/core-v6 0.0.37 → 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/CTPublisher.sol +20 -5
- 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 -203
- 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/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 -218
package/README.md
CHANGED
package/foundry.toml
CHANGED
|
@@ -17,7 +17,8 @@ fail_on_revert = false
|
|
|
17
17
|
ethereum = "${RPC_ETHEREUM_MAINNET}"
|
|
18
18
|
|
|
19
19
|
[lint]
|
|
20
|
-
exclude_lints = ["
|
|
20
|
+
exclude_lints = ["mixed-case-variable", "pascal-case-struct"]
|
|
21
|
+
|
|
21
22
|
[fmt]
|
|
22
23
|
number_underscore = "thousands"
|
|
23
24
|
multiline_func_header = "all"
|
package/package.json
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@croptop/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.39",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/mejango/croptop-core-v6"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"foundry.toml",
|
|
12
|
+
"references/",
|
|
13
|
+
"remappings.txt",
|
|
14
|
+
"script/ConfigureFeeProject.s.sol",
|
|
15
|
+
"script/Deploy.s.sol",
|
|
16
|
+
"script/helpers/",
|
|
17
|
+
"src/"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20.0.0"
|
|
21
|
+
},
|
|
9
22
|
"scripts": {
|
|
10
23
|
"test": "forge test",
|
|
11
24
|
"coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
|
|
@@ -13,21 +26,20 @@
|
|
|
13
26
|
"deploy:mainnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks mainnets",
|
|
14
27
|
"deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
|
|
15
28
|
"deploy:testnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks testnets",
|
|
16
|
-
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-
|
|
29
|
+
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'croptop-core-v6'"
|
|
17
30
|
},
|
|
18
31
|
"dependencies": {
|
|
19
|
-
"@bananapus/721-hook-v6": "
|
|
20
|
-
"@bananapus/
|
|
21
|
-
"@bananapus/
|
|
22
|
-
"@bananapus/
|
|
23
|
-
"@bananapus/
|
|
24
|
-
"@bananapus/
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
32
|
+
"@bananapus/721-hook-v6": "0.0.43",
|
|
33
|
+
"@bananapus/core-v6": "0.0.39",
|
|
34
|
+
"@bananapus/ownable-v6": "0.0.24",
|
|
35
|
+
"@bananapus/permission-ids-v6": "0.0.22",
|
|
36
|
+
"@bananapus/router-terminal-v6": "0.0.36",
|
|
37
|
+
"@bananapus/suckers-v6": "0.0.33",
|
|
38
|
+
"@openzeppelin/contracts": "5.6.1",
|
|
39
|
+
"@rev-net/core-v6": "0.0.37"
|
|
27
40
|
},
|
|
28
41
|
"devDependencies": {
|
|
29
|
-
"@bananapus/address-registry-v6": "
|
|
30
|
-
"@
|
|
31
|
-
"@sphinx-labs/plugins": "^0.33.3"
|
|
42
|
+
"@bananapus/address-registry-v6": "0.0.25",
|
|
43
|
+
"@sphinx-labs/plugins": "0.33.3"
|
|
32
44
|
}
|
|
33
45
|
}
|
|
@@ -248,14 +248,16 @@ contract ConfigureFeeProjectScript is Script, Sphinx {
|
|
|
248
248
|
if (block.chainid == 1 || block.chainid == 11_155_111) {
|
|
249
249
|
suckerDeployerConfigurations = new JBSuckerDeployerConfig[](3);
|
|
250
250
|
// OP
|
|
251
|
-
suckerDeployerConfigurations[0] =
|
|
252
|
-
|
|
251
|
+
suckerDeployerConfigurations[0] = JBSuckerDeployerConfig({
|
|
252
|
+
deployer: suckers.optimismDeployer, peer: bytes32(0), mappings: tokenMappings
|
|
253
|
+
});
|
|
253
254
|
|
|
254
255
|
suckerDeployerConfigurations[1] =
|
|
255
|
-
JBSuckerDeployerConfig({deployer: suckers.baseDeployer, mappings: tokenMappings});
|
|
256
|
+
JBSuckerDeployerConfig({deployer: suckers.baseDeployer, peer: bytes32(0), mappings: tokenMappings});
|
|
256
257
|
|
|
257
|
-
suckerDeployerConfigurations[2] =
|
|
258
|
-
|
|
258
|
+
suckerDeployerConfigurations[2] = JBSuckerDeployerConfig({
|
|
259
|
+
deployer: suckers.arbitrumDeployer, peer: bytes32(0), mappings: tokenMappings
|
|
260
|
+
});
|
|
259
261
|
} else {
|
|
260
262
|
suckerDeployerConfigurations = new JBSuckerDeployerConfig[](1);
|
|
261
263
|
// L2 -> Mainnet
|
|
@@ -263,6 +265,7 @@ contract ConfigureFeeProjectScript is Script, Sphinx {
|
|
|
263
265
|
deployer: address(suckers.optimismDeployer) != address(0)
|
|
264
266
|
? suckers.optimismDeployer
|
|
265
267
|
: address(suckers.baseDeployer) != address(0) ? suckers.baseDeployer : suckers.arbitrumDeployer,
|
|
268
|
+
peer: bytes32(0),
|
|
266
269
|
mappings: tokenMappings
|
|
267
270
|
});
|
|
268
271
|
|
package/src/CTDeployer.sol
CHANGED
|
@@ -43,17 +43,22 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
43
43
|
//*********************************************************************//
|
|
44
44
|
|
|
45
45
|
error CTDeployer_NotOwnerOfProject(uint256 projectId, address hook, address caller);
|
|
46
|
+
//*********************************************************************//
|
|
47
|
+
// ---------------------------- events -------------------------------- //
|
|
48
|
+
//*********************************************************************//
|
|
49
|
+
|
|
50
|
+
event CTDeployer_SuckerDeploymentFailed(uint256 indexed projectId, bytes32 indexed salt, bytes reason);
|
|
46
51
|
|
|
47
52
|
//*********************************************************************//
|
|
48
53
|
// ---------------- public immutable stored properties --------------- //
|
|
49
54
|
//*********************************************************************//
|
|
50
55
|
|
|
51
|
-
/// @notice Mints ERC-721s that represent Juicebox project ownership and transfers.
|
|
52
|
-
IJBProjects public immutable override PROJECTS;
|
|
53
|
-
|
|
54
56
|
/// @notice The deployer to launch Croptop recorded collections from.
|
|
55
57
|
IJB721TiersHookDeployer public immutable override DEPLOYER;
|
|
56
58
|
|
|
59
|
+
/// @notice Mints ERC-721s that represent Juicebox project ownership and transfers.
|
|
60
|
+
IJBProjects public immutable override PROJECTS;
|
|
61
|
+
|
|
57
62
|
/// @notice The Croptop publisher.
|
|
58
63
|
ICTPublisher public immutable override PUBLISHER;
|
|
59
64
|
|
|
@@ -95,25 +100,13 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
95
100
|
PUBLISHER = publisher;
|
|
96
101
|
SUCKER_REGISTRY = suckerRegistry;
|
|
97
102
|
|
|
98
|
-
//
|
|
103
|
+
// Set permission for the CTPublisher to adjust tiers while the deployer temporarily owns new hooks.
|
|
99
104
|
uint8[] memory permissionIds = new uint8[](1);
|
|
100
|
-
permissionIds[0] = JBPermissionIds.MAP_SUCKER_TOKEN;
|
|
101
|
-
|
|
102
|
-
// Give the operator the permission.
|
|
103
|
-
// Set up the permission data.
|
|
104
|
-
JBPermissionsData memory permissionData =
|
|
105
|
-
JBPermissionsData({operator: address(SUCKER_REGISTRY), projectId: 0, permissionIds: permissionIds});
|
|
106
|
-
|
|
107
|
-
// Set the permissions.
|
|
108
|
-
PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
|
|
109
|
-
|
|
110
|
-
// Set permission for the CTPublisher to adjust the tier.
|
|
111
105
|
permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
|
|
112
106
|
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
JBPermissionsData memory permissionData =
|
|
108
|
+
JBPermissionsData({operator: address(PUBLISHER), projectId: 0, permissionIds: permissionIds});
|
|
115
109
|
|
|
116
|
-
// Set permission for the CTPublisher to adjust the tier.
|
|
117
110
|
PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
|
|
118
111
|
}
|
|
119
112
|
|
|
@@ -137,7 +130,7 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
137
130
|
|
|
138
131
|
// Make sure the caller is the owner of the project.
|
|
139
132
|
if (PROJECTS.ownerOf(projectId) != _msgSender()) {
|
|
140
|
-
revert CTDeployer_NotOwnerOfProject(projectId, address(hook), _msgSender());
|
|
133
|
+
revert CTDeployer_NotOwnerOfProject({projectId: projectId, hook: address(hook), caller: _msgSender()});
|
|
141
134
|
}
|
|
142
135
|
|
|
143
136
|
// Transfer the hook's ownership to the project.
|
|
@@ -145,9 +138,9 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
145
138
|
}
|
|
146
139
|
|
|
147
140
|
/// @notice Deploy a simple project meant to receive posts from Croptop templates.
|
|
148
|
-
/// @dev The
|
|
149
|
-
///
|
|
150
|
-
///
|
|
141
|
+
/// @dev The deployed hook remains owned by `CTDeployer` until the project owner claims collection ownership.
|
|
142
|
+
/// The initial owner is granted direct deployer-scoped hook permissions as a launch-time convenience. Those
|
|
143
|
+
/// permissions can bypass Croptop's publisher surface until ownership is claimed away from the deployer.
|
|
151
144
|
/// @param owner The address that'll own the project.
|
|
152
145
|
/// @param projectConfig The configuration for the project.
|
|
153
146
|
/// @param suckerDeploymentConfiguration The configuration for the suckers to deploy.
|
|
@@ -170,8 +163,8 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
170
163
|
rulesetConfigurations[0].weight = 1_000_000 * (10 ** 18);
|
|
171
164
|
rulesetConfigurations[0].metadata.baseCurrency = JBCurrencyIds.ETH;
|
|
172
165
|
|
|
173
|
-
//
|
|
174
|
-
projectId = PROJECTS.
|
|
166
|
+
// Reserve the project ID up front so permissionless project creations cannot invalidate hook deployment.
|
|
167
|
+
projectId = PROJECTS.createFor(address(this));
|
|
175
168
|
|
|
176
169
|
// Deploy a blank project.
|
|
177
170
|
// slither-disable-next-line reentrancy-benign
|
|
@@ -202,17 +195,15 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
202
195
|
rulesetConfigurations[0].metadata.useDataHookForPay = true;
|
|
203
196
|
rulesetConfigurations[0].metadata.useDataHookForCashOut = true;
|
|
204
197
|
|
|
205
|
-
// Launch the
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
})
|
|
215
|
-
);
|
|
198
|
+
// Launch the rulesets for the reserved project.
|
|
199
|
+
// slither-disable-next-line unused-return
|
|
200
|
+
controller.launchRulesetsFor({
|
|
201
|
+
projectId: projectId,
|
|
202
|
+
projectUri: projectConfig.projectUri,
|
|
203
|
+
rulesetConfigurations: rulesetConfigurations,
|
|
204
|
+
terminalConfigurations: projectConfig.terminalConfigurations,
|
|
205
|
+
memo: "Deployed from Croptop"
|
|
206
|
+
});
|
|
216
207
|
|
|
217
208
|
// Set the data hook for the project.
|
|
218
209
|
dataHookOf[projectId] = IJBRulesetDataHook(hook);
|
|
@@ -227,23 +218,32 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
227
218
|
// intentionally ordered. If both deployers fail, the deployment proceeds without suckers rather than reverting,
|
|
228
219
|
// allowing projects to launch on unsupported chains with manual sucker setup later.
|
|
229
220
|
if (suckerDeploymentConfiguration.salt != bytes32(0)) {
|
|
221
|
+
bytes32 suckerSalt = keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender()));
|
|
222
|
+
|
|
223
|
+
// Successful deployments are discoverable from the registry, and failures are reported without reverting
|
|
224
|
+
// the project launch.
|
|
230
225
|
// slither-disable-next-line unused-return
|
|
231
|
-
SUCKER_REGISTRY.deploySuckersFor({
|
|
226
|
+
try SUCKER_REGISTRY.deploySuckersFor({
|
|
232
227
|
projectId: projectId,
|
|
233
|
-
salt:
|
|
228
|
+
salt: suckerSalt,
|
|
234
229
|
configurations: suckerDeploymentConfiguration.deployerConfigurations
|
|
235
|
-
})
|
|
230
|
+
}) returns (
|
|
231
|
+
address[] memory
|
|
232
|
+
) {
|
|
233
|
+
// no-op
|
|
234
|
+
}
|
|
235
|
+
catch (bytes memory reason) {
|
|
236
|
+
// slither-disable-next-line reentrancy-events
|
|
237
|
+
emit CTDeployer_SuckerDeploymentFailed({projectId: projectId, salt: suckerSalt, reason: reason});
|
|
238
|
+
}
|
|
236
239
|
}
|
|
237
240
|
|
|
238
|
-
//
|
|
241
|
+
// Transfer the project NFT to its intended owner.
|
|
239
242
|
PROJECTS.transferFrom({from: address(this), to: owner, tokenId: projectId});
|
|
240
243
|
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
// dynamically via PROJECTS.ownerOf(projectId). Before claiming, CTDeployer is the static hook owner,
|
|
245
|
-
// so these permissions allow the project owner to manage tiers through CTDeployer. As a tradeoff,
|
|
246
|
-
// the owner can also bypass the Croptop publisher surface until ownership is claimed away.
|
|
244
|
+
// Give the initial project owner direct collection-control permissions while CTDeployer remains the hook's
|
|
245
|
+
// owner. This preserves the documented Croptop launch tradeoff: the owner can manage the collection directly
|
|
246
|
+
// before calling `claimCollectionOwnershipOf(...)`, after which hook permissions follow the project NFT owner.
|
|
247
247
|
uint8[] memory permissionIds = new uint8[](4);
|
|
248
248
|
permissionIds[0] = JBPermissionIds.ADJUST_721_TIERS;
|
|
249
249
|
permissionIds[1] = JBPermissionIds.SET_721_METADATA;
|
|
@@ -253,7 +253,7 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
253
253
|
PERMISSIONS.setPermissionsFor({
|
|
254
254
|
account: address(this),
|
|
255
255
|
permissionsData: JBPermissionsData({
|
|
256
|
-
operator:
|
|
256
|
+
operator: owner,
|
|
257
257
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
258
258
|
projectId: uint64(projectId),
|
|
259
259
|
permissionIds: permissionIds
|
|
@@ -272,12 +272,13 @@ contract CTDeployer is ERC2771Context, JBPermissioned, IJBRulesetDataHook, IERC7
|
|
|
272
272
|
external
|
|
273
273
|
returns (address[] memory suckers)
|
|
274
274
|
{
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
});
|
|
275
|
+
address owner = PROJECTS.ownerOf(projectId);
|
|
276
|
+
|
|
277
|
+
// First prove the external caller is allowed to request sucker deployment for the project owner.
|
|
278
|
+
_requirePermissionFrom({account: owner, projectId: projectId, permissionId: JBPermissionIds.DEPLOY_SUCKERS});
|
|
279
279
|
|
|
280
|
-
// Deploy the suckers.
|
|
280
|
+
// Deploy the suckers. The sucker registry performs its own permission check against this forwarding helper,
|
|
281
|
+
// so an unapproved CTDeployer fails at the downstream registry boundary without an extra preflight read here.
|
|
281
282
|
// slither-disable-next-line unused-return
|
|
282
283
|
suckers = SUCKER_REGISTRY.deploySuckersFor({
|
|
283
284
|
projectId: projectId,
|
package/src/CTPublisher.sol
CHANGED
|
@@ -33,6 +33,7 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
33
33
|
error CTPublisher_MaxTotalSupplyLessThanMin(uint256 min, uint256 max);
|
|
34
34
|
error CTPublisher_NotInAllowList(address addr, address[] allowedAddresses);
|
|
35
35
|
error CTPublisher_PriceTooSmall(uint256 price, uint256 minimumPrice);
|
|
36
|
+
error CTPublisher_DuplicatePayMetadata();
|
|
36
37
|
error CTPublisher_FeePaymentFailed(uint256 feeAmount);
|
|
37
38
|
error CTPublisher_SplitPercentExceedsMaximum(uint256 splitPercent, uint256 maximumSplitPercent);
|
|
38
39
|
error CTPublisher_TotalSupplyTooBig(uint256 totalSupply, uint256 maximumTotalSupply);
|
|
@@ -236,6 +237,15 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
236
237
|
// Keep a reference to the metadata ID target.
|
|
237
238
|
address metadataIdTarget = hook.METADATA_ID_TARGET();
|
|
238
239
|
|
|
240
|
+
// Revert if the caller's additional metadata already contains a pay ID — this would shadow Croptop's
|
|
241
|
+
// tier selection, allowing the caller to mint arbitrary tiers.
|
|
242
|
+
{
|
|
243
|
+
bytes4 payId = JBMetadataResolver.getId({purpose: "pay", target: metadataIdTarget});
|
|
244
|
+
// slither-disable-next-line unused-return
|
|
245
|
+
(bool exists,) = JBMetadataResolver.getDataFor({id: payId, metadata: additionalPayMetadata});
|
|
246
|
+
if (exists) revert CTPublisher_DuplicatePayMetadata();
|
|
247
|
+
}
|
|
248
|
+
|
|
239
249
|
// Create the metadata for the payment to specify the tier IDs that should be minted. We create manually the
|
|
240
250
|
// original metadata, following
|
|
241
251
|
// the specifications from the JBMetadataResolver library.
|
|
@@ -464,18 +474,23 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
|
|
|
464
474
|
uint256 tierId = tierIdForEncodedIPFSUriOf[address(hook)][post.encodedIPFSUri];
|
|
465
475
|
|
|
466
476
|
if (tierId != 0) {
|
|
467
|
-
//
|
|
468
|
-
//
|
|
477
|
+
// Validate the cached tier still exists and its URI still matches.
|
|
478
|
+
// The cache can become stale if the tier was removed (via adjustTiers) or
|
|
479
|
+
// its URI was changed (via setMetadata). In either case, clear the stale
|
|
480
|
+
// mapping and fall through to create a new tier.
|
|
481
|
+
// slither-disable-next-line calls-loop
|
|
482
|
+
JB721Tier memory cachedTier =
|
|
483
|
+
store.tierOf({hook: address(hook), id: tierId, includeResolvedUri: false});
|
|
469
484
|
// slither-disable-next-line calls-loop
|
|
470
|
-
if (store.isTierRemoved(address(hook), tierId))
|
|
485
|
+
if (store.isTierRemoved(address(hook), tierId) || cachedTier.encodedIPFSUri != post.encodedIPFSUri)
|
|
486
|
+
{
|
|
471
487
|
delete tierIdForEncodedIPFSUriOf[address(hook)][post.encodedIPFSUri];
|
|
472
488
|
} else {
|
|
473
489
|
tierIdsToMint[i] = tierId;
|
|
474
490
|
|
|
475
491
|
// For existing tiers, use the actual tier price (not the user-supplied post.price)
|
|
476
492
|
// to prevent fee evasion by passing price=0 for an existing tier.
|
|
477
|
-
|
|
478
|
-
totalPrice += store.tierOf({hook: address(hook), id: tierId, includeResolvedUri: false}).price;
|
|
493
|
+
totalPrice += cachedTier.price;
|
|
479
494
|
}
|
|
480
495
|
}
|
|
481
496
|
}
|
|
@@ -24,8 +24,8 @@ interface ICTDeployer {
|
|
|
24
24
|
function PUBLISHER() external view returns (ICTPublisher);
|
|
25
25
|
|
|
26
26
|
/// @notice Claim ownership of a tiered ERC-721 hook collection by transferring it to the project.
|
|
27
|
-
/// @dev After claiming,
|
|
28
|
-
///
|
|
27
|
+
/// @dev After claiming, hook ownership resolves through the current project NFT owner. That owner must grant
|
|
28
|
+
/// CTPublisher the ADJUST_721_TIERS permission separately for mintFrom() to continue working.
|
|
29
29
|
/// @param hook The hook to claim ownership of. The caller must own the project the hook belongs to.
|
|
30
30
|
function claimCollectionOwnershipOf(IJB721TiersHook hook) external;
|
|
31
31
|
|
package/ADMINISTRATION.md
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Administration
|
|
2
|
-
|
|
3
|
-
## At A Glance
|
|
4
|
-
|
|
5
|
-
| Item | Details |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| Scope | Croptop deployment flow, publish-policy administration, and irreversible project owner sink behavior |
|
|
8
|
-
| Control posture | Mixed deployer-managed and project-local control |
|
|
9
|
-
| Highest-risk actions | Burn-locking a project into `CTProjectOwner`, misconfiguring posting criteria, and deploying suckers with the wrong authority assumptions |
|
|
10
|
-
| Recovery posture | Posting policy can often be changed, but burn-lock and some deployer wiring choices usually require replacement flows |
|
|
11
|
-
|
|
12
|
-
## Purpose
|
|
13
|
-
|
|
14
|
-
`croptop-core-v6` has two distinct control planes: project-local publishing control and deployer-level structural wiring. The high-risk surfaces are posting criteria, hook ownership, publisher permissions, and the irreversible `CTProjectOwner` burn-lock path.
|
|
15
|
-
|
|
16
|
-
## Control Model
|
|
17
|
-
|
|
18
|
-
- `CTPublisher` enforces publish policy but does not own the project.
|
|
19
|
-
- `CTDeployer` is both a deployment helper and a live ruleset data-hook wrapper.
|
|
20
|
-
- The initial project owner receives direct hook-management permissions from `CTDeployer` at deployment time.
|
|
21
|
-
- Project owners or delegates administer publishing through the hook owner and `JBPermissions`.
|
|
22
|
-
- `CTProjectOwner` is an irreversible ownership sink for projects that want Croptop-mediated control.
|
|
23
|
-
- `SUCKER_REGISTRY` and `PUBLISHER` receive structural permissions from `CTDeployer`.
|
|
24
|
-
|
|
25
|
-
## Roles
|
|
26
|
-
|
|
27
|
-
| Role | How Assigned | Scope | Notes |
|
|
28
|
-
| --- | --- | --- | --- |
|
|
29
|
-
| Project owner | `JBProjects.ownerOf(projectId)` | Per project | May grant delegates through `JBPermissions` |
|
|
30
|
-
| Hook owner | `JBOwnable(hook).owner()` | Per hook | Often resolves to the project owner after claim |
|
|
31
|
-
| `CTDeployer` | Immutable singleton | Global | Launch helper and runtime wrapper |
|
|
32
|
-
| `CTPublisher` | Immutable singleton | Global runtime surface | Needs `ADJUST_721_TIERS` authority on relevant hooks |
|
|
33
|
-
| `CTProjectOwner` | Receives project NFT transfer | Per project | Burn-lock path with no return function |
|
|
34
|
-
| `SUCKER_REGISTRY` | Immutable dependency | Global | Holds wildcard `MAP_SUCKER_TOKEN` from the deployer |
|
|
35
|
-
|
|
36
|
-
## Privileged Surfaces
|
|
37
|
-
|
|
38
|
-
| Contract | Function | Who Can Call | Effect |
|
|
39
|
-
| --- | --- | --- | --- |
|
|
40
|
-
| `CTDeployer` | `deployProjectFor(...)` | Anyone | Launches a Croptop-shaped project and configures initial permissions |
|
|
41
|
-
| `CTDeployer` | `claimCollectionOwnershipOf(...)` | Current project owner | Transfers hook ownership from the deployer path to the project |
|
|
42
|
-
| `CTDeployer` | `deploySuckersFor(...)` | Project owner or `DEPLOY_SUCKERS` delegate | Extends a project with suckers |
|
|
43
|
-
| `CTPublisher` | `configurePostingCriteriaFor(...)` | Hook owner or `ADJUST_721_TIERS` delegate | Changes posting policy for a hook and category |
|
|
44
|
-
| `CTPublisher` | `mintFrom(...)` | Anyone subject to policy | Publishes posts, mints first copies, and routes the Croptop fee |
|
|
45
|
-
| `CTProjectOwner` | `onERC721Received(...)` | Any project NFT transfer into it | Locks the project into the Croptop owner helper and grants `CTPublisher` tier-adjust authority |
|
|
46
|
-
|
|
47
|
-
Important nuance:
|
|
48
|
-
|
|
49
|
-
- after `deployProjectFor(...)`, the initial project owner can directly manage tiers, metadata, minting, and discount percent through permissions granted from `CTDeployer`
|
|
50
|
-
- that means the owner can bypass the publisher path until ownership is claimed away from `CTDeployer`
|
|
51
|
-
|
|
52
|
-
## Immutable And One-Way
|
|
53
|
-
|
|
54
|
-
- `CTDeployer`'s wildcard permission grants to `SUCKER_REGISTRY` and `CTPublisher` are structural.
|
|
55
|
-
- `dataHookOf[projectId]` is write-once through deployment flow.
|
|
56
|
-
- Sending a project NFT into `CTProjectOwner` is effectively irreversible.
|
|
57
|
-
- `FEE_PROJECT_ID` in `CTPublisher` is constructor-immutable.
|
|
58
|
-
|
|
59
|
-
## Operational Notes
|
|
60
|
-
|
|
61
|
-
- Validate posting criteria before broad publisher access.
|
|
62
|
-
- Decide intentionally whether the project should keep the initial direct-management path or move to project-owned hook control with `claimCollectionOwnershipOf(...)`.
|
|
63
|
-
- Use `claimCollectionOwnershipOf(...)` when the project should own the hook directly instead of relying on the deployer as the ownership bridge.
|
|
64
|
-
- Treat the burn-lock path as governance finality, not convenience.
|
|
65
|
-
- Review Croptop deployer changes as both launch-time and runtime changes.
|
|
66
|
-
|
|
67
|
-
## Machine Notes
|
|
68
|
-
|
|
69
|
-
- Do not treat `CTDeployer` as a passive script helper; it is also part of the live runtime path.
|
|
70
|
-
- Treat `src/CTPublisher.sol`, `src/CTDeployer.sol`, and `src/CTProjectOwner.sol` as the minimum source set for control-plane review.
|
|
71
|
-
- If a project NFT has already been sent to `CTProjectOwner`, stop assuming the original owner can recover it.
|
|
72
|
-
|
|
73
|
-
## Recovery
|
|
74
|
-
|
|
75
|
-
- If posting policy is wrong but the project still controls the hook, fix it through `configurePostingCriteriaFor(...)`.
|
|
76
|
-
- If the wrong hook path or burn-lock path was chosen, recovery usually means a new project or new hook arrangement.
|
|
77
|
-
- `CTProjectOwner` is not a reversible safety valve.
|
|
78
|
-
|
|
79
|
-
## Admin Boundaries
|
|
80
|
-
|
|
81
|
-
- Neither project owners nor Croptop can change the fixed fee divisor in `CTPublisher`.
|
|
82
|
-
- `CTPublisher` cannot trap fee ETH intentionally; failed fee-terminal payments refund `_msgSender()` or revert.
|
|
83
|
-
- `CTProjectOwner` cannot return project ownership once it receives the NFT.
|
|
84
|
-
- `CTDeployer` cannot later rewrite `dataHookOf[projectId]` through a setter.
|
|
85
|
-
- `CTDeployer` does not stop the initial project owner from using the directly granted hook permissions before ownership is claimed away.
|
|
86
|
-
|
|
87
|
-
## Source Map
|
|
88
|
-
|
|
89
|
-
- `src/CTPublisher.sol`
|
|
90
|
-
- `src/CTDeployer.sol`
|
|
91
|
-
- `src/CTProjectOwner.sol`
|
|
92
|
-
- `script/Deploy.s.sol`
|
|
93
|
-
- `script/helpers/CroptopDeploymentLib.sol`
|
|
94
|
-
- `test/TestAuditGaps.sol`
|
package/ARCHITECTURE.md
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
`croptop-core-v6` turns a Juicebox project with a 721 tiers hook into a permissioned publishing market. Project owners define what posts are valid, third parties publish content by minting or reusing tiers, and Croptop routes a fixed publish fee to the canonical fee project.
|
|
6
|
-
|
|
7
|
-
## System Overview
|
|
8
|
-
|
|
9
|
-
`CTPublisher` is the runtime policy and fee-routing surface. `CTDeployer` is the launch wrapper that can package a project, its 721 hook config, posting rules, and optional omnichain setup in one transaction. `CTProjectOwner` is the irreversible ownership helper for projects that want Croptop-mediated administration instead of a plain owner EOA.
|
|
10
|
-
|
|
11
|
-
## Core Invariants
|
|
12
|
-
|
|
13
|
-
- A post can only be published if it satisfies the configured category, pricing, supply, split, and allowlist rules.
|
|
14
|
-
- Publish fees must be computed from the call value, not from ambient contract balance.
|
|
15
|
-
- `CTPublisher` must not trap fee funds. If fee-project payment fails, the fee is refunded to `_msgSender()`, and if that refund fails the publish reverts.
|
|
16
|
-
- Tier creation and minting must still respect `nana-721-hook-v6` invariants.
|
|
17
|
-
- `CTDeployer` intentionally creates a temporary owner-bypass period before collection ownership is claimed away from the deployer.
|
|
18
|
-
- `CTProjectOwner` is a burn-lock primitive, not a flexible admin panel.
|
|
19
|
-
|
|
20
|
-
## Modules
|
|
21
|
-
|
|
22
|
-
| Module | Responsibility | Notes |
|
|
23
|
-
| --- | --- | --- |
|
|
24
|
-
| `CTPublisher` | Post validation, tier reuse or creation, first-copy minting, fee routing | Main runtime contract |
|
|
25
|
-
| `CTDeployer` | Project launch, hook wiring, optional sucker setup, wrapper behavior | Launch-time and runtime wrapper |
|
|
26
|
-
| `CTProjectOwner` | Irreversible ownership helper | Governance-sensitive |
|
|
27
|
-
| `CTAllowedPost`, `CTPost`, related structs | Publishing policy and request encoding | Shared config surface |
|
|
28
|
-
|
|
29
|
-
## Trust Boundaries
|
|
30
|
-
|
|
31
|
-
- Tier storage and minting semantics live in `nana-721-hook-v6`.
|
|
32
|
-
- Terminal accounting and project ownership live in `nana-core-v6`.
|
|
33
|
-
- When omnichain setup is enabled, this repo composes patterns from `nana-suckers-v6` and `nana-omnichain-deployers-v6`.
|
|
34
|
-
|
|
35
|
-
## Critical Flows
|
|
36
|
-
|
|
37
|
-
### Publish
|
|
38
|
-
|
|
39
|
-
```text
|
|
40
|
-
poster
|
|
41
|
-
-> calls mintFrom(...)
|
|
42
|
-
-> publisher validates each post against project policy
|
|
43
|
-
-> publisher creates or reuses 721 tiers
|
|
44
|
-
-> project terminal receives the publish payment
|
|
45
|
-
-> fee project receives the fixed fee slice, or _msgSender() is refunded if that fee payment fails
|
|
46
|
-
-> first copy of each post tier is minted to the poster
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Launch
|
|
50
|
-
|
|
51
|
-
```text
|
|
52
|
-
creator
|
|
53
|
-
-> CTDeployer launches the project and 721-hook shape
|
|
54
|
-
-> configures Croptop posting rules
|
|
55
|
-
-> optionally wires omnichain sucker deployment
|
|
56
|
-
-> may remain in the flow as a runtime wrapper when hook composition is enabled
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Accounting Model
|
|
60
|
-
|
|
61
|
-
This repo does not define treasury accounting. Its critical economic logic is publish-fee routing and the mapping from valid post data to tier creation or reuse.
|
|
62
|
-
|
|
63
|
-
`CTPublisher` also relies on duplicate-content and pricing checks to stop fee evasion through batch composition or tier reuse.
|
|
64
|
-
|
|
65
|
-
## Security Model
|
|
66
|
-
|
|
67
|
-
- Fee routing is liveness-first but still value-sensitive; fallback refunds must stay correct.
|
|
68
|
-
- `CTDeployer` has a larger review surface than a normal deployer because it can also participate at runtime.
|
|
69
|
-
- Croptop's product boundary is partly social: until collection ownership is claimed away from `CTDeployer`, the project owner can interact through the granted permissions rather than only through the publisher surface.
|
|
70
|
-
- Posting-policy bugs are product-level authorization bugs, not just metadata bugs.
|
|
71
|
-
|
|
72
|
-
## Safe Change Guide
|
|
73
|
-
|
|
74
|
-
- Put generic tier logic in `nana-721-hook-v6`, not here.
|
|
75
|
-
- If fee behavior changes, review payment ordering, fee-project fallback, and refund failure handling together.
|
|
76
|
-
- If deployer ownership or permission grants change, re-check the temporary bypass window and post-claim ownership behavior together.
|
|
77
|
-
- If `CTDeployer` changes, test both project launch and any wrapped hook flow it participates in.
|
|
78
|
-
- Treat `CTProjectOwner` changes as governance changes.
|
|
79
|
-
|
|
80
|
-
## Canonical Checks
|
|
81
|
-
|
|
82
|
-
- publish-path fee routing and policy enforcement:
|
|
83
|
-
`test/CTPublisher.t.sol`
|
|
84
|
-
- fee fallback and refund safety:
|
|
85
|
-
`test/audit/FeeFallbackBlackhole.t.sol`
|
|
86
|
-
- duplicate-content and batch-fee-evasion resistance:
|
|
87
|
-
`test/regression/DuplicateUriFeeEvasion.t.sol`
|
|
88
|
-
|
|
89
|
-
## Source Map
|
|
90
|
-
|
|
91
|
-
- `src/CTPublisher.sol`
|
|
92
|
-
- `src/CTDeployer.sol`
|
|
93
|
-
- `src/CTProjectOwner.sol`
|
|
94
|
-
- `test/CTPublisher.t.sol`
|
|
95
|
-
- `test/audit/FeeFallbackBlackhole.t.sol`
|
|
96
|
-
- `test/regression/DuplicateUriFeeEvasion.t.sol`
|
package/AUDIT_INSTRUCTIONS.md
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Audit Instructions
|
|
2
|
-
|
|
3
|
-
Croptop is a publishing layer on top of Juicebox projects and the tiered 721 stack. Audit it as a permissions, fee-routing, and project-launch system.
|
|
4
|
-
|
|
5
|
-
## Audit Objective
|
|
6
|
-
|
|
7
|
-
Find issues that:
|
|
8
|
-
|
|
9
|
-
- let publishers create or mint posts outside configured criteria
|
|
10
|
-
- let users evade Croptop fees or route them incorrectly
|
|
11
|
-
- grant fee-free or privileged cash-outs to the wrong actors
|
|
12
|
-
- make tier reuse bypass stale-content, fee, or policy checks
|
|
13
|
-
- leave a project in an unintended ownership or admin state
|
|
14
|
-
|
|
15
|
-
## Scope
|
|
16
|
-
|
|
17
|
-
In scope:
|
|
18
|
-
|
|
19
|
-
- `src/CTPublisher.sol`
|
|
20
|
-
- `src/CTDeployer.sol`
|
|
21
|
-
- `src/CTProjectOwner.sol`
|
|
22
|
-
- all interfaces in `src/interfaces/`
|
|
23
|
-
- all structs in `src/structs/`
|
|
24
|
-
- deployment helpers in `script/`
|
|
25
|
-
|
|
26
|
-
## Start Here
|
|
27
|
-
|
|
28
|
-
1. `src/CTPublisher.sol`
|
|
29
|
-
2. `src/CTDeployer.sol`
|
|
30
|
-
3. `src/CTProjectOwner.sol`
|
|
31
|
-
|
|
32
|
-
## Security Model
|
|
33
|
-
|
|
34
|
-
Croptop composes several subsystems:
|
|
35
|
-
|
|
36
|
-
- `CTPublisher` enforces posting criteria, creates or adjusts tiers, and routes fees
|
|
37
|
-
- `CTDeployer` launches projects and wires hooks, criteria, and ownership helpers
|
|
38
|
-
- `CTProjectOwner` lets a project follow Croptop-specific admin rules instead of a fixed EOA
|
|
39
|
-
|
|
40
|
-
Trust boundaries that matter:
|
|
41
|
-
|
|
42
|
-
- project owners choose policy, but should not be able to bypass the policy they configured
|
|
43
|
-
- fee recipients and external hooks may revert or reenter
|
|
44
|
-
- sucker-based privileges must stay limited to genuine omnichain components
|
|
45
|
-
|
|
46
|
-
## Roles And Privileges
|
|
47
|
-
|
|
48
|
-
| Role | Powers | How constrained |
|
|
49
|
-
|------|--------|-----------------|
|
|
50
|
-
| Project owner | Choose policy and ownership mode | Must not bypass the active policy through helper paths |
|
|
51
|
-
| `CTPublisher` | Create or reuse tiers and route fees | Must stay within configured criteria |
|
|
52
|
-
| `CTDeployer` | Launch projects and wire helpers | Must not retain unexpected post-launch authority |
|
|
53
|
-
| Sucker integration | Access narrow omnichain-only paths | Must be backed by authentic registry state |
|
|
54
|
-
|
|
55
|
-
## Integration Assumptions
|
|
56
|
-
|
|
57
|
-
| Dependency | Assumption | What breaks if wrong |
|
|
58
|
-
|------------|------------|----------------------|
|
|
59
|
-
| `nana-721-hook-v6` | Tier state and tier adjustments match Croptop policy checks | Posting criteria and tier-reuse safety break |
|
|
60
|
-
| `nana-core-v6` | Terminal and project routing are authentic | Fee routing and publish settlement drift |
|
|
61
|
-
| `nana-ownable-v6` | Ownership helper resolves the intended admin | Projects can end up misowned or stranded |
|
|
62
|
-
| `nana-suckers-v6` | Registry identifies genuine omnichain actors | Fee-free or privileged paths widen incorrectly |
|
|
63
|
-
|
|
64
|
-
## Critical Invariants
|
|
65
|
-
|
|
66
|
-
1. Minimum price, supply bounds, split limits, category restrictions, and allowlists stay binding on every publish path.
|
|
67
|
-
2. Every Croptop mint either pays the configured fee or takes the documented fallback path without underpaying Croptop.
|
|
68
|
-
3. Existing tiers cannot be reused in a way that revives stale criteria or dodges fee collection.
|
|
69
|
-
4. Sucker-only or fee-exempt paths cannot be reached through spoofed registry state or stale deployment wiring.
|
|
70
|
-
5. Ownership handoff and burn-lock flows do not accidentally widen privileges or strand administration.
|
|
71
|
-
|
|
72
|
-
## Attack Surfaces
|
|
73
|
-
|
|
74
|
-
- publish and mint entrypoints
|
|
75
|
-
- fee computation from user input versus onchain state
|
|
76
|
-
- tier creation, adjustment, and reuse logic
|
|
77
|
-
- deployer-mediated pay or cash-out data-hook behavior
|
|
78
|
-
- permission grants during deployment and ownership transfer
|
|
79
|
-
|
|
80
|
-
## Accepted Risks Or Behaviors
|
|
81
|
-
|
|
82
|
-
- Fee routing may degrade to a fallback path rather than block publishing entirely.
|
|
83
|
-
|
|
84
|
-
## Verification
|
|
85
|
-
|
|
86
|
-
- `npm install`
|
|
87
|
-
- `forge build`
|
|
88
|
-
- `forge test`
|