@croptop/core-v6 0.0.33 → 0.0.34

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/ADMINISTRATION.md CHANGED
@@ -1,174 +1,94 @@
1
1
  # Administration
2
2
 
3
- Admin privileges and their scope in croptop-core-v6.
4
-
5
3
  ## At A Glance
6
4
 
7
5
  | Item | Details |
8
- |------|---------|
9
- | Scope | Croptop project deployment, posting-criteria management, publisher permissions, and project burn-lock ownership flows. |
10
- | Operators | Project owners, hook owners and delegates, the `CTDeployer`, `CTPublisher`, optional `CTProjectOwner`, and the configured sucker registry. |
11
- | Highest-risk actions | Sending a project NFT to `CTProjectOwner`, misconfiguring posting criteria, or relying on the write-once `dataHookOf` mapping without validating the hook first. |
12
- | Recovery posture | Ownership burn-lock mistakes are not recoverable in place. Operational fixes usually mean updating criteria if the project is still controlled, or deploying a new project/hook if it is not. |
13
-
14
- ## Routine Operations
15
-
16
- - Configure posting criteria before broad publisher access, because `mintFrom()` enforces those rules for every post.
17
- - Use `claimCollectionOwnershipOf()` when the project should own its hook directly instead of leaving hook control with the deployer path.
18
- - Treat sucker deployment as a project extension that still depends on the Croptop data-hook proxy remaining correct for the project.
19
- - Avoid transferring project NFTs to `CTProjectOwner` unless the intention is to burn human control permanently.
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 require replacement flows |
20
11
 
21
- ## One-Way Or High-Risk Actions
12
+ ## Purpose
22
13
 
23
- - `CTProjectOwner` permanently locks any JBProjects NFT it receives.
24
- - `dataHookOf[projectId]` is set during deployment and has no later setter.
25
- - Constructor-time wildcard permissions granted by `CTDeployer` are structural and cannot be revoked from within the deployer.
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.
26
15
 
27
- ## Recovery Notes
16
+ ## Control Model
28
17
 
