@bananapus/721-hook-v6 0.0.35 → 0.0.37
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 +49 -163
- package/ARCHITECTURE.md +71 -49
- package/AUDIT_INSTRUCTIONS.md +47 -84
- package/README.md +40 -28
- package/RISKS.md +85 -86
- package/SKILLS.md +17 -16
- package/USER_JOURNEYS.md +85 -62
- package/foundry.toml +2 -0
- package/package.json +1 -1
- package/references/operations.md +7 -3
- package/references/runtime.md +5 -4
- package/src/JB721CheckpointsDeployer.sol +2 -0
- package/src/JB721TiersHook.sol +0 -2
- package/src/JB721TiersHookProjectDeployer.sol +0 -1
- package/src/JB721TiersHookStore.sol +1 -2
- package/src/abstract/JB721Hook.sol +0 -1
- package/src/interfaces/IJB721CheckpointsDeployer.sol +3 -0
- package/src/interfaces/IJB721TiersHook.sol +0 -2
- package/src/interfaces/IJB721TiersHookStore.sol +1 -1
- package/src/libraries/JB721Constants.sol +0 -1
- package/src/structs/JB721InitTiersConfig.sol +0 -1
- package/src/structs/JB721Tier.sol +0 -2
- package/src/structs/JB721TierConfig.sol +0 -2
- package/src/structs/JB721TierConfigFlags.sol +0 -1
- package/src/structs/JB721TierFlags.sol +0 -1
- package/src/structs/JB721TiersHookFlags.sol +0 -1
- package/src/structs/JB721TiersMintReservesConfig.sol +0 -1
- package/src/structs/JB721TiersRulesetMetadata.sol +0 -1
- package/src/structs/JB721TiersSetDiscountPercentConfig.sol +0 -1
- package/src/structs/JBBitmapWord.sol +0 -1
- package/src/structs/JBDeploy721TiersHookConfig.sol +0 -1
- package/src/structs/JBLaunchProjectConfig.sol +0 -1
- package/src/structs/JBLaunchRulesetsConfig.sol +0 -1
- package/src/structs/JBPayDataHookRulesetConfig.sol +0 -1
- package/src/structs/JBPayDataHookRulesetMetadata.sol +0 -1
- package/src/structs/JBQueueRulesetsConfig.sol +0 -1
- package/src/structs/JBStored721Tier.sol +0 -1
- package/test/unit/JB721CheckpointsDeployer_AccessControl.t.sol +116 -0
package/ADMINISTRATION.md
CHANGED
|
@@ -1,189 +1,75 @@
|
|
|
1
1
|
# Administration
|
|
2
2
|
|
|
3
|
-
Admin privileges and their scope in nana-721-hook-v6.
|
|
4
|
-
|
|
5
3
|
## At A Glance
|
|
6
4
|
|
|
7
5
|
| Item | Details |
|
|
8
|
-
|
|
9
|
-
| Scope |
|
|
10
|
-
|
|
|
11
|
-
| Highest-risk actions | Adjusting tiers
|
|
12
|
-
| Recovery posture |
|
|
13
|
-
|
|
14
|
-
## Routine Operations
|
|
15
|
-
|
|
16
|
-
- Grant only the specific 721 permission IDs a project operator needs instead of handing out broad owner-equivalent access.
|
|
17
|
-
- Use tier adjustments, metadata updates, owner mints, and discount changes with awareness of the project's current sale state and ruleset flags.
|
|
18
|
-
- Treat deployer and clone initialization steps as setup-only actions; verify ownership, pricing, and store references before publishing a hook address to users.
|
|
19
|
-
- When cash-out behavior or pay-hook composition changes, coordinate that with the project's ruleset configuration rather than assuming the hook can pause itself.
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| Scope | Tiered NFT hook configuration, tier adjustment, and deployer wiring |
|
|
8
|
+
| Control posture | Mixed project-owner and delegated control |
|
|
9
|
+
| Highest-risk actions | Adjusting tiers, setting discount or metadata behavior, and wiring the wrong hook or resolver |
|
|
10
|
+
| Recovery posture | Some configuration is mutable, but many tier properties are intentionally one-way |
|
|
20
11
|
|
|
21
|
-
##
|
|
12
|
+
## Purpose
|
|
22
13
|
|
|
23
|
-
-
|
|
24
|
-
- Hook ownership transfers change who can exercise every `onlyOwner` surface; accidental ownership moves are high-impact.
|
|
25
|
-
- Tier and pricing changes can have user-facing economic effects immediately even when they are technically reversible for future sales.
|
|
14
|
+
`nana-721-hook-v6` splits control between project-level hook ownership and the tier rules enforced by the store. Many important settings can be changed only in limited ways after launch.
|
|
26
15
|
|
|
27
|
-
##
|
|
16
|
+
## Control Model
|
|
28
17
|
|
|
29
|
-
-
|
|
30
|
-
-
|
|
18
|
+
- project owners or delegates control hook-level configuration
|
|
19
|
+
- tier creation and mutation are permissioned and partially one-way
|
|
20
|
+
- the store trusts the calling hook for its own state namespace
|
|
21
|
+
- deployers package setup, but do not remove the need for runtime review
|
|
31
22
|
|
|
32
23
|
## Roles
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
### Permission Operators
|
|
41
|
-
|
|
42
|
-
- **Assigned by**: The hook owner grants permissions via the `JBPermissions` contract.
|
|
43
|
-
- **Scope**: Per-project. Operators can be granted specific permission IDs scoped to the hook's `PROJECT_ID`.
|
|
44
|
-
- **How it works**: Each privileged function calls `_requirePermissionFrom(account: owner(), projectId: PROJECT_ID, permissionId: ...)`. This passes if the caller IS the owner, OR if the caller has been granted the specified permission ID by the owner for the project.
|
|
45
|
-
|
|
46
|
-
### Terminal (Protocol-Level Caller)
|
|
47
|
-
|
|
48
|
-
- **Assigned by**: The project's `JBDirectory` configuration.
|
|
49
|
-
- **Scope**: Only a contract registered as a terminal for the hook's project in `JBDirectory` can call `afterPayRecordedWith()` and `afterCashOutRecordedWith()`.
|
|
50
|
-
- **Verification**: `DIRECTORY.isTerminalOf(projectId, IJBTerminal(msg.sender))` is checked in `JB721Hook.sol`.
|
|
51
|
-
|
|
52
|
-
### Store Callers (msg.sender Trust Model)
|
|
53
|
-
|
|
54
|
-
- **Assigned by**: Implicit. `JB721TiersHookStore` trusts `msg.sender` as the hook contract.
|
|
55
|
-
- **Scope**: All `record*` functions in the store use `msg.sender` as the hook address key. Any contract can call the store, but state changes are scoped to `msg.sender`'s own data namespace.
|
|
56
|
-
- **Why this is safe**: Each hook clone has its own address, and the store keys all data by `[msg.sender][tierId]`. A malicious contract calling the store can only modify its own namespace.
|
|
57
|
-
|
|
58
|
-
## Privileged Functions
|
|
59
|
-
|
|
60
|
-
### JB721TiersHook
|
|
25
|
+
| Role | How Assigned | Scope | Notes |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| Project owner | `owner()` or project control surface | Per hook | Main authority |
|
|
28
|
+
| Tier delegate | `JBPermissions` grant | Per project | Usually tier, mint, discount, or metadata permissions |
|
|
29
|
+
| Reserve beneficiary | Tier config | Per tier | Receives reserve NFTs |
|
|
61
30
|
|
|
62
|
-
|
|
63
|
-
|----------|--------------|-----------------|--------------|
|
|
64
|
-
| `adjustTiers()` | `ADJUST_721_TIERS` | `owner()` | Adds new tiers and/or soft-removes existing tiers. Sets tier split groups in JBSplits. |
|
|
65
|
-
| `mintFor()` | `MINT_721` | `owner()` | Manually mints NFTs from tiers that have `flags.allowOwnerMint` enabled. Bypasses price checks (passes `type(uint256).max` as amount). |
|
|
66
|
-
| `setDiscountPercentOf()` | `SET_721_DISCOUNT_PERCENT` | `owner()` | Sets the discount percentage for a single tier. |
|
|
67
|
-
| `setDiscountPercentsOf()` | `SET_721_DISCOUNT_PERCENT` | `owner()` | Batch-sets discount percentages for multiple tiers. |
|
|
68
|
-
| `setMetadata()` | `SET_721_METADATA` | `owner()` | Updates collection name, symbol, baseURI, contractURI, tokenUriResolver, and/or per-tier encoded IPFS URIs. Empty strings leave values unchanged. |
|
|
69
|
-
| `initialize()` | None (one-time) | `_initialized` flag check | Initializes a cloned hook. Can only be called once. Transfers ownership to caller on completion. |
|
|
31
|
+
## Privileged Surfaces
|
|
70
32
|
|
|
71
|
-
|
|
33
|
+
- `adjustTiers(...)`
|
|
34
|
+
- `mintFor(...)`
|
|
35
|
+
- `setDiscountPercentOf(...)`
|
|
36
|
+
- `setMetadata(...)`
|
|
37
|
+
- deployer setup and hook ownership transfer paths
|
|
72
38
|
|
|
73
|
-
|
|
74
|
-
|----------|--------------|-----------------|--------------|
|
|
75
|
-
| `launchProjectFor()` | None | Anyone can call | Creates a new project with a 721 hook. Ownership goes to the specified `owner` address. |
|
|
76
|
-
| `launchRulesetsFor()` | `QUEUE_RULESETS` + `SET_TERMINALS` | Project NFT owner or delegate | Deploys a hook and launches rulesets for an existing project. |
|
|
77
|
-
| `queueRulesetsOf()` | `QUEUE_RULESETS` | Project NFT owner or delegate | Deploys a hook and queues rulesets for an existing project. |
|
|
39
|
+
## Immutable And One-Way
|
|
78
40
|
|
|
79
|
-
|
|
41
|
+
- many tier properties are immutable once created
|
|
42
|
+
- removed tiers do not reduce `maxTierIdOf`
|
|
43
|
+
- pool-like mutable rescue does not exist here; bad tier design is often expensive to unwind
|
|
80
44
|
|
|
81
|
-
|
|
82
|
-
|----------|--------------|-----------------|--------------|
|
|
83
|
-
| `deployHookFor()` | None | Anyone can call | Clones and initializes a new hook instance. Ownership starts with the deployer contract, then is transferred to `msg.sender`. |
|
|
45
|
+
## Operational Notes
|
|
84
46
|
|
|
85
|
-
|
|
47
|
+
- review tier parameters before launch as if they were economic policy
|
|
48
|
+
- treat discount changes and metadata changes as meaningful authority, not cosmetic controls
|
|
49
|
+
- be explicit about whether the hook participates in pay, cash out, or both
|
|
50
|
+
- separate resolver trust from hook and store trust
|
|
86
51
|
|
|
87
|
-
|
|
88
|
-
|----------|----------------|--------------|
|
|
89
|
-
| `afterPayRecordedWith()` | Project terminal | Processes payment, mints NFTs. Verifies caller via `DIRECTORY.isTerminalOf()`. |
|
|
90
|
-
| `afterCashOutRecordedWith()` | Project terminal | Burns NFTs on cash out. Verifies caller via `DIRECTORY.isTerminalOf()` and that `msg.value == 0`. |
|
|
52
|
+
## Machine Notes
|
|
91
53
|
|
|
92
|
-
|
|
54
|
+
- do not reason from the hook alone when the bug may live in the store
|
|
55
|
+
- if a resolver is involved, inspect it separately
|
|
56
|
+
- if tier counts are large, re-check gas-sensitive reads and writes before operational changes
|
|
93
57
|
|
|
94
|
-
|
|
95
|
-
|----------|--------|--------------|
|
|
96
|
-
| `recordAddTiers()` | Hook contract | Adds tiers to the caller's namespace. Category sort order enforced. |
|
|
97
|
-
| `recordRemoveTierIds()` | Hook contract | Marks tiers as removed in bitmap. Respects `flags.cantBeRemoved` flag. |
|
|
98
|
-
| `recordMint()` | Hook contract | Records mints, decrements supply, enforces price and reserve checks. |
|
|
99
|
-
| `recordMintReservesFor()` | Hook contract | Mints reserved NFTs from a tier. |
|
|
100
|
-
| `recordBurn()` | Hook contract | Increments burn counter for token IDs. |
|
|
101
|
-
| `recordFlags()` | Hook contract | Sets behavioral flags for the caller's hook. |
|
|
102
|
-
| `recordSetTokenUriResolver()` | Hook contract | Sets the token URI resolver. |
|
|
103
|
-
| `recordSetEncodedIPFSUriOf()` | Hook contract | Sets the encoded IPFS URI for a tier. |
|
|
104
|
-
| `recordSetDiscountPercentOf()` | Hook contract | Updates a tier's discount percent. Enforces bounds and `flags.cantIncreaseDiscountPercent`. |
|
|
105
|
-
| `recordTransferForTier()` | Hook contract | Updates per-tier balance tracking on transfer. |
|
|
106
|
-
| `cleanTiers()` | Anyone | Reorganizes the tier sorting linked list to skip removed tiers. Pure bookkeeping, no value at risk. |
|
|
58
|
+
## Recovery
|
|
107
59
|
|
|
108
|
-
|
|
60
|
+
- some mistakes can be corrected through allowed metadata or discount changes
|
|
61
|
+
- many tier-design mistakes are effectively permanent once live
|
|
62
|
+
- deployer mistakes may require a new hook path rather than in-place repair
|
|
109
63
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
1. **JBOwnable** (`JB721TiersHook` inherits from it): The hook has a single `owner()` that can be an EOA or a Juicebox project. When owned by a project, the holder of that project's ERC-721 NFT is the effective owner.
|
|
113
|
-
|
|
114
|
-
2. **JBPermissions** (protocol-wide permission registry): The owner can grant specific permission IDs to operator addresses. Each permission is scoped to a `(operator, account, projectId, permissionId)` tuple. The `ROOT` permission (ID 1) grants all permissions.
|
|
115
|
-
|
|
116
|
-
The `_requirePermissionFrom()` check (inherited from `JBOwnable` via `JBPermissioned`) passes if:
|
|
117
|
-
- `msg.sender == account` (the owner themselves), OR
|
|
118
|
-
- `JBPermissions.hasPermission(msg.sender, account, projectId, permissionId)` returns true.
|
|
119
|
-
|
|
120
|
-
### Permission IDs Used
|
|
121
|
-
|
|
122
|
-
| Permission ID | Constant Name | Used By |
|
|
123
|
-
|--------------|---------------|---------|
|
|
124
|
-
| `JBPermissionIds.ADJUST_721_TIERS` | `ADJUST_721_TIERS` | `adjustTiers()` |
|
|
125
|
-
| `JBPermissionIds.MINT_721` | `MINT_721` | `mintFor()` |
|
|
126
|
-
| `JBPermissionIds.SET_721_DISCOUNT_PERCENT` | `SET_721_DISCOUNT_PERCENT` | `setDiscountPercentOf()`, `setDiscountPercentsOf()` |
|
|
127
|
-
| `JBPermissionIds.SET_721_METADATA` | `SET_721_METADATA` | `setMetadata()` |
|
|
128
|
-
| `JBPermissionIds.QUEUE_RULESETS` | `QUEUE_RULESETS` | `launchRulesetsFor()`, `queueRulesetsOf()` |
|
|
129
|
-
| `JBPermissionIds.SET_TERMINALS` | `SET_TERMINALS` | `launchRulesetsFor()` |
|
|
130
|
-
|
|
131
|
-
## Immutable Configuration
|
|
132
|
-
|
|
133
|
-
The following are set at deploy/initialization time and **cannot be changed afterward**:
|
|
134
|
-
|
|
135
|
-
| Property | Set In | Scope |
|
|
136
|
-
|----------|--------|-------|
|
|
137
|
-
| `DIRECTORY` | Constructor | Which terminal/controller directory is trusted |
|
|
138
|
-
| `PRICES` | Constructor | Which prices contract is used for cross-currency conversions |
|
|
139
|
-
| `RULESETS` | Constructor | Which rulesets contract is consulted |
|
|
140
|
-
| `STORE` | Constructor | Which store manages tier data |
|
|
141
|
-
| `SPLITS` | Constructor | Which splits contract manages tier split groups |
|
|
142
|
-
| `METADATA_ID_TARGET` | Constructor | The address used for metadata ID derivation (original implementation address for clones) |
|
|
143
|
-
| `PROJECT_ID` | `initialize()` | Which project this hook belongs to |
|
|
144
|
-
| Pricing context (currency, decimals) | `initialize()` | Packed into `_packedPricingContext` -- the token denomination for tier prices |
|
|
145
|
-
| `JB721TiersHookFlags` | `initialize()` | `noNewTiersWithReserves`, `noNewTiersWithVotes`, `noNewTiersWithOwnerMinting`, `preventOverspending`, `issueTokensForSplits` |
|
|
146
|
-
| Per-tier `flags.cantBeRemoved` | `recordAddTiers()` | Whether a tier can be soft-removed |
|
|
147
|
-
| Per-tier `flags.cantIncreaseDiscountPercent` | `recordAddTiers()` | Whether a tier's discount can be increased |
|
|
148
|
-
| Per-tier `reserveFrequency` | `recordAddTiers()` | How often reserve NFTs accrue |
|
|
149
|
-
| Per-tier `initialSupply` | `recordAddTiers()` | Maximum number of NFTs mintable from the tier |
|
|
150
|
-
| Per-tier `price` | `recordAddTiers()` | The base price (and cash-out weight) of NFTs in the tier |
|
|
151
|
-
| Per-tier `category` | `recordAddTiers()` | The category grouping for sort order |
|
|
152
|
-
|
|
153
|
-
## Clone Pattern
|
|
154
|
-
|
|
155
|
-
`JB721TiersHook` is deployed as an implementation contract and then cloned via `LibClone.clone()` in `JB721TiersHookDeployer`. Each clone is a minimal proxy that delegates all calls to the implementation.
|
|
156
|
-
|
|
157
|
-
**Admin implications:**
|
|
158
|
-
- The implementation contract cannot be self-destructed or modified after deployment. Even if it could be, clones would break since they `delegatecall` to the implementation address.
|
|
159
|
-
- Each clone has its own storage (including `PROJECT_ID`, ownership, and tier data). The implementation's storage is unused.
|
|
160
|
-
- `METADATA_ID_TARGET` is set to the original implementation address, ensuring consistent metadata ID derivation across all clones.
|
|
161
|
-
- The `initialize()` function uses an `_initialized` bool flag to prevent re-initialization. The implementation contract's constructor sets `_initialized = true`, blocking direct initialization. Clones start with `_initialized = false` and set it to `true` during `initialize()`.
|
|
162
|
-
|
|
163
|
-
## Ruleset-Level Pauses
|
|
164
|
-
|
|
165
|
-
Two behaviors are controlled by the project's current ruleset metadata (packed into the 14-bit `metadata` field of `JBRulesetMetadata`), parsed by `JB721TiersRulesetMetadataResolver`:
|
|
166
|
-
|
|
167
|
-
| Bit | Flag | Effect |
|
|
168
|
-
|-----|------|--------|
|
|
169
|
-
| 0 | `transfersPaused` | When set, NFT transfers are blocked for tiers that have `flags.transfersPausable` enabled |
|
|
170
|
-
| 1 | `mintPendingReservesPaused` | When set, `mintPendingReservesFor()` reverts |
|
|
64
|
+
## Admin Boundaries
|
|
171
65
|
|
|
172
|
-
|
|
66
|
+
- no one can rewrite immutable tier properties after creation
|
|
67
|
+
- deployers do not bypass runtime permissions after setup
|
|
68
|
+
- resolver behavior cannot fix broken hook accounting
|
|
173
69
|
|
|
174
|
-
##
|
|
70
|
+
## Source Map
|
|
175
71
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
- **Cannot change reserve frequency after creation.** The `reserveFrequency` is immutable per tier.
|
|
181
|
-
- **Cannot reduce a tier's initial supply.** Supply can only decrease through minting and burning.
|
|
182
|
-
- **Cannot remove a tier marked `flags.cantBeRemoved`.** The store enforces this in `recordRemoveTierIds()`.
|
|
183
|
-
- **Cannot increase a tier's discount if `flags.cantIncreaseDiscountPercent` is set.** The store enforces this in `recordSetDiscountPercentOf()`.
|
|
184
|
-
- **Cannot mint from tiers without `flags.allowOwnerMint`.** The `mintFor()` function passes `isOwnerMint: true` to the store, which checks the flag.
|
|
185
|
-
- **Cannot re-initialize a hook.** The `initialize()` function reverts if `_initialized` is already true.
|
|
186
|
-
- **Cannot change the pricing currency, decimals, or prices contract.** `PRICES` is immutable (set in constructor), and the currency/decimals in `_packedPricingContext` are set once during initialization.
|
|
187
|
-
- **Cannot bypass the flag restrictions.** Once `noNewTiersWithReserves`, `noNewTiersWithVotes`, or `noNewTiersWithOwnerMinting` are set, all future tiers added via `adjustTiers()` must comply.
|
|
188
|
-
- **Cannot mint more reserves than the formula allows.** Reserve mints are bounded by `ceil(nonReserveMints / reserveFrequency)`.
|
|
189
|
-
- **Cannot modify the split groups outside of `adjustTiers()`.** Tier split groups are set during tier addition via the library; there is no separate admin function to change them directly on the hook (though the project owner could call `JBSplits.setSplitGroupsOf()` directly if they have the appropriate permission).
|
|
72
|
+
- `src/JB721TiersHook.sol`
|
|
73
|
+
- `src/JB721TiersHookStore.sol`
|
|
74
|
+
- `src/JB721TiersHookDeployer.sol`
|
|
75
|
+
- `src/JB721TiersHookProjectDeployer.sol`
|
package/ARCHITECTURE.md
CHANGED
|
@@ -2,75 +2,97 @@
|
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
`nana-721-hook-v6`
|
|
5
|
+
`nana-721-hook-v6` is the shared tiered NFT layer for Juicebox V6. It lets projects sell NFT tiers, track reserves, route split payouts, and cash out NFTs without replacing core treasury accounting.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## System Overview
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- `JB721TiersHookStore` owns compact tier storage and many validation rules.
|
|
11
|
-
- `JB721TiersHookDeployer` and `JB721TiersHookProjectDeployer` own deployment and project-launch convenience.
|
|
12
|
-
- The repo does not replace the core terminal, controller, or surplus logic; it plugs into them.
|
|
9
|
+
`JB721TiersHook` is the runtime hook. `JB721TiersHookStore` is the accounting backend for tiers, supply, reserves, and lookup. The deployers package that hook into reusable flows for existing projects and new project launches.
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
Custom token URI resolvers usually live outside this repo, but they still affect the trusted surface seen by users.
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
| --- | --- |
|
|
18
|
-
| `JB721TiersHook` | Data hook, pay hook, and cash-out hook for tiered NFTs |
|
|
19
|
-
| `JB721TiersHookStore` | Packed tier storage, mint accounting, reserves, and validation |
|
|
20
|
-
| `JB721Hook`, `ERC721` | Shared NFT machinery and token metadata plumbing |
|
|
21
|
-
| deployers | Clone and launch helpers for hook instances and projects |
|
|
22
|
-
| libraries and structs | Tier math, metadata decoding, flags, and config surfaces |
|
|
13
|
+
## Core Invariants
|
|
23
14
|
|
|
24
|
-
|
|
15
|
+
- the hook must not create alternate treasury accounting
|
|
16
|
+
- tier supply, burned counts, and reserves must stay coherent
|
|
17
|
+
- cash-out weight must reflect the intended tier economics
|
|
18
|
+
- reserve minting and split routing must not drift from stored tier state
|
|
19
|
+
- store-linked list and bitmap assumptions must stay valid under tier add, remove, and clean operations
|
|
20
|
+
- deployer wiring must preserve the expected ruleset and hook shape
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
## Modules
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
| Module | Responsibility | Notes |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `JB721TiersHook` | Pay hook, cash-out hook, permissions, and project-facing execution | Runtime core |
|
|
27
|
+
| `JB721TiersHookStore` | Tier definitions, balances, reserve tracking, and accounting | Shared state |
|
|
28
|
+
| `JB721TiersHookDeployer` | Clone deployer for existing projects | Wiring helper |
|
|
29
|
+
| `JB721TiersHookProjectDeployer` | Project-launch deployer with hook setup | Launch helper |
|
|
30
|
+
| `JB721Hook` | Abstract 721 hook base | Shared behavior |
|
|
31
|
+
|
|
32
|
+
## Trust Boundaries
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
- core accounting, pricing, and terminal authentication remain in `nana-core-v6`
|
|
35
|
+
- metadata resolvers can be project-specific and should be treated as trusted external surfaces
|
|
36
|
+
- the store is trusted by every hook that uses it
|
|
37
|
+
- deployers are trusted to wire the hook into the intended project and ruleset shape
|
|
38
|
+
|
|
39
|
+
## Critical Flows
|
|
40
|
+
|
|
41
|
+
### Pay And Mint
|
|
37
42
|
|
|
38
43
|
```text
|
|
39
|
-
|
|
40
|
-
->
|
|
44
|
+
payment arrives
|
|
45
|
+
-> hook decodes metadata and tier choices
|
|
46
|
+
-> store records mints, credits, supply changes, and reserve effects
|
|
47
|
+
-> hook may route split payouts from forwarded funds
|
|
48
|
+
-> collection state and balances update for the beneficiary
|
|
41
49
|
```
|
|
42
50
|
|
|
43
|
-
### Cash
|
|
51
|
+
### Cash Out
|
|
44
52
|
|
|
45
53
|
```text
|
|
46
|
-
|
|
47
|
-
->
|
|
48
|
-
->
|
|
54
|
+
cash out requested
|
|
55
|
+
-> hook checks NFT-specific metadata and selected token IDs
|
|
56
|
+
-> hook burns NFTs
|
|
57
|
+
-> store records burn and supply effects
|
|
58
|
+
-> terminal reclaims value using hook-aware cash-out math
|
|
49
59
|
```
|
|
50
60
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
- Tiers are an ordered, compact data structure. Category ordering and tier IDs are part of storage semantics, not just metadata.
|
|
54
|
-
- Original tier price drives cash-out weight. Discounts change purchase price, not reclaim weight.
|
|
55
|
-
- Reserve frequency and owner-mint settings interact; configurations that would double-count mint authority must stay invalid.
|
|
56
|
-
- Pending reserves belong in supply-sensitive calculations even before they are lazily minted.
|
|
57
|
-
- If `useDataHookForCashOut` is enabled, fungible-token cash outs are intentionally displaced by NFT cash-out semantics.
|
|
61
|
+
## Accounting Model
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
This repo owns tier accounting and NFT lifecycle logic. It does not own the canonical project ledger for balances, fees, or surplus.
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
- Preview behavior, pay behavior, reserve minting, and cash-out behavior all depend on the same tier semantics.
|
|
63
|
-
- Metadata decoding and credit handling create edge cases around partial spends and exact-tier selection.
|
|
65
|
+
The most important state lives in the store: remaining supply, burned counts, reserve tracking, and per-tier configuration.
|
|
64
66
|
|
|
65
|
-
##
|
|
67
|
+
## Security Model
|
|
66
68
|
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
+
- store corruption has ecosystem-wide blast radius because many products reuse it
|
|
70
|
+
- reserve logic, discounts, and cash-out weight are the main economic risk surfaces
|
|
71
|
+
- split distribution and fallback behavior are part of correctness, not a secondary concern
|
|
72
|
+
- gas costs matter because some reads and writes scale with tier count
|
|
69
73
|
|
|
70
74
|
## Safe Change Guide
|
|
71
75
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
|
|
76
|
+
- review hook and store behavior together when changing tier lifecycle logic
|
|
77
|
+
- if reserve logic changes, re-check cash-out weight and pending reserve effects together
|
|
78
|
+
- if deployer behavior changes, re-check ruleset wiring and ownership transfer paths
|
|
79
|
+
- do not treat resolver behavior as proof that hook accounting is correct
|
|
80
|
+
|
|
81
|
+
## Canonical Checks
|
|
82
|
+
|
|
83
|
+
- pay, mint, and redeem end-to-end behavior:
|
|
84
|
+
`test/E2E/Pay_Mint_Redeem_E2E.t.sol`
|
|
85
|
+
- store and lifecycle invariants:
|
|
86
|
+
`test/invariants/TierLifecycleInvariant.t.sol`
|
|
87
|
+
`test/invariants/TieredHookStoreInvariant.t.sol`
|
|
88
|
+
- split-credit and deployer regressions:
|
|
89
|
+
`test/audit/CodexSplitCreditsMismatch.t.sol`
|
|
90
|
+
`test/regression/ProjectDeployerRulesets.t.sol`
|
|
91
|
+
|
|
92
|
+
## Source Map
|
|
93
|
+
|
|
94
|
+
- `src/JB721TiersHook.sol`
|
|
95
|
+
- `src/JB721TiersHookStore.sol`
|
|
96
|
+
- `src/JB721TiersHookDeployer.sol`
|
|
97
|
+
- `src/JB721TiersHookProjectDeployer.sol`
|
|
98
|
+
- `src/libraries/JB721TiersHookLib.sol`
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -1,114 +1,77 @@
|
|
|
1
1
|
# Audit Instructions
|
|
2
2
|
|
|
3
|
-
This repo
|
|
3
|
+
This repo adds tiered NFT issuance and cash-out behavior to Juicebox projects. Audit it as a shared accounting layer whose mistakes can affect many downstream products.
|
|
4
4
|
|
|
5
|
-
## Objective
|
|
5
|
+
## Audit Objective
|
|
6
6
|
|
|
7
7
|
Find issues that:
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- let
|
|
12
|
-
-
|
|
8
|
+
|
|
9
|
+
- corrupt tier supply, reserve state, or burn accounting
|
|
10
|
+
- misprice cash outs or split routing
|
|
11
|
+
- let permissions or deployer wiring create unsafe lifecycle changes
|
|
12
|
+
- create gas or liveness failures in tier-heavy deployments
|
|
13
|
+
- break trust boundaries between hook, store, and resolver behavior
|
|
13
14
|
|
|
14
15
|
## Scope
|
|
15
16
|
|
|
16
17
|
In scope:
|
|
18
|
+
|
|
17
19
|
- `src/JB721TiersHook.sol`
|
|
18
20
|
- `src/JB721TiersHookStore.sol`
|
|
19
|
-
- `src
|
|
20
|
-
- `src/JB721TiersHookProjectDeployer.sol`
|
|
21
|
-
- `src/abstract/`
|
|
22
|
-
- `src/interfaces/`
|
|
23
|
-
- `src/libraries/`
|
|
24
|
-
- `src/structs/`
|
|
21
|
+
- deployers, libraries, interfaces, and structs under `src/`
|
|
25
22
|
- deployment scripts in `script/`
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
## System Model
|
|
30
|
-
|
|
31
|
-
The hook can act as:
|
|
32
|
-
- a data hook for payment and cash-out accounting inputs
|
|
33
|
-
- a pay hook that mints NFTs
|
|
34
|
-
- a cash-out hook that burns NFTs and computes reclaim weight
|
|
35
|
-
|
|
36
|
-
Key moving parts:
|
|
37
|
-
- `JB721TiersHookStore` holds compact tier state
|
|
38
|
-
- hook instances read and mutate tier data through the store
|
|
39
|
-
- tier prices, discounts, reserves, credits, split percentages, and category order shape mint behavior
|
|
40
|
-
- optional token URI resolvers can override metadata generation
|
|
41
|
-
|
|
42
|
-
The most important design subtlety is that this repo affects both:
|
|
43
|
-
- NFT state
|
|
44
|
-
- core Juicebox accounting inputs and fulfillment order
|
|
45
|
-
|
|
46
|
-
That combination is why small-looking mistakes here often become ecosystem-wide economic bugs.
|
|
24
|
+
## Start Here
|
|
47
25
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
No tier may mint beyond its configured total supply once purchases, owner mints, and pending reserves are all considered.
|
|
52
|
-
|
|
53
|
-
2. Reserve accounting is exact
|
|
54
|
-
Pending reserves must neither disappear nor inflate reclaim denominators beyond what the design intends.
|
|
55
|
-
|
|
56
|
-
3. Split routing matches accounting
|
|
57
|
-
If part of a mint price is routed to splits, token issuance and treasury accounting must reflect only the intended project portion.
|
|
26
|
+
1. `src/JB721TiersHook.sol`
|
|
27
|
+
2. `src/JB721TiersHookStore.sol`
|
|
28
|
+
3. `src/libraries/JB721TiersHookLib.sol`
|
|
58
29
|
|
|
59
|
-
|
|
60
|
-
The reclaim value for NFTs must match documented tier economics and must not be manipulable through discounts, credits, cross-currency inputs, or reserve timing.
|
|
30
|
+
## Security Model
|
|
61
31
|
|
|
62
|
-
|
|
63
|
-
One hook instance must not corrupt or observe mutable state belonging to another project unexpectedly.
|
|
32
|
+
The hook:
|
|
64
33
|
|
|
65
|
-
|
|
66
|
-
|
|
34
|
+
- mints and burns tiered NFTs through Juicebox flows
|
|
35
|
+
- records tier lifecycle state in a shared store
|
|
36
|
+
- can route split payouts from forwarded value
|
|
37
|
+
- composes with project-specific metadata resolvers
|
|
67
38
|
|
|
68
|
-
|
|
69
|
-
Token URI resolvers must not become an implicit control plane for mint, burn, or accounting behavior.
|
|
39
|
+
## Roles And Privileges
|
|
70
40
|
|
|
71
|
-
|
|
41
|
+
| Role | Powers | How constrained |
|
|
42
|
+
|------|--------|-----------------|
|
|
43
|
+
| Project authority | Configure tiers, metadata, and minting policy | Must stay inside explicit permission checks |
|
|
44
|
+
| Store caller | Mutate store state in its own namespace | Must not corrupt tier accounting |
|
|
45
|
+
| Resolver | Serve metadata and URI behavior | Must not be confused with accounting truth |
|
|
72
46
|
|
|
73
|
-
|
|
74
|
-
- overspending and leftover-credit edge cases
|
|
75
|
-
- cross-currency pricing with missing or stale feeds
|
|
76
|
-
- tier additions or adjustments with invalid sort order or percent bounds
|
|
77
|
-
- split hooks or terminal recipients that revert or partially fail
|
|
78
|
-
- data-hook and pay-hook interactions inside the same payment
|
|
47
|
+
## Integration Assumptions
|
|
79
48
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
83
|
-
|
|
49
|
+
| Dependency | Assumption | What breaks if wrong |
|
|
50
|
+
|------------|------------|----------------------|
|
|
51
|
+
| `nana-core-v6` | Terminal auth and pricing behavior are accurate | Pay and cash-out behavior drift |
|
|
52
|
+
| Resolver repo | Metadata reads behave as expected | UI and marketplace behavior break |
|
|
84
53
|
|
|
85
|
-
##
|
|
54
|
+
## Critical Invariants
|
|
86
55
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
56
|
+
1. Tier supply stays coherent.
|
|
57
|
+
Remaining supply, burned counts, and outstanding ownership must reconcile.
|
|
58
|
+
2. Reserve logic stays bounded.
|
|
59
|
+
Pending reserves and reserve minting must not over-allocate.
|
|
60
|
+
3. Cash-out weight is consistent.
|
|
61
|
+
NFT reclaim value must match the tier model the hook and store intend.
|
|
62
|
+
4. Split and fallback behavior is safe.
|
|
63
|
+
Failed split paths must not silently corrupt value or lifecycle state.
|
|
93
64
|
|
|
94
|
-
##
|
|
65
|
+
## Attack Surfaces
|
|
95
66
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
67
|
+
- pay and cash-out hook entrypoints
|
|
68
|
+
- tier add, remove, and clean flows
|
|
69
|
+
- reserve minting
|
|
70
|
+
- split distribution and fallback paths
|
|
71
|
+
- resolver integration
|
|
101
72
|
|
|
102
|
-
##
|
|
73
|
+
## Verification
|
|
103
74
|
|
|
104
|
-
Standard workflow:
|
|
105
75
|
- `npm install`
|
|
106
76
|
- `forge build`
|
|
107
77
|
- `forge test`
|
|
108
|
-
|
|
109
|
-
Current tests emphasize:
|
|
110
|
-
- audit and regression fixes around split accounting and cross-currency behavior
|
|
111
|
-
- invariants on tier lifecycle and store state
|
|
112
|
-
- fork coverage for ERC-20 cash-out and tier split routes
|
|
113
|
-
|
|
114
|
-
High-value findings in this repo tend to become repeatable vulnerabilities in downstream repos, so favor proofs that show the primitive itself returning or recording the wrong value.
|