@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
package/README.md CHANGED
@@ -80,8 +80,8 @@ npm install @croptop/core-v6
80
80
 
81
81
  ```bash
82
82
  npm install
83
- forge build
84
- forge test
83
+ forge build --deny notes
84
+ forge test --deny notes --fail-fast --summary --detailed --skip "*/script/**"
85
85
  ```
86
86
 
87
87
  Useful scripts:
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 = ["pascal-case-struct", "mixed-case-variable"]
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.38",
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-v5'"
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": "^0.0.38",
20
- "@bananapus/buyback-hook-v6": "^0.0.30",
21
- "@bananapus/core-v6": "^0.0.36",
22
- "@bananapus/ownable-v6": "^0.0.20",
23
- "@bananapus/permission-ids-v6": "^0.0.19",
24
- "@bananapus/router-terminal-v6": "^0.0.30",
25
- "@bananapus/suckers-v6": "^0.0.28",
26
- "@openzeppelin/contracts": "^5.6.1"
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": "^0.0.17",
30
- "@rev-net/core-v6": "^0.0.32",
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
- JBSuckerDeployerConfig({deployer: suckers.optimismDeployer, mappings: tokenMappings});
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
- JBSuckerDeployerConfig({deployer: suckers.arbitrumDeployer, mappings: tokenMappings});
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
 
@@ -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
- // Give the sucker registry permission to map tokens for all revnets.
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
- // Set permission for the CTPublisher to mint the NFT.
114
- permissionData = JBPermissionsData({operator: address(PUBLISHER), projectId: 0, permissionIds: permissionIds});
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 initial project owner is intentionally granted direct hook-management permissions from
149
- /// `CTDeployer`. This means the owner/operator can bypass the Croptop publisher path and interact
150
- /// with the hook directly if they choose to. That is an explicit product tradeoff.
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
- // Get the next project ID.
174
- projectId = PROJECTS.count() + 1;
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 project, and sanity check the project ID.
206
- assert(
207
- projectId
208
- == controller.launchProjectFor({
209
- owner: address(this),
210
- projectUri: projectConfig.projectUri,
211
- rulesetConfigurations: rulesetConfigurations,
212
- terminalConfigurations: projectConfig.terminalConfigurations,
213
- memo: "Deployed from Croptop"
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: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
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
- //transfer to _owner.
241
+ // Transfer the project NFT to its intended owner.
239
242
  PROJECTS.transferFrom({from: address(this), to: owner, tokenId: projectId});
240
243
 
241
- // Set permission for the project's owner to do all the NFT things.
242
- // These permissions are granted from CTDeployer (address(this)) to the initial owner.
243
- // The hook checks permissions against hook.owner(), which after claimCollectionOwnershipOf() resolves
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: address(owner),
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
- // Enforce permissions.
276
- _requirePermissionFrom({
277
- account: PROJECTS.ownerOf(projectId), projectId: projectId, permissionId: JBPermissionIds.DEPLOY_SUCKERS
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,
@@ -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, CTPublisher is atomically granted the ADJUST_721_TIERS permission so that mintFrom()
28
- /// continues to work.
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`
@@ -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`