29
- - If posting rules are wrong but the project still controls the hook, fix them through the hook-owner surface.
30
- - If ownership was accidentally burned into `CTProjectOwner` or the wrong hook path was deployed, recovery generally means abandoning that control path and redeploying the project or hook composition.
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`.
31
24
 
32
25
  ## Roles
33
26
 
34
- ### 1. Project Owner
35
-
36
- **How assigned:** Receives the JBProjects ERC-721 NFT for the project. Initially set by the `owner` parameter in `CTDeployer.deployProjectFor()`. Can be transferred via standard ERC-721 transfer.
37
-
38
- **Scope:** Per-project. Controls posting rules and hook ownership for a single project.
39
-
40
- ### 2. Hook Owner
41
-
42
- **How assigned:** Determined by `JBOwnable(hook).owner()`. For CTDeployer-launched projects, the deployer initially owns the hook (via `DEPLOYER.deployHookFor()`). The project owner can later claim hook ownership via `claimCollectionOwnershipOf()`.
43
-
44
- **Scope:** Per-hook. The hook owner (or anyone with `ADJUST_721_TIERS` permission for that hook's project) can configure posting criteria.
45
-
46
- ### 3. CTDeployer (Contract)
47
-
48
- **How assigned:** Immutable singleton deployed at construction. Acts as the `IJBRulesetDataHook` for all CTDeployer-launched projects (set in `deployProjectFor()`).
49
-
50
- **Scope:** All Croptop-deployed projects. Proxies pay/cashout data hook calls, grants fee-free cashouts to suckers, and holds broad permissions on behalf of launched projects.
51
-
52
- ### 4. CTPublisher (Contract)
53
-
54
- **How assigned:** Immutable singleton deployed at construction. Receives `ADJUST_721_TIERS` permission from CTDeployer at construction.
55
-
56
- **Scope:** All hooks for which it has `ADJUST_721_TIERS` permission. Creates NFT tiers and mints first copies.
57
-
58
- ### 5. CTProjectOwner (Contract)
59
-
60
- **How assigned:** Optional burn-lock proxy. Receives project ownership when a project NFT is `safeTransferFrom`'d to it.
61
-
62
- **Scope:** Per-project. Grants `CTPublisher` permanent `ADJUST_721_TIERS` permission for the received project. Once the project is transferred here, human ownership is effectively burned.
63
-
64
- - **Important:** `onERC721Received()` accepts project NFTs from any transfer, not only mints. If a project owner accidentally transfers their project NFT to `CTProjectOwner`, it is permanently locked -- there is no recovery function. The only check is that `msg.sender` is the `PROJECTS` contract (ensuring it is a JBProjects NFT, not an arbitrary ERC-721).
65
-
66
- ### 6. Sucker Registry
67
-
68
- **How assigned:** Immutable dependency set at CTDeployer construction. Receives `MAP_SUCKER_TOKEN` permission at construction.
69
-
70
- **Scope:** All projects deployed via CTDeployer. Can map tokens for cross-chain bridging. Determines which addresses get fee-free cashouts.
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; no return function |
34
+ | `SUCKER_REGISTRY` | Immutable dependency | Global | Holds wildcard `MAP_SUCKER_TOKEN` from the deployer |
71
35
 
72
- ### 7. Publishers (Poster Addresses)
36
+ ## Privileged Surfaces
73
37
 
74
- **How assigned:** Either any address (when allowlist is empty) or explicitly added to a per-hook per-category allowlist via `configurePostingCriteriaFor()`.
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 |
75
46
 
76
- **Scope:** Per-hook, per-category. Can create NFT tiers (posts) and mint first copies, subject to posting criteria.
47
+ The important nuance is:
77
48
 
78
- ## Privileged Functions
49
+ - after `deployProjectFor(...)`, the initial project owner can directly manage tiers, metadata, minting, and discount percent through permissions granted from `CTDeployer`
50
+ - this means the owner can bypass the publisher path until ownership is claimed away from `CTDeployer`
79
51
 
80
- ### CTDeployer
52
+ ## Immutable And One-Way
81
53
 
82
- | Function | Required Role | Permission ID | Scope | What It Does |
83
- |----------|--------------|---------------|-------|-------------|
84
- | `deployProjectFor()` | Anyone | None | Global | Deploys a new Juicebox project with 721 hook, configures posting rules, optionally deploys suckers, transfers ownership to `owner`. No access restriction -- anyone can deploy a project. |
85
- | `claimCollectionOwnershipOf()` | Project owner | None (direct `ownerOf` check) | Per-project | Transfers hook ownership to the project via `JBOwnable.transferOwnershipToProject()`. Caller must be `PROJECTS.ownerOf(projectId)`. |
86
- | `deploySuckersFor()` | Project owner or delegate | `JBPermissionIds.DEPLOY_SUCKERS` | Per-project | Deploys new cross-chain suckers for an existing project. Uses `_requirePermissionFrom()` against the project owner. |
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.
87
58
 
88
- ### CTPublisher
59
+ ## Operational Notes
89
60
 
90
- | Function | Required Role | Permission ID | Scope | What It Does |
91
- |----------|--------------|---------------|-------|-------------|
92
- | `configurePostingCriteriaFor()` | Hook owner or delegate | `JBPermissionIds.ADJUST_721_TIERS` | Per-hook, per-category | Sets posting rules: minimum price, min/max supply, max split percent, address allowlist. Uses `_requirePermissionFrom()` against `JBOwnable(hook).owner()`. |
93
- | `mintFrom()` | Anyone (subject to allowlist) | None (enforced by allowlist in `_setupPosts`) | Per-hook | Publishes posts as 721 tiers, mints first copies, routes 5% fee to `FEE_PROJECT_ID`. Validates all posts against configured criteria. |
61
+ - Validate posting criteria before broad publisher access; the publisher enforces those rules on every post.
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.
94
66
 
95
- ### CTProjectOwner
67
+ ## Machine Notes
96
68
 
97
- | Function | Required Role | Permission ID | Scope | What It Does |
98
- |----------|--------------|---------------|-------|-------------|
99
- | `onERC721Received()` | Anyone who transfers a JBProjects NFT | None | Per-project | On receiving a project NFT from `PROJECTS`, grants `CTPublisher` the `ADJUST_721_TIERS` permission for that project. The contract does not restrict this to mint receipts; any transferred JBProjects NFT will be accepted and effectively burn human ownership. |
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 crawling.
71
+ - If a project NFT has already been sent to `CTProjectOwner`, stop assuming the original owner can recover it.
100
72
 
101
- ### Permissions Granted at CTDeployer Construction
73
+ ## Recovery
102
74
 
103
- These permissions are set in the CTDeployer constructor and apply to all projects it will ever deploy (wildcard `projectId: 0`):
104
-
105
- | Permission | Granted To | Purpose |
106
- |-----------|-----------|---------|
107
- | `MAP_SUCKER_TOKEN` | `SUCKER_REGISTRY` | Allows the sucker registry to map tokens for cross-chain bridging on any project owned by CTDeployer. |
108
- | `ADJUST_721_TIERS` | `PUBLISHER` (CTPublisher) | Allows CTPublisher to add tiers to any hook on any project owned by CTDeployer. |
109
-
110
- ### Permissions Granted During `deployProjectFor()`
111
-
112
- These permissions are set per-project during deployment:
113
-
114
- | Permission | Granted To | Purpose |
115
- |-----------|-----------|---------|
116
- | `ADJUST_721_TIERS` | `owner` | Allows the project owner to adjust 721 tiers. |
117
- | `SET_721_METADATA` | `owner` | Allows the project owner to update 721 metadata. |
118
- | `MINT_721` | `owner` | Allows the project owner to mint 721 tokens directly. |
119
- | `SET_721_DISCOUNT_PERCENT` | `owner` | Allows the project owner to set tier discount percentages. |
120
-
121
- ## Data Hook Proxy
122
-
123
- When deploying a project, `CTDeployer` sets itself as the project's `dataHook` in the ruleset metadata. It then proxies data hook calls to the project's actual 721 tiers hook:
124
-
125
- - **`beforePayRecordedWith`**: Calls `IJBRulesetDataHook(hook).beforePayRecordedWith(context)` where `hook = dataHookOf[context.projectId]`, then returns the 721 hook's specifications.
126
- - **`beforeCashOutRecordedWith`**: Checks if the caller is a registered sucker via `SUCKER_REGISTRY.isSuckerOf()`. If so, returns 0% cash-out tax (fee-free bridging). Otherwise, delegates to the 721 hook.
127
- - **`hasMintPermissionFor`**: Returns `true` for registered suckers, `false` for all other addresses. Does not delegate to the 721 hook.
128
-
129
- This proxy pattern exists so that CTDeployer can intercept cash-out calls to grant fee-free bridging to suckers while still supporting the 721 hook's NFT minting logic.
130
-
131
- The `dataHookOf[projectId]` mapping is write-once (set during `deployProjectFor`, no setter function). The proxy target cannot be changed after deployment.
132
-
133
- ## Immutable Configuration
134
-
135
- These values are set at deploy time and cannot be changed after deployment:
136
-
137
- | Value | Contract | Set At | Description |
138
- |-------|----------|--------|-------------|
139
- | `FEE_DIVISOR` | CTPublisher | Compile time (constant = 20) | Fee percentage: 5% (1/20). Hardcoded, not configurable. |
140
- | `FEE_PROJECT_ID` | CTPublisher | Constructor (immutable) | Project ID that receives all fees. Cannot be changed. |
141
- | `DIRECTORY` | CTPublisher | Constructor (immutable) | JBDirectory for project/terminal lookups. |
142
- | `PROJECTS` | CTDeployer | Constructor (immutable) | JBProjects NFT contract. |
143
- | `DEPLOYER` | CTDeployer | Constructor (immutable) | JB721TiersHookDeployer for hook creation. |
144
- | `PUBLISHER` | CTDeployer | Constructor (immutable) | CTPublisher contract reference. |
145
- | `SUCKER_REGISTRY` | CTDeployer | Constructor (immutable) | Sucker registry for cross-chain bridging. |
146
- | `PERMISSIONS` | CTDeployer, CTProjectOwner | Constructor (immutable) | JBPermissions contract for access control. |
147
- | `trustedForwarder` | CTDeployer, CTPublisher | Constructor (immutable via ERC2771Context) | Meta-transaction trusted forwarder address. |
148
- | `dataHookOf[projectId]` | CTDeployer | `deployProjectFor()` | Once set during deployment, the data hook for a project can never be changed. Write-once storage. |
149
- | Project weight | CTDeployer | `deployProjectFor()` | Hardcoded at `1_000_000 * 10^18` with ETH base currency and max cashout tax rate. |
150
- | Hook deploy salt | CTDeployer | `deployProjectFor()` | `keccak256(abi.encode(salt, msg.sender))` -- deterministic but caller-specific. |
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.
151
78
 
152
79
  ## Admin Boundaries
153
80
 
154
- What admins CANNOT do:
155
-
156
- 1. **Project owners cannot change the fee rate.** `FEE_DIVISOR = 20` (5%) is a compile-time constant. No function exists to modify it.
157
-
158
- 2. **Project owners cannot change the fee recipient.** `FEE_PROJECT_ID` is immutable. Fees always route to the same project.
159
-
160
- 3. **Project owners cannot change the data hook.** `dataHookOf[projectId]` is write-once (set during `deployProjectFor`, no setter function). The data hook proxy pattern is permanent.
161
-
162
- 4. **Project owners cannot disable Croptop posting entirely for a category.** `configurePostingCriteriaFor()` requires `minimumTotalSupply > 0`. The workaround is to set an astronomically high `minimumPrice` with `minimumTotalSupply = maximumTotalSupply = 1`. See finding NM-006.
163
-
164
- 5. **Project owners cannot bypass posting criteria through `CTPublisher`, but they may still bypass the publisher surface entirely.** `mintFrom()` enforces all configured rules. Separately, the initial owner/operator can hold direct hook-management permissions from `CTDeployer`, which lets them adjust tiers or mint without going through `CTPublisher` until ownership is claimed away or permissions are narrowed.
165
-
166
- 6. **CTPublisher cannot mint without paying.** `mintFrom()` requires `msg.value >= totalPrice + fee`. There is no free-mint path through CTPublisher.
167
-
168
- 7. **CTProjectOwner cannot return project ownership.** Once a project NFT is transferred to CTProjectOwner, there is no function to transfer it back. Ownership is effectively burned.
169
-
170
- 8. **No admin can modify existing tier prices.** Once a tier is created via `_setupPosts()`, the price is set in the `JB721TiersHookStore`. CTPublisher uses the stored price for fee calculation on subsequent mints (not `post.price`). See H-19 fix.
81
+ - Neither project owners nor Croptop can change the fixed Croptop 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.
171
86
 
172
- 9. **No admin can drain CTPublisher funds.** CTPublisher has no `withdraw()` function and no `receive()` / `fallback()`. The only ETH that enters the contract is during `mintFrom()` and it is fully routed to the project terminal and fee terminal (or refunded to the caller if the fee terminal reverts) within the same transaction. If that refund also fails, the mint reverts rather than trapping ETH in the publisher.
87
+ ## Source Map
173
88
 
174
- 10. **Sucker registry trust is irrevocable.** The `MAP_SUCKER_TOKEN` permission is granted at CTDeployer construction with `projectId: 0` (wildcard). There is no function to revoke this permission from within CTDeployer.
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 CHANGED
@@ -2,70 +2,95 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- `croptop-core-v6` turns a Juicebox project with a 721 tiers hook into a permissioned publishing surface. Project owners define what kinds of posts are allowed, and third parties can mint new content tiers only if their post matches those rules. A fixed fee is routed to a designated fee project on every publish.
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
6
 
7
- ## Boundaries
7
+ ## System Overview
8
8
 
9
- - `CTPublisher` owns publishing policy and fee routing.
10
- - `CTDeployer` owns one-shot project creation and optional sucker setup.
11
- - The underlying 721 tier implementation remains in `nana-721-hook-v6`.
12
- - The repo does not reimplement terminal accounting; payments still settle through Juicebox terminals.
9
+ `CTPublisher` is the runtime policy and fee-routing surface. `CTDeployer` is the launch-time wrapper that can package a project, its 721 hook configuration, 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.
13
10
 
14
- ## Main Components
11
+ ## Core Invariants
15
12
 
16
- | Component | Responsibility |
17
- | --- | --- |
18
- | `CTPublisher` | Validates posts, creates or reuses tiers, mints the first copy, and routes publish fees |
19
- | `CTDeployer` | Launches a Juicebox project plus hook configuration in one transaction and can proxy hook callbacks |
20
- | `CTProjectOwner` | Burn-lock helper that permanently delegates tier adjustment authority to Croptop |
21
- | `CTAllowedPost`, `CTPost`, related structs | Encode project-level publishing policy and publish requests |
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 the fee-project payment fails, the fee is refunded to `_msgSender()`, and if that refund fails the publish reverts.
16
+ - Tier creation and minting must continue to 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.
22
19
 
23
- ## Runtime Model
20
+ ## Modules
24
21
 
25
- ### Publishing
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 deployer patterns from `nana-suckers-v6` and `nana-omnichain-deployers-v6` instead of reimplementing them.
34
+
35
+ ## Critical Flows
36
+
37
+ ### Publish
26
38
 
27
39
  ```text
