@croptop/core-v6 0.0.27 → 0.0.29
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 +30 -3
- package/ARCHITECTURE.md +49 -157
- package/AUDIT_INSTRUCTIONS.md +69 -485
- package/CHANGELOG.md +57 -0
- package/README.md +54 -137
- package/RISKS.md +27 -3
- package/SKILLS.md +28 -187
- package/STYLE_GUIDE.md +56 -17
- package/USER_JOURNEYS.md +37 -708
- package/package.json +5 -6
- package/references/operations.md +25 -0
- package/references/runtime.md +27 -0
- package/script/Deploy.s.sol +4 -23
- package/src/CTDeployer.sol +5 -1
- package/src/CTPublisher.sol +16 -15
- package/test/CTPublisher.t.sol +10 -7
- package/test/audit/DeployerPermissionBypass.t.sol +213 -0
- package/test/audit/FeeFallbackBlackhole.t.sol +263 -0
- package/test/regression/FeeEvasion.t.sol +15 -10
- package/test/regression/StaleTierIdMapping.t.sol +8 -5
- package/CHANGE_LOG.md +0 -273
package/ADMINISTRATION.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
Admin privileges and their scope in croptop-core-v6.
|
|
4
4
|
|
|
5
|
+
## At A Glance
|
|
6
|
+
|
|
7
|
+
| 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.
|
|
20
|
+
|
|
21
|
+
## One-Way Or High-Risk Actions
|
|
22
|
+
|
|
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.
|
|
26
|
+
|
|
27
|
+
## Recovery Notes
|
|
28
|
+
|
|
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.
|
|
31
|
+
|
|
5
32
|
## Roles
|
|
6
33
|
|
|
7
34
|
### 1. Project Owner
|
|
@@ -69,7 +96,7 @@ Admin privileges and their scope in croptop-core-v6.
|
|
|
69
96
|
|
|
70
97
|
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
71
98
|
|----------|--------------|---------------|-------|-------------|
|
|
72
|
-
| `onERC721Received()` | Anyone who transfers a JBProjects NFT | None | Per-project | On receiving a project NFT from `PROJECTS
|
|
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. |
|
|
73
100
|
|
|
74
101
|
### Permissions Granted at CTDeployer Construction
|
|
75
102
|
|
|
@@ -134,7 +161,7 @@ What admins CANNOT do:
|
|
|
134
161
|
|
|
135
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.
|
|
136
163
|
|
|
137
|
-
5. **Project owners cannot bypass posting criteria
|
|
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.
|
|
138
165
|
|
|
139
166
|
6. **CTPublisher cannot mint without paying.** `mintFrom()` requires `msg.value >= totalPrice + fee`. There is no free-mint path through CTPublisher.
|
|
140
167
|
|
|
@@ -142,6 +169,6 @@ What admins CANNOT do:
|
|
|
142
169
|
|
|
143
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.
|
|
144
171
|
|
|
145
|
-
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
|
|
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.
|
|
146
173
|
|
|
147
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.
|
package/ARCHITECTURE.md
CHANGED
|
@@ -1,179 +1,71 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Architecture
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
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.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Boundaries
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
├── CTProjectOwner.sol — Proxy owner for Croptop-deployed projects
|
|
14
|
-
├── interfaces/
|
|
15
|
-
│ ├── ICTDeployer.sol
|
|
16
|
-
│ ├── ICTProjectOwner.sol
|
|
17
|
-
│ └── ICTPublisher.sol
|
|
18
|
-
└── structs/
|
|
19
|
-
├── CTAllowedPost.sol — Rules for what can be posted
|
|
20
|
-
├── CTDeployerAllowedPost.sol — Deployer-level post rules (no hook field)
|
|
21
|
-
├── CTPost.sol — A post submission
|
|
22
|
-
├── CTProjectConfig.sol — Project configuration
|
|
23
|
-
└── CTSuckerDeploymentConfig.sol — Cross-chain config
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Key Data Flows
|
|
27
|
-
|
|
28
|
-
### Project Deployment
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
Creator → CTDeployer.deployProjectFor(owner, projectConfig, suckerConfig, controller)
|
|
32
|
-
1. Deploy 721 hook via IJB721TiersHookDeployer (empty tiers, ETH currency)
|
|
33
|
-
2. Launch JB project with:
|
|
34
|
-
→ weight = 1,000,000 * 10^18
|
|
35
|
-
→ cashOutTaxRate = MAX (100%)
|
|
36
|
-
→ dataHook = CTDeployer itself (see "Data Hook Behavior" below)
|
|
37
|
-
→ useDataHookForPay = true, useDataHookForCashOut = true
|
|
38
|
-
3. Store dataHookOf[projectId] = the 721 hook (for pay forwarding)
|
|
39
|
-
4. Configure allowed post rules on CTPublisher
|
|
40
|
-
5. Deploy suckers for cross-chain support (if configured)
|
|
41
|
-
6. Transfer project NFT to the specified owner
|
|
42
|
-
7. Grant owner permissions: ADJUST_721_TIERS, SET_721_METADATA, MINT_721, SET_721_DISCOUNT_PERCENT
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Content Publishing (mintFrom)
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
Publisher → CTPublisher.mintFrom(hook, posts[], nftBeneficiary, feeBeneficiary, ...)
|
|
49
|
-
→ _setupPosts: for each post:
|
|
50
|
-
1. Reject empty encodedIPFSUri
|
|
51
|
-
2. Reject duplicate encodedIPFSUri within the batch
|
|
52
|
-
3. If tier already exists for this encodedIPFSUri:
|
|
53
|
-
→ Use existing tier ID, add tier's price to totalPrice
|
|
54
|
-
→ (Prevents fee evasion by using actual on-chain price, not user-supplied)
|
|
55
|
-
4. If tier is new:
|
|
56
|
-
→ Load allowance for (hook, category) — see "Allowed Post Rules" below
|
|
57
|
-
→ Validate: category enabled, price >= minimum, supply in range,
|
|
58
|
-
splitPercent <= maximum, caller in allowlist (if restricted)
|
|
59
|
-
→ Create JB721TierConfig with the post's price, supply, category,
|
|
60
|
-
splitPercent, and splits array
|
|
61
|
-
→ Record tierIdForEncodedIPFSUriOf mapping
|
|
62
|
-
→ Add post.price to totalPrice
|
|
63
|
-
→ Calculate fee: totalPrice / FEE_DIVISOR (5% fee, FEE_DIVISOR = 20)
|
|
64
|
-
Fee is skipped when projectId == FEE_PROJECT_ID
|
|
65
|
-
→ adjustTiers on the 721 hook to add new tiers
|
|
66
|
-
→ Pay the project terminal: payValue = msg.value - fee
|
|
67
|
-
Metadata encodes tier IDs to mint, so the 721 hook mints one NFT per post
|
|
68
|
-
→ Pay pre-computed fee to FEE_PROJECT_ID terminal (try-catch; falls back to feeBeneficiary, then msg.sender)
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Allowed Post Rules
|
|
72
|
-
|
|
73
|
-
Each category on each 721 hook has an allowance stored as bit-packed values in `_packedAllowanceFor[hook][category]`. The project owner configures these via `configurePostingCriteriaFor`.
|
|
74
|
-
|
|
75
|
-
| Field | Type | Bits | Purpose |
|
|
76
|
-
|-------|------|------|---------|
|
|
77
|
-
| `minimumPrice` | `uint104` | 0-103 | Floor price per NFT. Posts below this revert. |
|
|
78
|
-
| `minimumTotalSupply` | `uint32` | 104-135 | Minimum editions. Must be >= 1; a zero value means the category is disabled. |
|
|
79
|
-
| `maximumTotalSupply` | `uint32` | 136-167 | Maximum editions. Must be >= minimumTotalSupply. |
|
|
80
|
-
| `maximumSplitPercent` | `uint32` | 168-199 | Cap on the publisher's split (out of `SPLITS_TOTAL_PERCENT = 1,000,000,000`). 0 means no splits allowed. |
|
|
81
|
-
| `allowedAddresses` | `address[]` | separate storage | If non-empty, only these addresses may post in this category. Empty means anyone can post. |
|
|
82
|
-
|
|
83
|
-
Validation order in `_setupPosts`: category enabled (minimumTotalSupply != 0) -> price check -> supply range check -> split percent cap -> allowlist check.
|
|
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.
|
|
84
13
|
|
|
85
|
-
|
|
14
|
+
## Main Components
|
|
86
15
|
|
|
87
|
-
|
|
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 |
|
|
88
22
|
|
|
89
|
-
|
|
23
|
+
## Runtime Model
|
|
90
24
|
|
|
91
|
-
|
|
25
|
+
### Publishing
|
|
92
26
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Mint permission: Only suckers get on-demand mint permission
|
|
27
|
+
```text
|
|
28
|
+
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
|
|
32
|
+
-> 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
|
|
102
35
|
```
|
|
103
36
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
### Why a Proxy Owner?
|
|
107
|
-
|
|
108
|
-
When CTDeployer creates a project, it initially owns the project NFT (because `launchProjectFor` mints to `msg.sender`). CTDeployer needs to be the initial owner so it can:
|
|
109
|
-
1. Configure posting criteria on CTPublisher (requires `ADJUST_721_TIERS` permission from the hook's owner, which is CTDeployer).
|
|
110
|
-
2. Deploy suckers (requires project ownership).
|
|
111
|
-
3. Grant the final owner NFT management permissions.
|
|
112
|
-
|
|
113
|
-
After setup, CTDeployer transfers the project NFT to the specified `owner`. The 721 hook's ownership stays with CTDeployer, which has granted `ADJUST_721_TIERS` permission to CTPublisher with `projectId = 0` (wildcard). This means CTPublisher can add tiers to any Croptop-deployed project without further permission grants.
|
|
114
|
-
|
|
115
|
-
### CTProjectOwner: The Immutable-Rules Pattern
|
|
116
|
-
|
|
117
|
-
`CTProjectOwner` is a separate contract that serves as a "lockbox" for projects that want immutable posting rules. When the project owner transfers their project NFT to a CTProjectOwner instance:
|
|
118
|
-
|
|
119
|
-
1. `onERC721Received` fires and grants CTPublisher the `ADJUST_721_TIERS` permission for that project.
|
|
120
|
-
2. CTProjectOwner exposes no function to reconfigure posting criteria, queue new rulesets, or transfer the project further.
|
|
121
|
-
3. The posting rules become permanently frozen -- content can still be published under the existing rules, but the rules themselves cannot change.
|
|
122
|
-
|
|
123
|
-
This enables a trust model: creators can prove to publishers that the rules (price floors, supply caps, split percentages) will never change, providing a credible commitment that the economic terms are permanent.
|
|
124
|
-
|
|
125
|
-
### Claiming Collection Ownership
|
|
126
|
-
|
|
127
|
-
`CTDeployer.claimCollectionOwnershipOf(hook)` allows the project NFT holder to take direct ownership of the 721 hook by calling `JBOwnable.transferOwnershipToProject(projectId)`. After this, the hook's owner resolves to whoever holds the project NFT. The caller must then independently grant CTPublisher the `ADJUST_721_TIERS` permission, or subsequent posts will revert.
|
|
128
|
-
|
|
129
|
-
## Publisher Fee and Split Mechanics
|
|
130
|
-
|
|
131
|
-
### Fee Structure
|
|
132
|
-
|
|
133
|
-
CTPublisher charges a 5% fee (`FEE_DIVISOR = 20`) on the total price of all posts in a `mintFrom` call. The fee is skipped when the target project is the fee project itself (`FEE_PROJECT_ID`).
|
|
37
|
+
### Project Launch
|
|
134
38
|
|
|
39
|
+
```text
|
|
40
|
+
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
|
|
135
43
|
```
|
|
136
|
-
msg.value breakdown:
|
|
137
|
-
totalPrice → paid to the project's terminal (mints NFTs + project tokens)
|
|
138
|
-
totalPrice/20 → paid to FEE_PROJECT_ID's terminal (Croptop platform fee)
|
|
139
|
-
remainder → reverts if insufficient, excess stays as overpayment
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Publisher Split Mechanism
|
|
143
|
-
|
|
144
|
-
When a publisher creates a new post, they can set a `splitPercent` and a `splits` array on their `CTPost`. These are stored directly on the 721 tier via `JB721TierConfig.splitPercent` and `JB721TierConfig.splits`.
|
|
145
|
-
|
|
146
|
-
The split mechanics work at the 721 hook level: whenever someone mints (buys) an NFT from that tier, the 721 hook routes `splitPercent` of the tier's price to the addresses in the `splits` array. The publisher typically sets themselves as a split recipient so they earn a share of every future mint from the tier they created.
|
|
147
|
-
|
|
148
|
-
The project owner controls the maximum split a publisher can claim via `maximumSplitPercent` in the allowed post rules. If `maximumSplitPercent` is 0, publishers cannot set any splits. This gives project owners control over how much revenue publishers can capture versus how much flows to the project treasury.
|
|
149
44
|
|
|
150
|
-
##
|
|
45
|
+
## Critical Invariants
|
|
151
46
|
|
|
152
|
-
|
|
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.
|
|
153
52
|
|
|
154
|
-
|
|
53
|
+
## Where Complexity Lives
|
|
155
54
|
|
|
156
|
-
|
|
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.
|
|
157
58
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
**Category-based organization.** Posts are organized by `category` (uint24), with each category having independent allowance rules. This lets a single project support multiple content types (e.g., category 1 for images at 0.01 ETH, category 2 for music at 0.1 ETH) with different price floors, supply limits, and access controls.
|
|
161
|
-
|
|
162
|
-
**Duplicate prevention via encodedIPFSUri mapping.** `tierIdForEncodedIPFSUriOf[hook][encodedIPFSUri]` ensures each piece of content can only create one tier. If a tier already exists, the mint uses the existing tier (at its on-chain price, not the caller-supplied price). This prevents duplicate tiers and fee evasion.
|
|
163
|
-
|
|
164
|
-
## Extension Points
|
|
59
|
+
## Dependencies
|
|
165
60
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
| 721 hook | `IJB721TiersHook` | NFT tier management, split routing on mints |
|
|
170
|
-
| Publisher | `ICTPublisher` | Content posting workflow and allowance configuration |
|
|
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
|
|
171
64
|
|
|
172
|
-
##
|
|
65
|
+
## Safe Change Guide
|
|
173
66
|
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
- `@openzeppelin/contracts` -- ERC2771 (meta-transactions), ERC721Receiver
|
|
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.
|