@croptop/core-v6 0.0.27 → 0.0.28
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 +28 -1
- package/ARCHITECTURE.md +48 -157
- package/AUDIT_INSTRUCTIONS.md +69 -485
- package/CHANGELOG.md +57 -0
- package/README.md +48 -137
- package/RISKS.md +18 -1
- package/SKILLS.md +28 -187
- package/STYLE_GUIDE.md +56 -17
- package/USER_JOURNEYS.md +37 -708
- package/package.json +2 -2
- package/references/operations.md +25 -0
- package/references/runtime.md +27 -0
- package/src/CTPublisher.sol +10 -7
- package/test/CTPublisher.t.sol +10 -7
- package/test/regression/FeeEvasion.t.sol +15 -10
- package/test/regression/StaleTierIdMapping.t.sol +8 -5
- package/CHANGE_LOG.md +0 -273
package/README.md
CHANGED
|
@@ -1,99 +1,41 @@
|
|
|
1
1
|
# Croptop Core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Croptop turns a Juicebox project with a 721 hook into a permissioned publishing marketplace. Project owners define posting criteria, then anyone who meets those rules can publish new NFT tiers and mint the first copy of each post.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Docs: <https://docs.juicebox.money>
|
|
6
|
+
Site: <https://croptop.eth.limo>
|
|
7
|
+
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Overview
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Croptop is built around three ideas:
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
- project owners set category-level posting criteria such as price floors, supply bounds, split limits, and optional allowlists
|
|
14
|
+
- publishers call `mintFrom` to create or reuse 721 tiers that represent their post
|
|
15
|
+
- a one-click deployer can create a full Juicebox project, its 721 hook configuration, and its posting rules in a single transaction
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
Every mint collects a 5% Croptop fee unless the target project is itself the fee project.
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
1. Project owner configures posting criteria per category
|
|
17
|
-
→ configurePostingCriteriaFor(allowedPosts)
|
|
18
|
-
→ Sets min price, supply bounds, max split %, address allowlist
|
|
19
|
-
|
|
|
20
|
-
2. Anyone posts content that meets the criteria
|
|
21
|
-
→ mintFrom(hook, posts, nftBeneficiary, feeBeneficiary, ...)
|
|
22
|
-
→ Validates each post against category rules
|
|
23
|
-
→ Creates new 721 tiers on the hook (or reuses existing ones)
|
|
24
|
-
→ Mints first copy of each tier to the poster
|
|
25
|
-
|
|
|
26
|
-
3. Payment routing
|
|
27
|
-
→ 5% fee (totalPrice / FEE_DIVISOR) sent to fee project
|
|
28
|
-
→ Remainder paid into the project's primary terminal
|
|
29
|
-
|
|
|
30
|
-
4. Anyone can mint additional copies
|
|
31
|
-
→ Standard 721 tier minting via the project's hook
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
```mermaid
|
|
35
|
-
sequenceDiagram
|
|
36
|
-
participant Poster
|
|
37
|
-
participant CTPublisher
|
|
38
|
-
participant Hook as 721 Tiers Hook
|
|
39
|
-
participant Terminal as Project Terminal
|
|
40
|
-
participant FeeTerminal as Fee Project Terminal
|
|
41
|
-
|
|
42
|
-
Poster->>CTPublisher: mintFrom(hook, posts, ...)
|
|
43
|
-
CTPublisher->>CTPublisher: Validate each post against category criteria
|
|
44
|
-
loop For each post
|
|
45
|
-
CTPublisher->>Hook: adjustTiersOf() -- create new tier (or reuse existing)
|
|
46
|
-
end
|
|
47
|
-
CTPublisher->>Terminal: pay() -- total price minus fee
|
|
48
|
-
Note over Terminal: Mints first copy of each tier to poster
|
|
49
|
-
CTPublisher->>FeeTerminal: pay() -- 5% fee (totalPrice / 20)
|
|
50
|
-
Note over FeeTerminal: Routes fee to designated fee project
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Fee Structure
|
|
54
|
-
|
|
55
|
-
Every `mintFrom` call collects a 5% fee on the total tier price. The fee is calculated as `totalPrice / FEE_DIVISOR` where `FEE_DIVISOR = 20`. The fee is paid to the primary ETH terminal of a designated fee project (`FEE_PROJECT_ID`, set at deployment). The remainder goes to the target project's primary terminal as a normal payment.
|
|
56
|
-
|
|
57
|
-
- If the project being posted to **is** the fee project, no fee is collected (avoids circular payments).
|
|
58
|
-
- Integer division truncates, so the fee loses up to 19 wei of dust per mint.
|
|
59
|
-
- The fee amount is pre-computed as `msg.value - payValue` (not derived from `address(this).balance`), so force-sent ETH does not affect fee routing. The fee terminal payment is wrapped in try-catch with a fallback to `feeBeneficiary` then `msg.sender`.
|
|
60
|
-
|
|
61
|
-
### One-Click Deployment
|
|
19
|
+
Use this repo when the product is "permissioned publishing on a Juicebox project." Do not use it when you only need plain 721 tier sales; that belongs in `nana-721-hook-v6`.
|
|
62
20
|
|
|
63
|
-
|
|
64
|
-
- Acts as a data hook proxy, forwarding pay/cash-out calls to the underlying 721 hook
|
|
65
|
-
- Grants fee-free cash outs to cross-chain suckers
|
|
66
|
-
- Optionally deploys suckers for omnichain support
|
|
21
|
+
If a bug looks like ordinary tier issuance or terminal accounting, start in the 721 hook or core repo first. Croptop is where posting policy, fee routing, and publishing-specific project wiring begin.
|
|
67
22
|
|
|
68
|
-
|
|
23
|
+
## Key Contracts
|
|
69
24
|
|
|
70
|
-
|
|
25
|
+
| Contract | Role |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `CTPublisher` | Validates posts, adjusts 721 tiers, mints the first copy, and routes protocol and project payments. |
|
|
28
|
+
| `CTDeployer` | Launches a project, configures Croptop posting rules, and can wire in omnichain sucker deployments. |
|
|
29
|
+
| `CTProjectOwner` | Burn-lock ownership helper that can permanently route project administration through Croptop's publishing surface. |
|
|
71
30
|
|
|
72
|
-
##
|
|
31
|
+
## Mental Model
|
|
73
32
|
|
|
74
|
-
|
|
75
|
-
|----------|-------------|
|
|
76
|
-
| `CTPublisher` | Core publishing engine. Validates posts against owner-configured allowances (min price, supply bounds, address allowlists, max split percent), creates new 721 tiers on the hook, mints the first copy to the poster, and routes a 5% fee to a designated fee project. Inherits `JBPermissioned` and `ERC2771Context`. |
|
|
77
|
-
| `CTDeployer` | One-click project factory. Deploys a Juicebox project with a 721 tiers hook pre-wired, configures posting criteria via `CTPublisher`, optionally deploys cross-chain suckers, and acts as an `IJBRulesetDataHook` proxy that forwards pay/cash-out calls to the underlying hook while granting fee-free cash outs to suckers. |
|
|
78
|
-
| `CTProjectOwner` | Burn-lock ownership helper. Receives a project's ERC-721 ownership token and automatically grants `CTPublisher` the `ADJUST_721_TIERS` permission, effectively making the project's tier configuration immutable except through Croptop posts. |
|
|
33
|
+
There are two separate concerns here:
|
|
79
34
|
|
|
80
|
-
|
|
35
|
+
1. `CTPublisher` governs whether a post is allowed and how it becomes a tier
|
|
36
|
+
2. `CTDeployer` governs how a Croptop-flavored project is packaged and launched
|
|
81
37
|
|
|
82
|
-
|
|
83
|
-
|--------|---------|
|
|
84
|
-
| `CTAllowedPost` | Full posting criteria: hook address, category, price/supply bounds, max split percent, and address allowlist. |
|
|
85
|
-
| `CTDeployerAllowedPost` | Same as `CTAllowedPost` but without the hook address (inferred during deployment). |
|
|
86
|
-
| `CTPost` | A post to publish: encoded IPFS URI, total supply, price, category, split percent, and splits. |
|
|
87
|
-
| `CTProjectConfig` | Project deployment configuration: terminals, metadata URIs, allowed posts, collection name/symbol, and deterministic salt. |
|
|
88
|
-
| `CTSuckerDeploymentConfig` | Cross-chain sucker deployment: deployer configurations and deterministic salt. |
|
|
89
|
-
|
|
90
|
-
### Interfaces
|
|
91
|
-
|
|
92
|
-
| Interface | Description |
|
|
93
|
-
|-----------|-------------|
|
|
94
|
-
| `ICTPublisher` | Publishing engine: `mintFrom`, `configurePostingCriteriaFor`, `allowanceFor`, `tiersFor`, plus events. |
|
|
95
|
-
| `ICTDeployer` | Factory: `deployProjectFor`, `claimCollectionOwnershipOf`, `deploySuckersFor`. |
|
|
96
|
-
| `ICTProjectOwner` | Burn-lock: `onERC721Received` (IERC721Receiver). |
|
|
38
|
+
That distinction matters because many "Croptop bugs" are deployment-shape bugs rather than publishing-rule bugs.
|
|
97
39
|
|
|
98
40
|
## Install
|
|
99
41
|
|
|
@@ -101,76 +43,45 @@ Every `mintFrom` call collects a 5% fee on the total tier price. The fee is calc
|
|
|
101
43
|
npm install @croptop/core-v6
|
|
102
44
|
```
|
|
103
45
|
|
|
104
|
-
|
|
46
|
+
## Development
|
|
105
47
|
|
|
106
48
|
```bash
|
|
107
|
-
|
|
49
|
+
npm install
|
|
50
|
+
forge build
|
|
51
|
+
forge test
|
|
108
52
|
```
|
|
109
53
|
|
|
110
|
-
|
|
54
|
+
Useful scripts:
|
|
55
|
+
|
|
56
|
+
- `npm run deploy:mainnets`
|
|
57
|
+
- `npm run deploy:testnets`
|
|
58
|
+
- `npm run deploy:mainnets:project`
|
|
59
|
+
- `npm run deploy:testnets:project`
|
|
60
|
+
|
|
61
|
+
## Deployment Notes
|
|
111
62
|
|
|
112
|
-
|
|
113
|
-
|---------|-------------|
|
|
114
|
-
| `forge build` | Compile contracts |
|
|
115
|
-
| `forge test` | Run all tests (12 test files covering publishing, deployer, attacks, fork integration, metadata, and regressions) |
|
|
116
|
-
| `forge test -vvv` | Run tests with full trace |
|
|
63
|
+
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.
|
|
117
64
|
|
|
118
65
|
## Repository Layout
|
|
119
66
|
|
|
120
|
-
```
|
|
67
|
+
```text
|
|
121
68
|
src/
|
|
122
|
-
CTPublisher.sol
|
|
123
|
-
CTDeployer.sol
|
|
124
|
-
CTProjectOwner.sol
|
|
69
|
+
CTPublisher.sol
|
|
70
|
+
CTDeployer.sol
|
|
71
|
+
CTProjectOwner.sol
|
|
125
72
|
interfaces/
|
|
126
|
-
ICTPublisher.sol # Publisher interface + events
|
|
127
|
-
ICTDeployer.sol # Factory interface
|
|
128
|
-
ICTProjectOwner.sol # Burn-lock interface
|
|
129
73
|
structs/
|
|
130
|
-
CTAllowedPost.sol # Posting criteria with hook address
|
|
131
|
-
CTDeployerAllowedPost.sol # Posting criteria without hook (for deployment)
|
|
132
|
-
CTPost.sol # Post data: IPFS URI, supply, price, category
|
|
133
|
-
CTProjectConfig.sol # Full project deployment config
|
|
134
|
-
CTSuckerDeploymentConfig.sol # Cross-chain sucker config
|
|
135
74
|
test/
|
|
136
|
-
|
|
137
|
-
CTDeployer.t.sol # Deployer tests (~608 lines)
|
|
138
|
-
CTProjectOwner.t.sol # Project owner tests (~185 lines)
|
|
139
|
-
ClaimCollectionOwnership.t.sol # Collection ownership claim tests (~315 lines)
|
|
140
|
-
CroptopAttacks.t.sol # Security/adversarial tests (~437 lines, ~12 cases)
|
|
141
|
-
Fork.t.sol # Mainnet fork integration tests
|
|
142
|
-
TestAuditGaps.sol # Audit gap coverage tests (~689 lines)
|
|
143
|
-
Test_MetadataGeneration.t.sol # JBMetadataResolver roundtrip tests
|
|
144
|
-
fork/
|
|
145
|
-
PublishFork.t.sol # Fork-based publish tests (~437 lines)
|
|
146
|
-
regression/
|
|
147
|
-
DuplicateUriFeeEvasion.t.sol # Duplicate URI fee evasion regression (~312 lines)
|
|
148
|
-
FeeEvasion.t.sol # Fee evasion regression (~279 lines)
|
|
149
|
-
StaleTierIdMapping.t.sol # Stale tier ID mapping regression (~214 lines)
|
|
75
|
+
publisher, deployer, fork, attack, audit, metadata, and regression coverage
|
|
150
76
|
script/
|
|
151
|
-
Deploy.s.sol
|
|
152
|
-
ConfigureFeeProject.s.sol
|
|
77
|
+
Deploy.s.sol
|
|
78
|
+
ConfigureFeeProject.s.sol
|
|
153
79
|
helpers/
|
|
154
|
-
CroptopDeploymentLib.sol # Deployment artifact reader
|
|
155
80
|
```
|
|
156
81
|
|
|
157
|
-
##
|
|
158
|
-
|
|
159
|
-
| Permission | Required For |
|
|
160
|
-
|------------|-------------|
|
|
161
|
-
| `ADJUST_721_TIERS` | `configurePostingCriteriaFor` -- set posting criteria on a hook (from hook owner) |
|
|
162
|
-
| `DEPLOY_SUCKERS` | `deploySuckersFor` -- deploy cross-chain suckers for an existing project |
|
|
163
|
-
|
|
164
|
-
`CTDeployer` grants the following to the project owner during deployment:
|
|
165
|
-
- `ADJUST_721_TIERS` -- modify tiers directly
|
|
166
|
-
- `SET_721_METADATA` -- update collection metadata
|
|
167
|
-
- `MINT_721` -- mint NFTs directly
|
|
168
|
-
- `SET_721_DISCOUNT_PERCENT` -- adjust discount rates
|
|
169
|
-
|
|
170
|
-
## Risks
|
|
82
|
+
## Risks And Notes
|
|
171
83
|
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
- **Fee terminal failure:** The fee terminal payment is wrapped in try-catch. If the fee terminal reverts, the fee is sent to `feeBeneficiary` via low-level call, then to `msg.sender` if that also fails. A broken fee terminal never blocks mints, but the fee project loses revenue during the outage.
|
|
84
|
+
- posting criteria are only as safe as the project owner configures them
|
|
85
|
+
- fee routing depends on the designated fee project remaining correctly configured
|
|
86
|
+
- burn-lock ownership is intentionally irreversible and should only be used when immutability is desired
|
|
87
|
+
- duplicate-content and stale-tier edge cases are guarded by tests, but integrations should still treat metadata reuse carefully
|
package/RISKS.md
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Croptop Core Risk Register
|
|
2
|
+
|
|
3
|
+
This file focuses on the publishing, fee-routing, and hook-composition risks that matter once third parties can create NFT tiers on someone else's Juicebox project.
|
|
4
|
+
|
|
5
|
+
## How to use this file
|
|
6
|
+
|
|
7
|
+
- Read `Priority risks` first to understand the failure modes with the highest user or treasury impact.
|
|
8
|
+
- Use the detailed sections for contract-level reasoning about posting criteria, fee routing, and deployer composition.
|
|
9
|
+
- Treat `Accepted Behaviors` and `Invariants to Verify` as the line between intentional tradeoffs and defects.
|
|
10
|
+
|
|
11
|
+
## Priority risks
|
|
12
|
+
|
|
13
|
+
| Priority | Risk | Why it matters | Primary controls |
|
|
14
|
+
|----------|------|----------------|------------------|
|
|
15
|
+
| P0 | Hook/store and terminal trust | `mintFrom` depends on hook storage and directory terminal resolution; a bad integration can misprice posts or redirect value. | Audit integration assumptions, verify hook/store pairings, and monitor terminal configuration. |
|
|
16
|
+
| P1 | Tier ID race during concurrent posting | `_setupPosts` predicts future tier IDs before `adjustTiers`; concurrent writes can shift those IDs and break the batch. | Application-layer ordering, atomic reverts on mismatch, and operator awareness of concurrent posting. |
|
|
17
|
+
| P1 | Fee-path degradation without mint failure | The fee terminal is fail-open via try/catch, so posting continues even if the fee project temporarily stops receiving revenue. | Terminal health monitoring, fallback beneficiary handling, and explicit operational checks around fee routing. |
|
|
18
|
+
|
|
2
19
|
|
|
3
20
|
## 1. Trust Assumptions
|
|
4
21
|
|
package/SKILLS.md
CHANGED
|
@@ -1,200 +1,41 @@
|
|
|
1
1
|
# Croptop Core
|
|
2
2
|
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
Permissioned NFT publishing system that lets anyone post content as 721 tiers to a Juicebox project, subject to owner-defined criteria for price, supply, split percentages, and poster identity. Routes a 5% fee on each mint to a designated fee project.
|
|
6
|
-
|
|
7
|
-
## Contracts
|
|
8
|
-
|
|
9
|
-
| Contract | Role |
|
|
10
|
-
|----------|------|
|
|
11
|
-
| `CTPublisher` | Core publishing engine. Validates posts against bit-packed allowances, creates 721 tiers on hooks, mints first copies to posters, and routes fees. Inherits `JBPermissioned`, `ERC2771Context`. |
|
|
12
|
-
| `CTDeployer` | Factory that deploys a Juicebox project + 721 hook + posting criteria in one transaction. Also acts as `IJBRulesetDataHook` proxy that forwards pay/cash-out calls to the underlying hook while granting fee-free cash outs to suckers. |
|
|
13
|
-
| `CTProjectOwner` | Burn-lock contract: receives the project ownership NFT and grants `CTPublisher` the `ADJUST_721_TIERS` permission permanently. Since `CTProjectOwner` has no `transferFrom`, `reconfigure`, or `withdraw` functions, ownership is effectively burned -- no one can reclaim the project NFT, change rulesets, or modify posting criteria after transfer. Use this when you want a fully autonomous project where only community posting (within pre-set criteria) is possible. If you need to retain the ability to reconfigure the project, adjust tiers, or withdraw funds, keep ownership yourself (or use a multisig) instead. Configure all desired posting criteria **before** transferring the project NFT here, as they become immutable once ownership moves. Accepts both mints and `safeTransferFrom` calls originating from the `PROJECTS` contract. |
|
|
14
|
-
|
|
15
|
-
## Key Functions
|
|
16
|
-
|
|
17
|
-
### Publishing
|
|
18
|
-
|
|
19
|
-
| Function | What it does |
|
|
20
|
-
|----------|-------------|
|
|
21
|
-
| `CTPublisher.mintFrom(hook, posts, nftBeneficiary, feeBeneficiary, additionalPayMetadata, feeMetadata)` | Publishes posts as new 721 tiers, mints first copies to `nftBeneficiary`, deducts a 5% fee (`totalPrice / FEE_DIVISOR`) routed to `FEE_PROJECT_ID`, and pays the remainder into the project's primary terminal. Reuses existing tiers if the IPFS URI was already minted. |
|
|
22
|
-
| `CTPublisher.configurePostingCriteriaFor(allowedPosts)` | Sets per-category posting rules (min price, min/max supply, max split percent, address allowlist) for a given hook. Requires `ADJUST_721_TIERS` permission from the hook owner. |
|
|
23
|
-
|
|
24
|
-
### Views
|
|
25
|
-
|
|
26
|
-
| Function | What it does |
|
|
27
|
-
|----------|-------------|
|
|
28
|
-
| `CTPublisher.allowanceFor(hook, category)` | Returns the posting criteria for a hook/category: minimum price (uint104), min supply (uint32), max supply (uint32), max split percent (uint32), plus the address allowlist. Reads from bit-packed storage. |
|
|
29
|
-
| `CTPublisher.tiersFor(hook, encodedIPFSUris)` | Resolves an array of encoded IPFS URIs to their corresponding `JB721Tier` structs via the stored `tierIdForEncodedIPFSUriOf` mapping. |
|
|
30
|
-
|
|
31
|
-
### Project Deployment
|
|
32
|
-
|
|
33
|
-
| Function | What it does |
|
|
34
|
-
|----------|-------------|
|
|
35
|
-
| `CTDeployer.deployProjectFor(owner, projectConfig, suckerDeploymentConfiguration, controller)` | Deploys a new Juicebox project with a 721 tiers hook, configures posting criteria, optionally deploys suckers, and transfers project ownership to the specified owner. Uses `CTDeployer` as data hook proxy. Returns `(projectId, hook)`. |
|
|
36
|
-
| `CTDeployer.claimCollectionOwnershipOf(hook)` | Transfers hook ownership to the project via `JBOwnable.transferOwnershipToProject`. Only callable by the project owner. After claiming, the project owner must grant CTPublisher `ADJUST_721_TIERS` permission for the project so that `mintFrom()` continues to work. |
|
|
37
|
-
| `CTDeployer.deploySuckersFor(projectId, suckerDeploymentConfiguration)` | Deploys new cross-chain suckers for an existing project. Requires `DEPLOY_SUCKERS` permission. |
|
|
38
|
-
|
|
39
|
-
### Data Hook Proxy
|
|
40
|
-
|
|
41
|
-
| Function | What it does |
|
|
42
|
-
|----------|-------------|
|
|
43
|
-
| `CTDeployer.beforePayRecordedWith(context)` | Forwards pay context to the stored `dataHookOf[projectId]` (typically the 721 tiers hook). Hook specifications returned include a `noop` field — the 721 hook always returns `noop: false`. |
|
|
44
|
-
| `CTDeployer.beforeCashOutRecordedWith(context)` | Returns zero tax rate for sucker addresses (fee-free cross-chain cash outs). Otherwise forwards to the stored data hook. Forwarded hook specifications preserve the inner hook's `noop` flag. |
|
|
45
|
-
| `CTDeployer.hasMintPermissionFor(projectId, ruleset, addr)` | Returns `true` if `addr` is a sucker for the project. |
|
|
46
|
-
|
|
47
|
-
### Burn-Lock Ownership
|
|
48
|
-
|
|
49
|
-
| Function | What it does |
|
|
50
|
-
|----------|-------------|
|
|
51
|
-
| `CTProjectOwner.onERC721Received(operator, from, tokenId, data)` | On receiving the project NFT, grants `CTPublisher` the `ADJUST_721_TIERS` permission for that project. Accepts both mints and transfers from `PROJECTS` (reverts if `msg.sender` is not `PROJECTS`). |
|
|
52
|
-
|
|
53
|
-
## Integration Points
|
|
3
|
+
## Use This File For
|
|
54
4
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
| `@bananapus/core-v6` | `IJBDirectory`, `IJBPermissions`, `IJBTerminal`, `IJBProjects`, `IJBController`, `JBConstants`, `JBMetadataResolver` | Project lookup, permission enforcement, payment routing, project creation, metadata encoding |
|
|
58
|
-
| `@bananapus/721-hook-v6` | `IJB721TiersHook`, `IJB721TiersHookDeployer`, `JB721TierConfig`, `JB721Tier` | Tier creation/adjustment, hook deployment, tier data resolution |
|
|
59
|
-
| `@bananapus/ownable-v6` | `JBOwnable` | Ownership checks and transfers for hooks |
|
|
60
|
-
| `@bananapus/suckers-v6` | `IJBSuckerRegistry`, `JBSuckerDeployerConfig` | Cross-chain sucker deployment and fee-free cash-out detection |
|
|
61
|
-
| `@bananapus/permission-ids-v6` | `JBPermissionIds` | Permission ID constants (`ADJUST_721_TIERS`, `DEPLOY_SUCKERS`, `MAP_SUCKER_TOKEN`, etc.) |
|
|
62
|
-
| `@openzeppelin/contracts` | `ERC2771Context`, `IERC721Receiver` | Meta-transaction support, safe project NFT receipt |
|
|
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.
|
|
63
7
|
|
|
64
|
-
##
|
|
8
|
+
## Read This Next
|
|
65
9
|
|
|
66
|
-
|
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
10
|
+
| If you need... | Open this next |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Repo overview and expected flow | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
|
|
13
|
+
| Publishing and metadata behavior | [`src/CTPublisher.sol`](./src/CTPublisher.sol) |
|
|
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
|
+
| 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/) |
|
|
73
17
|
|
|
74
|
-
##
|
|
18
|
+
## Repo Map
|
|
75
19
|
|
|
76
|
-
|
|
|
77
|
-
|
|
78
|
-
|
|
|
79
|
-
| `
|
|
20
|
+
| Area | Where to look |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Main contracts | [`src/`](./src/) |
|
|
23
|
+
| Types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
|
|
24
|
+
| Scripts | [`script/`](./script/) |
|
|
25
|
+
| Tests | [`test/`](./test/) |
|
|
80
26
|
|
|
81
|
-
##
|
|
82
|
-
|
|
83
|
-
| Error | When |
|
|
84
|
-
|-------|------|
|
|
85
|
-
| `CTPublisher_EmptyEncodedIPFSUri` | Post has `encodedIPFSUri == bytes32(0)` |
|
|
86
|
-
| `CTPublisher_InsufficientEthSent` | `totalPrice + fee > msg.value` |
|
|
87
|
-
| `CTPublisher_MaxTotalSupplyLessThanMin` | `minimumTotalSupply > maximumTotalSupply` in config |
|
|
88
|
-
| `CTPublisher_NotInAllowList` | Caller not in allowlist (when allowlist is non-empty) |
|
|
89
|
-
| `CTPublisher_PriceTooSmall` | Post price below `minimumPrice` |
|
|
90
|
-
| `CTPublisher_SplitPercentExceedsMaximum` | Post `splitPercent > maximumSplitPercent` |
|
|
91
|
-
| `CTPublisher_TotalSupplyTooSmall` | Post `totalSupply < minimumTotalSupply` |
|
|
92
|
-
| `CTPublisher_TotalSupplyTooBig` | Post `totalSupply > maximumTotalSupply` (when max > 0) |
|
|
93
|
-
| `CTPublisher_UnauthorizedToPostInCategory` | Category unconfigured (`minSupply == 0`) |
|
|
94
|
-
| `CTPublisher_ZeroTotalSupply` | `configurePostingCriteriaFor` with `minimumTotalSupply == 0` |
|
|
95
|
-
| `CTPublisher_DuplicatePost(bytes32 encodedIPFSUri)` | Same `encodedIPFSUri` appears more than once within the same `mintFrom` batch |
|
|
96
|
-
| `CTDeployer_NotOwnerOfProject` | `claimCollectionOwnershipOf` called by non-owner |
|
|
97
|
-
|
|
98
|
-
## Constants
|
|
99
|
-
|
|
100
|
-
| Constant | Value | Purpose |
|
|
101
|
-
|----------|-------|---------|
|
|
102
|
-
| `FEE_DIVISOR` | 20 | 5% fee: `totalPrice / 20` |
|
|
103
|
-
| `FEE_PROJECT_ID` | immutable | Fees routed to this project. Fee skipped when `projectId == FEE_PROJECT_ID` |
|
|
104
|
-
|
|
105
|
-
## Storage
|
|
106
|
-
|
|
107
|
-
### CTPublisher
|
|
108
|
-
|
|
109
|
-
| Variable | Type | Purpose |
|
|
110
|
-
|----------|------|---------|
|
|
111
|
-
| `tierIdForEncodedIPFSUriOf` | `hook => encodedIPFSUri => uint256` | Maps IPFS URI to existing tier ID (prevents duplicates) |
|
|
112
|
-
| `_packedAllowanceFor` | `hook => category => uint256` | Bit-packed allowance: price (0-103), minSupply (104-135), maxSupply (136-167), maxSplitPercent (168-199) |
|
|
113
|
-
| `_allowedAddresses` | `hook => category => address[]` | Per-category address allowlist |
|
|
114
|
-
|
|
115
|
-
### CTDeployer
|
|
116
|
-
|
|
117
|
-
| Variable | Type | Purpose |
|
|
118
|
-
|----------|------|---------|
|
|
119
|
-
| `PROJECTS` | `IJBProjects` (immutable) | ERC-721 contract for Juicebox project ownership |
|
|
120
|
-
| `DEPLOYER` | `IJB721TiersHookDeployer` (immutable) | Factory for deploying 721 tiers hooks |
|
|
121
|
-
| `PUBLISHER` | `ICTPublisher` (immutable) | CTPublisher instance used for posting |
|
|
122
|
-
| `SUCKER_REGISTRY` | `IJBSuckerRegistry` (immutable) | Registry for cross-chain sucker deployment and lookup |
|
|
123
|
-
| `dataHookOf` | `projectId => IJBRulesetDataHook` | Stores original data hook per project (CTDeployer proxy pattern) |
|
|
124
|
-
|
|
125
|
-
## Gotchas
|
|
126
|
-
|
|
127
|
-
1. **Bit-packed allowances.** Allowances are packed into a single `uint256`: price in bits 0-103, min supply in 104-135, max supply in 136-167, max split percent in 168-199. Reading with wrong bit widths silently returns wrong values.
|
|
128
|
-
2. **Fee is 1/20, not a percentage.** `FEE_DIVISOR = 20` means fee = `totalPrice / 20` = 5%. Integer division truncates (rounding down favors payer).
|
|
129
|
-
3. **Fee skipped for fee project.** When `projectId == FEE_PROJECT_ID`, no fee is deducted. This prevents self-referential fee loops.
|
|
130
|
-
4. **Fee payment is pre-computed and try-catch wrapped.** After the main payment, `mintFrom` computes the fee as `msg.value - payValue` and sends it to the fee terminal via try-catch. If the fee terminal reverts, the fee falls back to `feeBeneficiary.call{value}`, then `msg.sender.call{value}`. A broken fee terminal never blocks mints.
|
|
131
|
-
5. **Tier reuse by IPFS URI.** If an encoded IPFS URI was already minted on the hook, the existing tier ID is reused instead of creating a new tier. The poster still gets a mint of the existing tier. The fee is calculated from the actual tier price stored on-chain (not from `post.price`), preventing fee evasion (H-19 fix).
|
|
132
|
-
6. **Stale tier mapping cleanup.** If a tier was removed externally via `adjustTiers()`, the `tierIdForEncodedIPFSUriOf` mapping is automatically cleared when the same IPFS URI is posted again, allowing a new tier to be created (L-52 fix).
|
|
133
|
-
7. **Array resizing via assembly.** `_setupPosts` resizes `tiersToAdd` via inline assembly when some posts reuse existing tiers. The `tierIdsToMint` array is NOT resized and may contain zeros for pre-existing tiers.
|
|
134
|
-
8. **CTProjectOwner only accepts from PROJECTS contract.** `onERC721Received` reverts if `msg.sender != address(PROJECTS)`. It does NOT check the `from` address, so both mints and transfers through `PROJECTS.safeTransferFrom` are accepted.
|
|
135
|
-
9. **CTDeployer rejects direct transfers.** `CTDeployer.onERC721Received` reverts if `from != address(0)`. It only accepts mints from `PROJECTS`.
|
|
136
|
-
10. **Temporary ownership during deployment.** `CTDeployer` owns the project NFT temporarily during `deployProjectFor` (to configure permissions and hooks), then transfers it to the specified `owner`. If the transfer reverts, the entire deployment fails.
|
|
137
|
-
11. **Data hook proxy pattern.** `CTDeployer` wraps itself as the data hook, forwarding to `dataHookOf[projectId]`. This is needed to intercept cash-out calls and grant fee-free cash outs to suckers. Both `useDataHookForPay` and `useDataHookForCashOut` are enabled (M-37 fix).
|
|
138
|
-
12. **Sucker registry trust.** `CTDeployer.beforeCashOutRecordedWith` trusts `SUCKER_REGISTRY.isSuckerOf` to determine fee exemption. If the registry is compromised, any address could cash out without tax.
|
|
139
|
-
13. **Allowlist uses linear scan.** `_isAllowed()` iterates the full allowlist array. Acceptable for <100 addresses; gas cost scales linearly with list size. This also affects `mintFrom` with large post batches: each post in the batch triggers a separate allowlist scan, so gas scales as `O(posts * allowlistSize)`. Keep batches under ~20 posts with allowlists under ~50 addresses to stay within block gas limits.
|
|
140
|
-
14. **Referral ID in metadata.** `FEE_PROJECT_ID` is stored in the first 32 bytes of mint metadata (via assembly `mstore`), allowing the fee terminal to track referrals.
|
|
141
|
-
15. **Deterministic deployment.** Hook salt is `keccak256(abi.encode(projectConfig.salt, msg.sender))` and sucker salt is `keccak256(abi.encode(suckerConfig.salt, msg.sender))`. Different callers with the same salt get different addresses.
|
|
142
|
-
16. **Default project weight.** `CTDeployer` deploys projects with `weight = 1_000_000 * 10^18`, ETH currency, and `maxCashOutTaxRate`. These defaults are hardcoded.
|
|
143
|
-
17. **ERC2771 meta-transaction support.** Both `CTPublisher` and `CTDeployer` support meta-transactions via `ERC2771Context` with a configurable trusted forwarder, allowing relayers to submit transactions on behalf of users.
|
|
144
|
-
|
|
145
|
-
## Example Integration
|
|
146
|
-
|
|
147
|
-
```solidity
|
|
148
|
-
import {ICTPublisher} from "@croptop/core-v6/src/interfaces/ICTPublisher.sol";
|
|
149
|
-
import {CTPost} from "@croptop/core-v6/src/structs/CTPost.sol";
|
|
150
|
-
import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
|
|
151
|
-
|
|
152
|
-
// --- Post content to a Croptop-enabled project ---
|
|
153
|
-
|
|
154
|
-
CTPost[] memory posts = new CTPost[](1);
|
|
155
|
-
posts[0] = CTPost({
|
|
156
|
-
encodedIPFSUri: 0x1234..., // encoded IPFS CID
|
|
157
|
-
totalSupply: 100,
|
|
158
|
-
price: 0.01 ether,
|
|
159
|
-
category: 1,
|
|
160
|
-
splitPercent: 0,
|
|
161
|
-
splits: new JBSplit[](0)
|
|
162
|
-
});
|
|
27
|
+
## Purpose
|
|
163
28
|
|
|
164
|
-
|
|
165
|
-
uint256 totalCost = 0.01 ether + (0.01 ether / 20);
|
|
29
|
+
Permissioned publishing layer for Juicebox 721 projects. Project owners define posting rules, publishers mint content as tiers through a 721 hook, Croptop routes fees, and the deployer can package the whole project shape in one transaction.
|
|
166
30
|
|
|
167
|
-
|
|
168
|
-
IJB721TiersHook(hookAddress),
|
|
169
|
-
posts,
|
|
170
|
-
msg.sender, // NFT beneficiary
|
|
171
|
-
msg.sender, // fee beneficiary
|
|
172
|
-
"", // additional pay metadata
|
|
173
|
-
"" // fee metadata
|
|
174
|
-
);
|
|
31
|
+
## Reference Files
|
|
175
32
|
|
|
176
|
-
|
|
33
|
+
- Open [`references/runtime.md`](./references/runtime.md) when you need publisher behavior, fee routing, data-hook forwarding, or the main invariants around posting criteria and tier reuse.
|
|
34
|
+
- Open [`references/operations.md`](./references/operations.md) when you need deployer behavior, burn-lock ownership implications, script breadcrumbs, or the common sources of stale assumptions.
|
|
177
35
|
|
|
178
|
-
|
|
179
|
-
owner: msg.sender,
|
|
180
|
-
projectConfig: CTProjectConfig({
|
|
181
|
-
terminalConfigurations: terminals,
|
|
182
|
-
projectUri: "ipfs://...",
|
|
183
|
-
allowedPosts: allowedPosts,
|
|
184
|
-
contractUri: "ipfs://...",
|
|
185
|
-
name: "My Collection",
|
|
186
|
-
symbol: "MYC",
|
|
187
|
-
salt: bytes32("my-project")
|
|
188
|
-
}),
|
|
189
|
-
suckerDeploymentConfiguration: CTSuckerDeploymentConfig({
|
|
190
|
-
deployerConfigurations: new JBSuckerDeployerConfig[](0),
|
|
191
|
-
salt: bytes32(0)
|
|
192
|
-
}),
|
|
193
|
-
controller: IJBController(controllerAddress)
|
|
194
|
-
});
|
|
36
|
+
## Working Rules
|
|
195
37
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```
|
|
38
|
+
- 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
|
+
- Treat posting criteria, fee routing, and duplicate-content handling as treasury-sensitive and product-sensitive at the same time.
|
|
40
|
+
- If the task mentions project immutability or admin recovery, inspect [`src/CTProjectOwner.sol`](./src/CTProjectOwner.sol) before changing deployer or publisher code.
|
|
41
|
+
- When a bug looks like generic 721 issuance, confirm it is not actually in `nana-721-hook-v6`.
|
package/STYLE_GUIDE.md
CHANGED
|
@@ -26,8 +26,8 @@ pragma solidity 0.8.28;
|
|
|
26
26
|
// Interfaces, structs, enums — caret for forward compatibility
|
|
27
27
|
pragma solidity ^0.8.0;
|
|
28
28
|
|
|
29
|
-
// Libraries —
|
|
30
|
-
pragma solidity
|
|
29
|
+
// Libraries — pin to exact version like contracts
|
|
30
|
+
pragma solidity 0.8.28;
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Imports
|
|
@@ -86,12 +86,20 @@ contract JBExample is JBPermissioned, IJBExample {
|
|
|
86
86
|
|
|
87
87
|
uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
|
|
88
88
|
|
|
89
|
+
//*********************************************************************//
|
|
90
|
+
// ------------------------ private constants ------------------------ //
|
|
91
|
+
//*********************************************************************//
|
|
92
|
+
|
|
89
93
|
//*********************************************************************//
|
|
90
94
|
// --------------- public immutable stored properties ---------------- //
|
|
91
95
|
//*********************************************************************//
|
|
92
96
|
|
|
93
97
|
IJBDirectory public immutable override DIRECTORY;
|
|
94
98
|
|
|
99
|
+
//*********************************************************************//
|
|
100
|
+
// -------------- internal immutable stored properties -------------- //
|
|
101
|
+
//*********************************************************************//
|
|
102
|
+
|
|
95
103
|
//*********************************************************************//
|
|
96
104
|
// --------------------- public stored properties -------------------- //
|
|
97
105
|
//*********************************************************************//
|
|
@@ -100,10 +108,26 @@ contract JBExample is JBPermissioned, IJBExample {
|
|
|
100
108
|
// -------------------- internal stored properties ------------------- //
|
|
101
109
|
//*********************************************************************//
|
|
102
110
|
|
|
111
|
+
//*********************************************************************//
|
|
112
|
+
// -------------------- private stored properties -------------------- //
|
|
113
|
+
//*********************************************************************//
|
|
114
|
+
|
|
115
|
+
//*********************************************************************//
|
|
116
|
+
// ------------------- transient stored properties ------------------- //
|
|
117
|
+
//*********************************************************************//
|
|
118
|
+
|
|
103
119
|
//*********************************************************************//
|
|
104
120
|
// -------------------------- constructor ---------------------------- //
|
|
105
121
|
//*********************************************************************//
|
|
106
122
|
|
|
123
|
+
//*********************************************************************//
|
|
124
|
+
// ---------------------------- modifiers ---------------------------- //
|
|
125
|
+
//*********************************************************************//
|
|
126
|
+
|
|
127
|
+
//*********************************************************************//
|
|
128
|
+
// ------------------------- receive / fallback ---------------------- //
|
|
129
|
+
//*********************************************************************//
|
|
130
|
+
|
|
107
131
|
//*********************************************************************//
|
|
108
132
|
// ---------------------- external transactions ---------------------- //
|
|
109
133
|
//*********************************************************************//
|
|
@@ -112,10 +136,18 @@ contract JBExample is JBPermissioned, IJBExample {
|
|
|
112
136
|
// ----------------------- external views ---------------------------- //
|
|
113
137
|
//*********************************************************************//
|
|
114
138
|
|
|
139
|
+
//*********************************************************************//
|
|
140
|
+
// -------------------------- public views --------------------------- //
|
|
141
|
+
//*********************************************************************//
|
|
142
|
+
|
|
115
143
|
//*********************************************************************//
|
|
116
144
|
// ----------------------- public transactions ----------------------- //
|
|
117
145
|
//*********************************************************************//
|
|
118
146
|
|
|
147
|
+
//*********************************************************************//
|
|
148
|
+
// ---------------------- internal transactions ---------------------- //
|
|
149
|
+
//*********************************************************************//
|
|
150
|
+
|
|
119
151
|
//*********************************************************************//
|
|
120
152
|
// ----------------------- internal helpers -------------------------- //
|
|
121
153
|
//*********************************************************************//
|
|
@@ -134,17 +166,28 @@ contract JBExample is JBPermissioned, IJBExample {
|
|
|
134
166
|
1. Custom errors
|
|
135
167
|
2. Public constants
|
|
136
168
|
3. Internal constants
|
|
137
|
-
4.
|
|
138
|
-
5.
|
|
139
|
-
6.
|
|
140
|
-
7.
|
|
141
|
-
8.
|
|
142
|
-
9.
|
|
143
|
-
10.
|
|
144
|
-
11.
|
|
145
|
-
12.
|
|
146
|
-
13.
|
|
147
|
-
14.
|
|
169
|
+
4. Private constants
|
|
170
|
+
5. Public immutable stored properties
|
|
171
|
+
6. Internal immutable stored properties
|
|
172
|
+
7. Public stored properties
|
|
173
|
+
8. Internal stored properties
|
|
174
|
+
9. Private stored properties
|
|
175
|
+
10. Transient stored properties
|
|
176
|
+
11. Constructor
|
|
177
|
+
12. Modifiers
|
|
178
|
+
13. Receive / fallback
|
|
179
|
+
14. External transactions
|
|
180
|
+
15. External views
|
|
181
|
+
16. Public views
|
|
182
|
+
17. Public transactions
|
|
183
|
+
18. Internal transactions
|
|
184
|
+
19. Internal helpers
|
|
185
|
+
20. Internal views
|
|
186
|
+
21. Private helpers
|
|
187
|
+
|
|
188
|
+
Use these additional section labels where they better match the contents of the block:
|
|
189
|
+
- `internal functions` is accepted as equivalent to `internal helpers`
|
|
190
|
+
- `events` and `structs` are acceptable in specialized contracts that define them explicitly
|
|
148
191
|
|
|
149
192
|
Functions are alphabetized within each section.
|
|
150
193
|
|
|
@@ -565,7 +608,3 @@ CI checks formatting via `forge fmt --check`.
|
|
|
565
608
|
### Contract Size Checks
|
|
566
609
|
|
|
567
610
|
CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
|
|
568
|
-
|
|
569
|
-
## Repo-Specific Deviations
|
|
570
|
-
|
|
571
|
-
None. This repo follows the standard configuration exactly.
|