28
40
  poster
29
- -> mintFrom(hook, posts, ...)
30
- -> publisher validates each post against project-defined criteria
31
- -> publisher calls the 721 hook to create or reuse tiers
41
+ -> calls mintFrom(...)
42
+ -> publisher validates each post against project policy
43
+ -> publisher creates or reuses 721 tiers
32
44
  -> project terminal receives the publish payment
33
- -> fee project receives the fixed fee slice, or `_msgSender()` is refunded that fee if the fee terminal rejects it
34
- -> first copy of each created tier is minted to the poster
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
35
47
  ```
36
48
 
37
- ### Project Launch
49
+ ### Launch
38
50
 
39
51
  ```text
40
52
  creator
41
- -> CTDeployer launches the project, hook, posting criteria, and optional suckers
42
- -> deployer can also stand in as a ruleset data-hook wrapper when needed
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
43
57
  ```
44
58
 
45
- ## Critical Invariants
46
-
47
- - A post is valid only if it satisfies the configured category, price, supply, split, and allowlist constraints.
48
- - Fee routing must be computed from the payment value, not transient contract balance, so forced ETH cannot distort the fee.
49
- - Fee routing must not strand ETH in `CTPublisher`. If the fee terminal rejects payment, the fee is refunded to `_msgSender()`; if that refund fails, the mint reverts.
50
- - `CTProjectOwner` only makes sense as a lock, not a flexible admin layer. Once a project is burn-locked, Croptop becomes the only intended tier-adjustment path.
51
- - Publishing should not bypass the 721 hook's own invariants around tier creation and minting.
59
+ ## Accounting Model
52
60
 
53
- ## Where Complexity Lives
61
+ This repo does not define treasury accounting. Its critical economic logic is publish-fee routing and the mapping from valid post data to 721 tier creation or reuse.
54
62
 
55
- - Post validation is spread across category rules, split limits, supply bounds, and optional allowlists.
56
- - `CTDeployer` is subtle because it is both a launch helper and, in some flows, a runtime hook proxy.
57
- - Fee routing is intentionally liveness-first: it prefers refunding `_msgSender()` over blocking the mint when the fee terminal is down, but still reverts if the refund itself cannot be delivered.
63
+ `CTPublisher` also relies on duplicate-content and pricing checks to stop fee evasion through batch composition or tier reuse. Those checks are part of economic correctness, not just content hygiene.
58
64
 
59
- ## Dependencies
65
+ ## Security Model
60
66
 
61
- - `nana-721-hook-v6` for tier storage and minting
62
- - `nana-suckers-v6` and `nana-omnichain-deployers-v6` patterns when omnichain deployment is enabled
63
- - `nana-core-v6` terminals, permissions, and project ownership
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.
64
71
 
65
72
  ## Safe Change Guide
66
73
 
67
- - Keep publishing policy separate from 721 implementation details. If a change is generally useful for all tiered collections, it belongs downstream.
68
- - When modifying fee logic, reason through terminal payment ordering and failure fallback paths together.
69
- - Any change to `CTDeployer` should be reviewed as both a deployer and a live hook wrapper, because it participates in runtime flows after launch.
70
- - Changes to burn-lock semantics should be treated as governance changes, not UI conveniences.
71
- - Be wary of adding product-specific tier semantics here that really belong in the generic 721 hook.
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,15 +1,15 @@
1
1
  # Audit Instructions
2
2
 
3
- Croptop is a publishing layer on top of Juicebox projects and the 721 hook stack. Audit it as a permissions and fee-routing system, not just a content app.
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
4
 
5
- ## Objective
5
+ ## Audit Objective
6
6
 
7
7
  Find issues that:
8
8
  - let publishers create or mint posts outside configured criteria
9
9
  - let users evade Croptop fees or route them incorrectly
10
10
  - grant fee-free or privileged cash-outs to the wrong actors
11
- - create stale, duplicate, or abusive tier reuse across posts
12
- - break ownership handoff or permanently lock a project in an unintended admin state
11
+ - make tier reuse bypass stale-content, fee, or policy checks
12
+ - leave a project in an unintended ownership or admin state
13
13
 
14
14
  ## Scope
15
15
 
@@ -17,76 +17,68 @@ In scope:
17
17
  - `src/CTPublisher.sol`
18
18
  - `src/CTDeployer.sol`
19
19
  - `src/CTProjectOwner.sol`
20
- - `src/interfaces/`
21
- - `src/structs/`
22
- - deployment scripts in `script/`
23
-
24
- External integrations that matter:
25
- - `nana-core-v6`
26
- - `nana-721-hook-v6`
27
- - `nana-ownable-v6`
28
- - `nana-suckers-v6`
29
-
30
- ## System Model
31
-
32
- Croptop has three roles:
33
- - `CTPublisher`: validates post configuration, creates or adjusts tiers, mints the first copy, and routes fees
34
- - `CTDeployer`: launches a project, wires hook ownership and post criteria, and acts as a data-hook proxy where required
35
- - `CTProjectOwner`: ownership helper for projects that want Croptop-controlled administration
36
-
37
- The system relies on project-specific posting criteria such as:
38
- - minimum price
39
- - supply bounds
40
- - category restrictions
41
- - split limits
42
- - optional address allowlists
20
+ - all interfaces in `src/interfaces/`
21
+ - all structs in `src/structs/`
22
+ - deployment helpers in `script/`
43
23
 
44
- ## Critical Invariants
24
+ ## Start Here
25
+
26
+ 1. `src/CTPublisher.sol`
27
+ 2. `src/CTDeployer.sol`
28
+ 3. `src/CTProjectOwner.sol`
29
+
30
+ ## Security Model
45
31
 
46
- 1. Post criteria are binding
47
- No publish path should bypass configured minimum price, total supply bounds, split caps, or allowlist restrictions.
32
+ Croptop composes several subsystems:
33
+ - `CTPublisher` enforces posting criteria, creates or adjusts tiers, and routes fees
34
+ - `CTDeployer` launches projects and wires hooks, criteria, and ownership helpers
35
+ - `CTProjectOwner` lets a project follow Croptop-specific admin rules instead of a fixed EOA
48
36
 
49
- 2. Fee collection is complete
50
- Each Croptop mint should either pay the configured fee or take the documented fallback path. Users must not be able to mint while underpaying Croptop.
37
+ Trust boundaries that matter:
38
+ - project owners choose policy, but should not be able to bypass the policy they configured
39
+ - fee recipients and external hooks may revert or reenter
40
+ - sucker-based privileges must be limited to genuine omnichain components
51
41
 
52
- 3. Tier reuse is safe
53
- Existing tiers must not be reusable in a way that evades fees, stale criteria, or duplicate-content protections.
42
+ ## Roles And Privileges
54
43
 
55
- 4. Sucker privileges stay narrow
56
- Any cash-out tax exemptions or mint permissions intended for legitimate suckers must not be reachable by arbitrary callers or spoofed registry state.
44
+ | Role | Powers | How constrained |
45
+ |------|--------|-----------------|
46
+ | Project owner | Choose policy and ownership mode | Must not bypass the active policy through helper paths |
47
+ | `CTPublisher` | Create or reuse tiers and route fees | Must stay within configured criteria |
48
+ | `CTDeployer` | Launch projects and wire helpers | Must not retain unexpected post-launch authority |
49
+ | Sucker integration | Access narrow omnichain-only paths | Must be backed by authentic registry state |
57
50
 
58
- 5. Ownership transitions are intentional
59
- Burn-lock or project-owner helper flows must not grant broader privileges than intended or accidentally strand project administration.
51
+ ## Integration Assumptions
60
52
 
61
- ## Threat Model
53
+ | Dependency | Assumption | What breaks if wrong |
54
+ |------------|------------|----------------------|
55
+ | `nana-721-hook-v6` | Tier state and tier adjustments match Croptop policy checks | Posting criteria and tier reuse safety break |
56
+ | `nana-core-v6` | Terminal and project routing are authentic | Fee routing and publish settlement drift |
57
+ | `nana-ownable-v6` | Ownership helper resolves the intended admin | Projects can end up misowned or stranded |
58
+ | `nana-suckers-v6` | Registry identifies genuine omnichain actors | Fee-free or privileged paths widen incorrectly |
59
+
60
+ ## Critical Invariants
62
61
 
63
- Prioritize:
64
- - malicious publishers choosing edge-case prices, split structures, or reused metadata
65
- - malicious project owners misconfiguring rules and then trying to escape them
66
- - fake or stale sucker registrations
67
- - fee-recipient failures that alter control flow
68
- - reentrancy through fee routing or tier-adjustment side effects
62
+ 1. Minimum price, supply bounds, split limits, category restrictions, and allowlists stay binding on every publish path.
63
+ 2. Every Croptop mint either pays the configured fee or takes the documented fallback path without underpaying Croptop.
64
+ 3. Existing tiers cannot be reused in a way that revives stale criteria or dodges fee collection.
65
+ 4. Sucker-only or fee-exempt paths cannot be reached through spoofed registry state or stale deployment wiring.
66
+ 5. Ownership handoff and burn-lock flows do not accidentally widen privileges or strand administration.
69
67
 
70
- ## Hotspots
68
+ ## Attack Surfaces
71
69
 
72
- - `CTPublisher.mintFrom` and its validation pipeline
73
- - any code path that computes fees from user-provided versus on-chain values
74
- - tier creation or adjustment against prior post state
75
- - `CTDeployer` data-hook behavior for pay and cash-out flows
76
- - permission grants made during deployment or project-owner handoff
77
- - any one-way lock or burn-based ownership design
70
+ - publish and mint entrypoints
71
+ - fee computation from user input versus on-chain state
72
+ - tier creation, adjustment, and reuse logic
73
+ - deployer-mediated pay or cash-out data-hook behavior
74
+ - permission grants during deployment and ownership transfer
78
75
 
79
- ## Build And Verification
76
+ ## Accepted Risks Or Behaviors
77
+
78
+ - Fee routing may degrade to a fallback path rather than block publishing entirely.
79
+
80
+ ## Verification
80
81
 
81
- Standard workflow:
82
82
  - `npm install`
83
83
  - `forge build`
84
84
  - `forge test`
85
-
86
- Current tests emphasize:
87
- - fee evasion
88
- - stale tier mappings
89
- - reentrancy and attacker-controlled publish flows
90
- - fork and omnichain composition
91
-
92
- Strong findings usually show either fee loss, unauthorized publishing power, or a project entering a control configuration it cannot safely escape.
package/README.md CHANGED
@@ -4,7 +4,12 @@ Croptop turns a Juicebox project with a 721 hook into a permissioned publishing
4
4
 
5
5
  Docs: <https://docs.juicebox.money>
6
6
  Site: <https://croptop.eth.limo>
7
- Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
7
+ Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
8
+ User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
9
+ Skills: [SKILLS.md](./SKILLS.md)
10
+ Risks: [RISKS.md](./RISKS.md)
11
+ Administration: [ADMINISTRATION.md](./ADMINISTRATION.md)
12
+ Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
8
13
 
9
14
  ## Overview
10
15
 
@@ -28,7 +33,7 @@ If a bug looks like ordinary tier issuance or terminal accounting, start in the
28
33
  | --- | --- |
29
34
  | `CTPublisher` | Validates posts, adjusts 721 tiers, mints the first copy, and routes protocol and project payments. |
30
35
  | `CTDeployer` | Launches a project, configures Croptop posting rules, and can wire in omnichain sucker deployments. |
31
- | `CTProjectOwner` | Burn-lock ownership helper that can permanently route project administration through Croptop's publishing surface. |
36
+ | `CTProjectOwner` | Ownership sink that can permanently hold a project NFT while still delegating the posting permissions Croptop needs. |
32
37
 
33
38
  ## Mental Model
34
39
 
@@ -39,6 +44,36 @@ There are two separate concerns here:
39
44
 
40
45
  That distinction matters because many "Croptop bugs" are deployment-shape bugs rather than publishing-rule bugs.
41
46
 
47
+ ## Read These Files First
48
+
49
+ 1. `src/CTPublisher.sol`
50
+ 2. `src/CTDeployer.sol`
51
+ 3. `src/CTProjectOwner.sol`
52
+ 4. `test/CTPublisher.t.sol`
53
+ 5. `test/ClaimCollectionOwnership.t.sol`
54
+
55
+ ## High-Signal Tests
56
+
57
+ 1. `test/CTPublisher.t.sol`
58
+ 2. `test/CTDeployer.t.sol`
59
+ 3. `test/ClaimCollectionOwnership.t.sol`
60
+ 4. `test/audit/FeeFallbackBlackhole.t.sol`
61
+ 5. `test/regression/DuplicateUriFeeEvasion.t.sol`
62
+
63
+ ## Integration Traps
64
+
65
+ - Croptop publishing policy is separate from ordinary 721 tier issuance, so readers often stop in the wrong repo
66
+ - fee routing is part of the publish path and has fallback behavior that affects who must be able to receive ETH
67
+ - `CTProjectOwner` intentionally changes the project's ownership shape and should be reviewed as part of the trust model
68
+ - duplicate-content, stale-tier, and fee-evasion edge cases are first-class surfaces, not only UI concerns
69
+
70
+ ## Where State Lives
71
+
72
+ - posting criteria and publish-side enforcement live in `CTPublisher`
73
+ - deployment-time project wiring lives in `CTDeployer`
74
+ - ownership-sink behavior lives in `CTProjectOwner`
75
+ - actual tier issuance and treasury accounting still live in sibling Juicebox repos
76
+
42
77
  ## Install
43
78
 
44
79
  ```bash
