@croptop/core-v6 0.0.33 → 0.0.35

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 usually 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 with 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
+ Important nuance:
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
+ - that 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.
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 review.
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 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 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.
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 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.
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 patterns from `nana-suckers-v6` and `nana-omnichain-deployers-v6`.
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 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.
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,92 +1,88 @@
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
9
  - let publishers create or mint posts outside configured criteria
9
10
  - let users evade Croptop fees or route them incorrectly
10
11
  - 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
12
+ - make tier reuse bypass stale-content, fee, or policy checks
13
+ - leave a project in an unintended ownership or admin state
13
14
 
14
15
  ## Scope
15
16
 
16
17
  In scope:
18
+
17
19
  - `src/CTPublisher.sol`
18
20
  - `src/CTDeployer.sol`
19
21
  - `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
22
+ - all interfaces in `src/interfaces/`
23
+ - all structs in `src/structs/`
24
+ - deployment helpers in `script/`
43
25
 
44
- ## Critical Invariants
26
+ ## Start Here
27
+
28
+ 1. `src/CTPublisher.sol`
29
+ 2. `src/CTDeployer.sol`
30
+ 3. `src/CTProjectOwner.sol`
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
+ ## Security Model
48
33
 
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.
34
+ Croptop composes several subsystems:
51
35
 
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.
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
54
39
 
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.
40
+ Trust boundaries that matter:
57
41
 
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.
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
60
45
 
61
- ## Threat Model
46
+ ## Roles And Privileges
62
47
 
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
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 |
69
54
 
70
- ## Hotspots
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
71
65
 
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
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.
78
71
 
79
- ## Build And Verification
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
80
85
 
81
- Standard workflow:
82
86
  - `npm install`
83
87
  - `forge build`
84
88
  - `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.