@@ -62,9 +97,9 @@ Useful scripts:
62
97
 
63
98
  ## Deployment Notes
64
99
 
65
- Deployments are handled through Sphinx using the environments configured in the repo scripts. `CTDeployer` can also compose cross-chain sucker deployments when the target publishing project needs omnichain support.
100
+ Deployments are handled through Sphinx using the environments configured in the repo scripts. `CTDeployer` can also compose cross-chain sucker deployments when a nonzero sucker configuration is supplied for the target publishing project.
66
101
 
67
- The deploy script now expects an explicit nonzero `FEE_PROJECT_ID` for canonical deployments. It does not safely
102
+ The deploy script now expects an explicit nonzero `FEE_PROJECT_ID` for production-style deployments. It does not safely
68
103
  autodiscover a fee project by scanning existing project IDs.
69
104
 
70
105
  ## Repository Layout
@@ -89,7 +124,13 @@ script/
89
124
  - posting criteria are only as safe as the project owner configures them
90
125
  - fee routing depends on the designated fee project remaining correctly configured; if its terminal rejects payments,
91
126
  Croptop refunds the fee to `_msgSender()` instead of trapping ETH in `CTPublisher`
92
- - burn-lock ownership is intentionally irreversible and should only be used when immutability is desired
93
- - after burn-locking into `CTProjectOwner`, the previous owner no longer holds the project NFT directly; control is
127
+ - parking a project in `CTProjectOwner` is intentionally irreversible in practice and should only be used when immutability is desired
128
+ - after routing ownership into `CTProjectOwner`, the previous owner no longer holds the project NFT directly; control is
94
129
  intentionally mediated through Croptop's owner helper and hook-admin surface instead of remaining a plain owner EOA
95
130
  - duplicate-content and stale-tier edge cases are guarded by tests, but integrations should still treat metadata reuse carefully
131
+
132
+ ## For AI Agents
133
+
134
+ - Do not describe Croptop as a generic 721 marketplace; it is a rules-driven publishing layer on top of Juicebox.
135
+ - Read `CTPublisher` before `CTDeployer` when the question is about publish eligibility or fee behavior.
136
+ - If the issue is basic tier minting or accounting, move to `nana-721-hook-v6` or `nana-core-v6`.
package/RISKS.md CHANGED
@@ -22,6 +22,7 @@ This file focuses on the publishing, fee-routing, and hook-composition risks tha
22
22
  - **Trusted forwarder.** ERC-2771 `_msgSender()` is trusted in both CTPublisher and CTDeployer for permission checks, allowlist validation, and payment routing. A compromised forwarder can post as any allowed address, deploy projects as any owner, and redirect payments.
23
23
  - **CTDeployer as permanent data hook proxy.** `CTDeployer` sets itself as the data hook for projects it deploys. `dataHookOf[projectId]` is set once during `deployProjectFor` and has no setter to update it. If the underlying data hook needs to change, there is no mechanism to do so without redeploying.
24
24
  - **Sucker registry.** `CTDeployer.beforeCashOutRecordedWith` trusts `SUCKER_REGISTRY.isSuckerOf()` for 0% tax cashouts, same risk as the omnichain deployer.
25
+ - **Sucker deployment is intended to be fail-open at launch time.** `deployProjectFor` calls `SUCKER_REGISTRY.deploySuckersFor` only when a non-zero deployment salt is configured, and the current deployment path is documented as allowing launch to continue on chains where the configured sucker deployer cascade cannot complete. Operators should not assume a successful Croptop launch implies omnichain support is live.
25
26
  - **CTProjectOwner as burn target.** Projects transferred to `CTProjectOwner` grant `ADJUST_721_TIERS` to `PUBLISHER`. The project NFT cannot be recovered -- this is intentional but irreversible.
26
27
  - **JBDirectory / Terminal resolution.** `CTPublisher.mintFrom` resolves terminals via `DIRECTORY.primaryTerminalOf()`. A compromised directory could redirect payment and fee flows.
27
28
  - **721 hook store.** `_setupPosts` calls `hook.STORE().tierOf()` and `hook.STORE().isTierRemoved()`. The store is trusted to return accurate tier data. A malicious hook returning a fake store can report manipulated prices, supply limits, and removal status, causing `_setupPosts` to miscalculate `totalPrice` or skip duplicate detection.
@@ -59,6 +60,7 @@ This file focuses on the publishing, fee-routing, and hook-composition risks tha
59
60
 
60
61
  - **CTDeployer forwards pay/cashout calls to `dataHookOf` with null check.** `beforePayRecordedWith` and `beforeCashOutRecordedWith` check for a null `dataHookOf` and return defaults (context weight, empty specs) instead of reverting. If a non-null data hook reverts, payments/cashouts for the project are still blocked.
61
62
  - **No mechanism for hook migration.** `dataHookOf` is written once in `deployProjectFor` and never updated. If the data hook becomes compromised, there is no governance path to replace it without deploying a new project.
63
+ - **Sucker support can be absent even when deployment requested it.** Launch does not treat successful sucker setup as part of the Croptop app's core publish flow. A project can come online with the publisher, hook, and main terminal flow active while no suckers were actually deployed yet. Monitoring and deployment tooling should verify the returned sucker set explicitly instead of inferring success from project launch.
62
64
  - **Tier ID prediction.** `_setupPosts` predicts new tier IDs as `maxTierIdOf(hook) + 1 + i`. If another transaction adds tiers between `maxTierIdOf` read and `adjustTiers` execution, tier IDs shift and the wrong tiers are minted. This is a race condition in concurrent posting.
63
65
  - **CTProjectOwner accepts any project NFT.** `onERC721Received` grants `ADJUST_721_TIERS` to `PUBLISHER` for whatever tokenId is received. If a non-Croptop project is accidentally transferred to `CTProjectOwner`, the publisher gains tier adjustment permission for it.
64
66
  - **Fee payment destination.** Fees are routed to `FEE_PROJECT_ID` via its primary terminal. If the fee project changes its terminal or token acceptance incompatibly, `mintFrom` attempts to refund the fee to `_msgSender()`. If the caller cannot receive ETH, the mint reverts.
package/SKILLS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## Use This File For
4
4
 
5
5
  - Use this file when the task touches Croptop publishing, project deployment, data-hook forwarding, fee routing, or burn-locked ownership behavior.
6
- - Start here, then jump into the publisher, deployer, or owner contract depending on which path the user is actually asking about.
6
+ - Start here, then decide whether the issue is posting-policy validation, tier reuse/content identity, deployer-packaged project shape, or burn-locked ownership. Those concerns interact, but they are not the same subsystem.
7
7
 
8
8
  ## Read This Next
9
9
 
@@ -13,7 +13,9 @@
13
13
  | Publishing and metadata behavior | [`src/CTPublisher.sol`](./src/CTPublisher.sol) |
14
14
  | Deployment and fee-project wiring | [`src/CTDeployer.sol`](./src/CTDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol), [`script/ConfigureFeeProject.s.sol`](./script/ConfigureFeeProject.s.sol) |
15
15
  | Ownership burn-lock behavior | [`src/CTProjectOwner.sol`](./src/CTProjectOwner.sol) |
16
- | Regression, attack, or fork coverage | [`test/regression/`](./test/regression/), [`test/fork/`](./test/fork/), [`test/`](./test/) |
16
+ | Runtime and operational invariants | [`references/runtime.md`](./references/runtime.md), [`references/operations.md`](./references/operations.md) |
17
+ | Publishing, metadata, and attack coverage | [`test/CTPublisher.t.sol`](./test/CTPublisher.t.sol), [`test/Test_MetadataGeneration.t.sol`](./test/Test_MetadataGeneration.t.sol), [`test/CroptopAttacks.t.sol`](./test/CroptopAttacks.t.sol) |
18
+ | Deployment, ownership, and fork coverage | [`test/CTDeployer.t.sol`](./test/CTDeployer.t.sol), [`test/CTProjectOwner.t.sol`](./test/CTProjectOwner.t.sol), [`test/ClaimCollectionOwnership.t.sol`](./test/ClaimCollectionOwnership.t.sol), [`test/Fork.t.sol`](./test/Fork.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol) |
17
19
 
18
20
  ## Repo Map
19
21
 
@@ -37,5 +39,8 @@ Permissioned publishing layer for Juicebox 721 projects. Project owners define p
37
39
 
38
40
  - Start in [`src/CTPublisher.sol`](./src/CTPublisher.sol) for posting-rule and fee behavior, but check [`src/CTDeployer.sol`](./src/CTDeployer.sol) when the bug might come from project shape or hook forwarding.
39
41
  - Treat posting criteria, fee routing, and duplicate-content handling as treasury-sensitive and product-sensitive at the same time.
42
+ - Category policy is part of the product surface. Changes to allowed addresses, supply bounds, or split caps alter what can be published, not just how it is paid for.
40
43
  - If the task mentions project immutability or admin recovery, inspect [`src/CTProjectOwner.sol`](./src/CTProjectOwner.sol) before changing deployer or publisher code.
44
+ - Metadata bugs can be publishing bugs, resolver-shape bugs, or duplicate-content bugs. Check all three before assuming a string-formatting issue.
45
+ - Duplicate-post and tier-reuse behavior are first-class runtime semantics. Do not treat them like cacheable convenience logic.
41
46
  - When a bug looks like generic 721 issuance, confirm it is not actually in `nana-721-hook-v6`.
package/USER_JOURNEYS.md CHANGED
@@ -1,56 +1,118 @@
1
1
  # User Journeys
2
2
 
3
- ## Who This Repo Serves
3
+ ## Repo Purpose
4
4
 
5
- - project owners turning a Juicebox 721 project into a publishing marketplace
6
- - publishers creating or reusing NFT tiers as posts
7
- - operators locking project administration into Croptop-specific ownership patterns
5
+ This repo turns a Juicebox 721 project into a permissioned publishing system.
6
+ It owns post validation, Croptop fee routing, and the deployment packaging that turns a project into a Croptop-managed
7
+ publisher. It does not own the base terminal accounting or the underlying 721 tier mechanics it wraps.
8
+
9
+ ## Primary Actors
10
+
11
+ - project owners creating a Croptop publishing surface
12
+ - publishers minting posts into an existing Croptop project
13
+ - auditors reviewing fee routing, posting policy, and owner-lock semantics
14
+
15
+ ## Key Surfaces
16
+
17
+ - `CTPublisher`: validates posts, adjusts tiers, mints the first copy, and routes Croptop fees
18
+ - `CTDeployer`: launches a Croptop-shaped project and can compose omnichain deployment
19
+ - `CTProjectOwner`: owner helper that can burn-lock administration into Croptop
20
+ - `mintFrom(...)`: main publishing entrypoint for new content
8
21
 
9
22
  ## Journey 1: Turn A Project Into A Croptop Publisher
10
23
 
11
- **Starting state:** a project already exists or is about to launch, and the owner wants category-level posting rules.
24
+ **Actor:** project owner.
25
+
26
+ **Intent:** install Croptop publishing policy on a project.
12
27
 
13
- **Success:** Croptop posting criteria are installed and future posts must satisfy them.
28
+ **Preconditions**
29
+ - the project already exists or will be launched through `CTDeployer`
30
+ - the owner has chosen category rules and the expected 721 hook shape
14
31
 
15
- **Flow**
16
- 1. Configure category-level posting constraints such as price floor, supply bounds, split limits, and optional allowlists.
17
- 2. Install or verify the 721 hook shape the project expects.
18
- 3. Route the project through Croptop's publisher logic so future post creation is policy-checked instead of free-form tier editing.
32
+ **Main Flow**
33
+ 1. Configure category-level constraints such as price floor, supply, splits, and allowlists.
34
+ 2. Install or verify the expected 721 hook setup.
35
+ 3. Route publishing through Croptop so future posts are policy-checked instead of free-form tier edits.
36
+
37
+ **Failure Modes**
38
+ - category rules do not match the intended publishing product
39
+ - teams assume Croptop replaces the need to audit the underlying 721 hook
40
+
41
+ **Postconditions**
42
+ - the project now routes publishing through Croptop policy rather than direct free-form tier creation
19
43
 
20
44
  ## Journey 2: Publish Content Into An Existing Croptop Project
21
45
 
22
- **Starting state:** a publisher has a post that satisfies the target project's posting rules.
46
+ **Actor:** publisher.
47
+
48
+ **Intent:** publish one post into a Croptop project and mint the first copy.
49
+
50
+ **Preconditions**
51
+ - the post satisfies the target project's category policy
52
+ - the caller can receive ETH if the fee refund fallback is needed
53
+ - duplicate-content and stale-tier implications are understood
23
54
 
24
- **Success:** the post becomes a valid 721 tier and the first mint settles correctly.
55
+ **Main Flow**
56
+ 1. Call `mintFrom(...)` with the content URI and pricing data.
57
+ 2. `CTPublisher` validates the post against category and fee policy.
58
+ 3. It creates or reuses the underlying tier, mints the first copy, and routes project revenue plus the Croptop fee.
25
59
 
26
- **Flow**
27
- 1. The publisher calls `mintFrom(...)` or the equivalent publishing surface with the content URI and pricing data.
28
- 2. `CTPublisher` checks the post against category rules and fee policy.
29
- 3. It creates or reuses the underlying 721 tier, mints the first copy, and routes both project revenue and the Croptop fee. If the fee terminal is unavailable, the fee is refunded to `_msgSender()` instead.
60
+ **Failure Modes**
61
+ - duplicate URIs or stale tier mappings
62
+ - publisher inputs satisfy the base 721 hook but violate Croptop's stricter rules
63
+ - the fee terminal rejects the fee payment and `_msgSender()` cannot receive the refund
30
64
 
31
- **Failure cases that matter:** duplicate URIs, split configurations that evade fees, stale tier mappings, publisher inputs that satisfy the 721 hook but violate Croptop's stricter publishing rules, and callers that cannot receive ETH when a fee refund fallback is needed.
65
+ **Postconditions**
66
+ - the post is minted or reused as a tier under Croptop policy and the fee path is accounted for
32
67
 
33
68
  ## Journey 3: Launch A New Croptop Project End To End
34
69
 
35
- **Starting state:** the product wants a fresh project that already has Croptop deployment choices baked in.
70
+ **Actor:** product team or deployer.
36
71
 
37
- **Success:** one deployment flow launches the project, wires the 721 hook, and installs the initial posting rules.
72
+ **Intent:** launch a project already wired for Croptop publishing.
38
73
 
39
- **Flow**
40
- 1. Use `CTDeployer` with project config, posting rules, and any omnichain deployment config.
41
- 2. The deployer launches the Juicebox project, configures the Croptop-specific owner model, and wires in publisher behavior.
42
- 3. The project is ready for publishers without a manual post-launch setup phase.
74
+ **Preconditions**
75
+ - the team has project config, posting rules, and any omnichain requirements ready
76
+ - the correct `FEE_PROJECT_ID` is known for deployment
77
+
78
+ **Main Flow**
79
+ 1. Use `CTDeployer` with project config, posting rules, and optional omnichain config.
80
+ 2. The deployer launches the project, configures Croptop ownership assumptions, and wires publisher behavior.
81
+ 3. The resulting project is ready for publishers without a manual post-launch setup gap.
82
+
83
+ **Failure Modes**
84
+ - the fee project is misconfigured or omitted
85
+ - teams treat `CTDeployer` as packaging only and miss its policy implications
86
+
87
+ **Postconditions**
88
+ - the resulting project is ready for Croptop publishers without a post-launch wiring gap
43
89
 
44
90
  ## Journey 4: Lock Administration Into Croptop's Owner Surface
45
91
 
46
- **Starting state:** the project should continue operating through Croptop's policy surface instead of ordinary project-owner discretion.
92
+ **Actor:** project owner.
93
+
94
+ **Intent:** keep governance inside Croptop's constrained owner surface.
47
95
 
48
- **Success:** the project's admin path is burn-locked or otherwise routed through `CTProjectOwner`.
96
+ **Preconditions**
97
+ - the owner wants irreversible product-shaping constraints, not ordinary owner flexibility
49
98
 
50
- **Flow**
51
- 1. Transfer or configure ownership so Croptop's owner helper controls the relevant admin surface.
99
+ **Main Flow**
100
+ 1. Transfer or configure ownership so `CTProjectOwner` controls the relevant admin surface.
52
101
  2. Restrict future edits to the paths Croptop intentionally exposes.
53
- 3. Accept that this is a product-shaping choice, not a cosmetic deployment detail.
102
+ 3. Accept that this is an ownership-model decision, not cosmetic packaging.
103
+
104
+ **Failure Modes**
105
+ - teams burn-lock before validating the publishing policy in production-like conditions
106
+ - reviewers miss that prior owner discretion no longer exists directly
107
+
108
+ **Postconditions**
109
+ - future administration is constrained to the Croptop owner surface instead of ordinary owner discretion
110
+
111
+ ## Trust Boundaries
112
+
113
+ - this repo is trusted for publishing policy and fee routing
114
+ - the underlying 721 hook remains trusted for tier issuance and lower-level NFT accounting
115
+ - Croptop fee behavior depends on the fee project and its terminal remaining correctly configured
54
116
 
55
117
  ## Hand-Offs
56
118
 
package/foundry.toml CHANGED
@@ -16,6 +16,8 @@ fail_on_revert = false
16
16
  [rpc_endpoints]
17
17
  ethereum = "${RPC_ETHEREUM_MAINNET}"
18
18
 
19
+ [lint]
20
+ exclude_lints = ["pascal-case-struct", "mixed-case-variable"]
19
21
  [fmt]
20
22
  number_underscore = "thousands"
21
23
  multiline_func_header = "all"
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
1
  {
2
- "name": "@croptop/core-v6",
3
- "version": "0.0.33",
4
- "license": "MIT",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/mejango/croptop-core-v6"
8
- },
9
- "scripts": {
10
- "test": "forge test",
11
- "coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
12
- "deploy:mainnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
13
- "deploy:mainnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks mainnets",
14
- "deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
15
- "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'"
17
- },
18
- "dependencies": {
19
- "@bananapus/721-hook-v6": "^0.0.33",
20
- "@bananapus/buyback-hook-v6": "^0.0.27",
21
- "@bananapus/core-v6": "^0.0.34",
22
- "@bananapus/ownable-v6": "^0.0.17",
23
- "@bananapus/permission-ids-v6": "^0.0.17",
24
- "@bananapus/router-terminal-v6": "^0.0.26",
25
- "@bananapus/suckers-v6": "^0.0.25",
26
- "@openzeppelin/contracts": "^5.6.1"
27
- },
28
- "devDependencies": {
29
- "@bananapus/address-registry-v6": "^0.0.17",
30
- "@rev-net/core-v6": "^0.0.31",
31
- "@sphinx-labs/plugins": "^0.33.3"
32
- }
2
+ "name": "@croptop/core-v6",
3
+ "version": "0.0.34",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/mejango/croptop-core-v6"
8
+ },
9
+ "scripts": {
10
+ "test": "forge test",
11
+ "coverage:integration": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
12
+ "deploy:mainnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
13
+ "deploy:mainnets:project": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/ConfigureFeeProject.s.sol --networks mainnets",
14
+ "deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
15
+ "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'"
17
+ },
18
+ "dependencies": {
19
+ "@bananapus/721-hook-v6": "^0.0.35",
20
+ "@bananapus/buyback-hook-v6": "^0.0.27",
21
+ "@bananapus/core-v6": "^0.0.34",
22
+ "@bananapus/ownable-v6": "^0.0.17",
23
+ "@bananapus/permission-ids-v6": "^0.0.17",
24
+ "@bananapus/router-terminal-v6": "^0.0.26",
25
+ "@bananapus/suckers-v6": "^0.0.25",
26
+ "@openzeppelin/contracts": "^5.6.1"
27
+ },
28
+ "devDependencies": {
29
+ "@bananapus/address-registry-v6": "^0.0.17",
30
+ "@rev-net/core-v6": "^0.0.32",
31
+ "@sphinx-labs/plugins": "^0.33.3"
32
+ }
33
33
  }
@@ -21,5 +21,5 @@
21
21
 
22
22
  ## Useful Proof Points
23
23
 
24
- - [`test/fork/`](../test/fork/) when deployment shape matters.
25
- - [`script/helpers/`](../script/helpers/) if the issue is really script/config assembly.
24
+ - [`test/Fork.t.sol`](../test/Fork.t.sol) when deployment shape matters.
25
+ - [`script/ConfigureFeeProject.s.sol`](../script/ConfigureFeeProject.s.sol) if the issue is really script/config assembly.
@@ -22,6 +22,6 @@
22
22
 
23
23
  ## Tests To Trust First
24
24
 
25
- - [`test/regression/`](../test/regression/) for pinned content and tier edge cases.
26
- - [`test/fork/`](../test/fork/) for live integration assumptions.
27
- - [`test/`](../test/) broadly when the issue could be in publisher or deployer behavior rather than one isolated function.
25
+ - [`test/CTPublisher.t.sol`](../test/CTPublisher.t.sol) and [`test/Test_MetadataGeneration.t.sol`](../test/Test_MetadataGeneration.t.sol) for content and metadata behavior.
26
+ - [`test/CTDeployer.t.sol`](../test/CTDeployer.t.sol) and [`test/Fork.t.sol`](../test/Fork.t.sol) for live deployment assumptions.
27
+ - [`test/CroptopAttacks.t.sol`](../test/CroptopAttacks.t.sol) and [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) when the issue could be in publisher or deployer behavior rather than one isolated function.
@@ -27,7 +27,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
27
27
  // --------------------------- custom errors ------------------------- //
28
28
  //*********************************************************************//
29
29
 
30
- // forge-lint: disable-next-line(mixed-case-variable)
31
30
  error CTPublisher_DuplicatePost(bytes32 encodedIPFSUri);
32
31
  error CTPublisher_EmptyEncodedIPFSUri();
33
32
  error CTPublisher_InsufficientEthSent(uint256 expected, uint256 sent);
@@ -66,7 +65,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
66
65
  /// @notice The ID of the tier that an IPFS metadata has been saved to.
67
66
  /// @custom:param hook The hook for which the tier ID applies.
68
67
  /// @custom:param encodedIPFSUri The IPFS URI.
69
- // forge-lint: disable-next-line(mixed-case-variable)
70
68
  mapping(address hook => mapping(bytes32 encodedIPFSUri => uint256)) public override tierIdForEncodedIPFSUriOf;
71
69
 
72
70
  //*********************************************************************//
@@ -324,7 +322,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
324
322
  /// is returned.
325
323
  function tiersFor(
326
324
  address hook,
327
- // forge-lint: disable-next-line(mixed-case-variable)
328
325
  bytes32[] memory encodedIPFSUris
329
326
  )
330
327
  external
@@ -332,7 +329,6 @@ contract CTPublisher is JBPermissioned, ERC2771Context, ICTPublisher {
332
329
  override
333
330
  returns (JB721Tier[] memory tiers)
334
331
  {
335
- // forge-lint: disable-next-line(mixed-case-variable)
336
332
  uint256 numberOfEncodedIPFSUris = encodedIPFSUris.length;
337
333
 
338
334
  // Initialize the tier array being returned.
@@ -73,7 +73,7 @@ interface ICTPublisher {
73
73
  /// @param hook The hook for which the tier ID applies.
74
74
  /// @param encodedIPFSUri The encoded IPFS URI to look up.
75
75
  /// @return The tier ID, or 0 if the URI has not been published.
76
- // forge-lint: disable-next-line(mixed-case-function, mixed-case-variable)
76
+ // forge-lint: disable-next-line(mixed-case-function)
77
77
  function tierIdForEncodedIPFSUriOf(address hook, bytes32 encodedIPFSUri) external view returns (uint256);
78
78
 
79
79
  /// @notice Get the tiers for the provided encoded IPFS URIs.
@@ -81,7 +81,6 @@ interface ICTPublisher {
81
81
  /// @param encodedIPFSUris The URIs to get tiers of.
82
82
  /// @return tiers The tiers that correspond to the provided encoded IPFS URIs. Empty tiers are returned for URIs
83
83
  /// without a tier.
84
- // forge-lint: disable-next-line(mixed-case-variable)
85
84
  function tiersFor(address hook, bytes32[] memory encodedIPFSUris) external view returns (JB721Tier[] memory tiers);
86
85
 
87
86
  /// @notice Configure the allowed criteria for publishing new NFTs to a hook.
@@ -11,7 +11,6 @@ pragma solidity ^0.8.0;
11
11
  /// @custom:member maximumSplitPercent The maximum split percent (out of JBConstants.SPLITS_TOTAL_PERCENT) that a
12
12
  /// poster can set. 0 means splits are not allowed.
13
13
  /// @custom:member allowedAddresses A list of addresses that are allowed to post on the category through Croptop.
14
- // forge-lint: disable-next-line(pascal-case-struct)
15
14
  struct CTAllowedPost {
16
15
  address hook;
17
16
  uint24 category;
@@ -10,7 +10,6 @@ pragma solidity ^0.8.0;
10
10
  /// @custom:member maximumSplitPercent The maximum split percent (out of JBConstants.SPLITS_TOTAL_PERCENT) that a
11
11
  /// poster can set. 0 means splits are not allowed.
12
12
  /// @custom:member allowedAddresses A list of addresses that are allowed to post on the category through Croptop.
13
- // forge-lint: disable-next-line(pascal-case-struct)
14
13
  struct CTDeployerAllowedPost {
15
14
  uint24 category;
16
15
  uint104 minimumPrice;
@@ -12,9 +12,7 @@ import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
12
12
  /// @custom:member splitPercent The percent of the tier's price to route to the splits (out of
13
13
  /// JBConstants.SPLITS_TOTAL_PERCENT).
14
14
  /// @custom:member splits The splits to route funds to when this tier is minted.
15
- // forge-lint: disable-next-line(pascal-case-struct)
16
15
  struct CTPost {
17
- // forge-lint: disable-next-line(mixed-case-variable)
18
16
  bytes32 encodedIPFSUri;
19
17
  uint32 totalSupply;
20
18
  uint104 price;
@@ -11,7 +11,6 @@ import {CTDeployerAllowedPost} from "../structs/CTDeployerAllowedPost.sol";
11
11
  /// @param name The name of the collection where posts will go.
12
12
  /// @param symbol The symbol of the collection where posts will go.
13
13
  /// @param salt A salt to use for the deterministic deployment.
14
- // forge-lint: disable-next-line(pascal-case-struct)
15
14
  struct CTProjectConfig {
16
15
  JBTerminalConfig[] terminalConfigurations;
17
16
  string projectUri;
@@ -5,7 +5,6 @@ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSucker
5
5
 
6
6
  /// @custom:member deployerConfigurations The information for how to suck tokens to other chains.
7
7
  /// @custom:member salt The salt to use for creating suckers so that they use the same address across chains.
8
- // forge-lint: disable-next-line(pascal-case-struct)
9
8
  struct CTSuckerDeploymentConfig {
10
9
  JBSuckerDeployerConfig[] deployerConfigurations;
11
10
  bytes32 salt;