@bananapus/omnichain-deployers-v6 0.0.19 → 0.0.21
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 -2
- package/ARCHITECTURE.md +48 -85
- package/AUDIT_INSTRUCTIONS.md +52 -366
- package/CHANGELOG.md +61 -0
- package/README.md +45 -189
- package/RISKS.md +23 -6
- package/SKILLS.md +27 -240
- package/STYLE_GUIDE.md +71 -19
- package/USER_JOURNEYS.md +45 -417
- package/package.json +4 -5
- package/references/operations.md +28 -0
- package/references/runtime.md +28 -0
- package/src/JBOmnichainDeployer.sol +7 -2
- package/src/interfaces/IJBOmnichainDeployer.sol +3 -0
- package/test/JBOmnichainDeployer.t.sol +23 -0
- package/test/OmnichainDeployerEdgeCases.t.sol +10 -1
- package/test/TestAuditGaps.sol +23 -0
- package/test/audit/CarryForwardRejectedHook.t.sol +305 -0
- package/test/audit/JBOmnichainDeployer.t.sol +10 -0
- package/test/fork/OmnichainForkTestBase.sol +10 -7
- package/test/fork/TestOmnichain721QueueAndAdjust.t.sol +28 -21
- package/CHANGE_LOG.md +0 -341
- package/assets/findings/nana-omnichain-deployers-v6-pashov-ai-audit-report-20260330-103536.md +0 -39
package/README.md
CHANGED
|
@@ -1,134 +1,46 @@
|
|
|
1
1
|
# Juicebox Omnichain Deployers
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@bananapus/omnichain-deployers-v6` launches Juicebox projects with cross-chain suckers and a 721 hook already wired in. It is the package you use when the default project shape should be omnichain from day one.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Docs: <https://docs.juicebox.money>
|
|
6
|
+
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Overview
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
The deployer wraps multiple launch concerns into one surface:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
- deploy or carry forward a tiered 721 hook
|
|
13
|
+
- install itself as the project's data-hook wrapper
|
|
14
|
+
- compose the 721 hook with an optional extra custom hook
|
|
15
|
+
- grant tax-free and mint-safe behavior to project suckers
|
|
16
|
+
- deploy suckers deterministically across chains
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
- **Composes the 721 hook and custom data hook** for payments -- the 721 hook is called first (via `tiered721HookOf`) to get its specs (including split fund amounts), then the custom hook from `_extraDataHookOf` (if `useDataHookForPay: true`) is called with a reduced amount context (payment minus split amount) so it only considers the available funds. The 721 hook's weight (already split-adjusted by `JB721TiersHookLib.calculateWeight`) is used directly, ensuring the terminal only mints tokens for the amount that actually enters the project treasury. If the 721 hook returns no specs (0 tiers), it is skipped in the merged output.
|
|
15
|
-
- **Composes hooks for cash outs** -- the 721 hook is called first (if `useDataHookForCashOut: true`), updating the cash out parameters (tax rate, count, supply). Then the custom hook is called (if `useDataHookForCashOut: true`) with the already-updated values from the 721 hook. Both hooks' specifications are merged into a single array (721 specs first, then custom hook specs). If the 721 hook has `useDataHookForCashOut: true` and reverts (e.g., for fungible-only cashouts), that revert propagates. Set `useDataHookForCashOut: false` on the 721 config to skip it.
|
|
16
|
-
- **Returns default values** if neither hook has the relevant flag set.
|
|
18
|
+
The wrapper exists so suckers can bridge without being blocked by project-specific cash-out tax logic while the project still keeps its own data hooks.
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
Use this repo when the default project shape is "Juicebox project plus 721 hook plus cross-chain bridge." Do not use it when a project is single-chain or does not need the wrapper semantics around suckers.
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
If the question is "how do suckers bridge?" start in `nana-suckers-v6`. If the question is "how does a 721 hook behave?" start in `nana-721-hook-v6`. This repo is where those components are packaged together and wrapped.
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
sequenceDiagram
|
|
24
|
-
participant Caller
|
|
25
|
-
participant Deployer as JBOmnichainDeployer
|
|
26
|
-
participant HookDeployer as IJB721TiersHookDeployer
|
|
27
|
-
participant Controller as JBController
|
|
28
|
-
participant Registry as JBSuckerRegistry
|
|
29
|
-
participant Owner
|
|
24
|
+
## Key Contract
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Deployer->>Controller: launchProjectFor(owner=deployer, ...)
|
|
35
|
-
Controller-->>Deployer: projectId + project NFT
|
|
36
|
-
Deployer->>Deployer: transferOwnershipToProject(projectId) — 721 hook ownership
|
|
37
|
-
Deployer->>Registry: deploySuckersFor(projectId, salt)
|
|
38
|
-
Registry-->>Deployer: sucker addresses
|
|
39
|
-
Deployer->>Owner: transfer project NFT
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
During operation:
|
|
43
|
-
|
|
44
|
-
```mermaid
|
|
45
|
-
sequenceDiagram
|
|
46
|
-
participant Terminal
|
|
47
|
-
participant Deployer as JBOmnichainDeployer
|
|
48
|
-
participant Registry as JBSuckerRegistry
|
|
49
|
-
participant Hook721 as 721 Hook
|
|
50
|
-
participant HookExtra as Custom Hook
|
|
51
|
-
|
|
52
|
-
Terminal->>Deployer: beforeCashOutRecordedWith(context)
|
|
53
|
-
Deployer->>Registry: isSuckerOf(projectId, holder)?
|
|
54
|
-
alt Holder is a sucker
|
|
55
|
-
Deployer-->>Terminal: 0% tax (early return)
|
|
56
|
-
else Hooks configured
|
|
57
|
-
opt 721 hook with useDataHookForCashOut=true
|
|
58
|
-
Deployer->>Hook721: beforeCashOutRecordedWith(context)
|
|
59
|
-
Hook721-->>Deployer: updated taxRate, count, supply, specs
|
|
60
|
-
end
|
|
61
|
-
opt Custom hook with useDataHookForCashOut=true
|
|
62
|
-
Deployer->>HookExtra: beforeCashOutRecordedWith(updated context)
|
|
63
|
-
HookExtra-->>Deployer: further updated values + specs
|
|
64
|
-
end
|
|
65
|
-
Deployer-->>Terminal: merged specs from both hooks
|
|
66
|
-
else Neither hook has useDataHookForCashOut=true
|
|
67
|
-
Deployer-->>Terminal: original values (default)
|
|
68
|
-
end
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### 721 Tiers Hook Integration
|
|
72
|
-
|
|
73
|
-
Every project deployed through `JBOmnichainDeployer` gets a 721 tiers hook, even with 0 initial tiers. This lets projects add NFT tiers later without needing to reconfigure the data hook. The deployer:
|
|
74
|
-
|
|
75
|
-
1. Deploys the 721 hook via `HOOK_DEPLOYER`
|
|
76
|
-
2. Transfers hook ownership to the project via `JBOwnable.transferOwnershipToProject(projectId)` (after the project NFT exists)
|
|
77
|
-
3. Stores the 721 hook per-ruleset in `_tiered721HookOf[projectId][rulesetId]` with its `useDataHookForCashOut` flag
|
|
78
|
-
4. Sets itself as the data hook on each ruleset, enforcing `useDataHookForPay = true` and `useDataHookForCashOut = true`
|
|
79
|
-
5. Stores the optional custom hook (e.g., buyback hook) separately in `_extraDataHookOf[projectId][rulesetId]` with its own per-hook flags
|
|
80
|
-
|
|
81
|
-
For `queueRulesetsOf`, if no new tiers are provided, the 721 hook from the latest ruleset is carried forward instead of deploying a new one.
|
|
82
|
-
|
|
83
|
-
This means a project can have both a 721 hook (for NFT minting on payments) and a custom data hook (for buyback, custom weight logic, etc.) running simultaneously. During both payments and cash outs, the hooks are called sequentially (721 hook first, then custom hook) and their specifications are merged.
|
|
84
|
-
|
|
85
|
-
### Simplified Overloads
|
|
86
|
-
|
|
87
|
-
Each of `launchProjectFor`, `launchRulesetsFor`, and `queueRulesetsOf` has a simplified overload that omits the `deploy721Config` parameter. These use `_default721Config(rulesetConfigurations)`, which creates an empty-tier 721 config with `currency` from the first ruleset's `baseCurrency`, `decimals = 18`, `useDataHookForCashOut = false`, and no salt. At least one ruleset configuration is required. For `queueRulesetsOf`, since the default config has 0 tiers, the existing 721 hook is always carried forward.
|
|
88
|
-
|
|
89
|
-
### Deterministic Cross-Chain Addresses
|
|
90
|
-
|
|
91
|
-
Sucker deployment salts are hashed with `_msgSender()` before use:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
salt = keccak256(abi.encode(userSalt, _msgSender()))
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
This means:
|
|
98
|
-
- **Same sender + same salt on each chain = same sucker addresses** (deterministic via CREATE2)
|
|
99
|
-
- Different senders can't collide, even with the same salt
|
|
100
|
-
- `salt = bytes32(0)` skips sucker deployment entirely
|
|
26
|
+
| Contract | Role |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `JBOmnichainDeployer` | Launches projects and rulesets, manages per-ruleset hook composition, and deploys suckers with deterministic salts. |
|
|
101
29
|
|
|
102
|
-
|
|
30
|
+
## Mental Model
|
|
103
31
|
|
|
104
|
-
|
|
32
|
+
This repo owns orchestration plus runtime wrapping:
|
|
105
33
|
|
|
106
|
-
|
|
107
|
-
|
|
34
|
+
1. launch a project with a known omnichain-capable shape
|
|
35
|
+
2. remember which hook composition belongs to which ruleset
|
|
36
|
+
3. special-case sucker behavior so bridge flows are not broken by project-specific logic
|
|
108
37
|
|
|
109
|
-
|
|
38
|
+
## Read These Files First
|
|
110
39
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
The `queueRulesetsOf` function guards against prediction failures by reverting if `latestRulesetIdOf(projectId) >= block.timestamp` (i.e., rulesets were already queued in the same block).
|
|
116
|
-
|
|
117
|
-
## Architecture
|
|
118
|
-
|
|
119
|
-
| Contract | Description |
|
|
120
|
-
|----------|-------------|
|
|
121
|
-
| `JBOmnichainDeployer` | Deploys projects, rulesets, and suckers. Wraps the project's real data hook to intercept cash outs from suckers (tax-free) and grant suckers mint permission. Implements `IJBRulesetDataHook`, `IERC721Receiver`, `ERC2771Context`, `JBPermissioned`. |
|
|
122
|
-
|
|
123
|
-
### Supporting Types
|
|
124
|
-
|
|
125
|
-
| Type | Description |
|
|
126
|
-
|------|-------------|
|
|
127
|
-
| `JBOmnichain721Config` | 721 hook deployment config: `deployTiersHookConfig` (tier configuration), `useDataHookForCashOut` flag, and `salt` for deterministic deployment. Passed to all deploy/launch/queue functions. |
|
|
128
|
-
| `JBDeployerHookConfig` | Per-hook config with `dataHook`, `useDataHookForPay`, and `useDataHookForCashOut` flags. Stored as a single value per `(projectId, rulesetId)` in `_extraDataHookOf` for the custom data hook. |
|
|
129
|
-
| `JBTiered721HookConfig` | Per-ruleset 721 hook config with `hook` (the `IJB721TiersHook`) and `useDataHookForCashOut` flag. Stored per `(projectId, rulesetId)` in `_tiered721HookOf`. |
|
|
130
|
-
| `JBSuckerDeploymentConfig` | Wraps an array of `JBSuckerDeployerConfig` with a `bytes32` salt for deterministic cross-chain addresses. |
|
|
131
|
-
| `IJBOmnichainDeployer` | Interface for all deployer entry points and the `extraDataHookOf` view. |
|
|
40
|
+
1. `src/JBOmnichainDeployer.sol`
|
|
41
|
+
2. `nana-suckers-v6/src/JBSucker.sol`
|
|
42
|
+
3. `nana-721-hook-v6/src/JB721TiersHook.sol`
|
|
43
|
+
4. the extra hook repo, if the deployment composes one
|
|
132
44
|
|
|
133
45
|
## Install
|
|
134
46
|
|
|
@@ -136,96 +48,40 @@ The `queueRulesetsOf` function guards against prediction failures by reverting i
|
|
|
136
48
|
npm install @bananapus/omnichain-deployers-v6
|
|
137
49
|
```
|
|
138
50
|
|
|
139
|
-
|
|
51
|
+
## Development
|
|
140
52
|
|
|
141
53
|
```bash
|
|
142
|
-
|
|
54
|
+
npm install
|
|
55
|
+
forge build
|
|
56
|
+
forge test
|
|
143
57
|
```
|
|
144
58
|
|
|
145
|
-
|
|
146
|
-
```
|
|
147
|
-
@bananapus/omnichain-deployers-v6/=lib/nana-omnichain-deployers-v6/
|
|
148
|
-
```
|
|
59
|
+
Useful scripts:
|
|
149
60
|
|
|
150
|
-
|
|
61
|
+
- `npm run deploy:mainnets`
|
|
62
|
+
- `npm run deploy:testnets`
|
|
151
63
|
|
|
152
|
-
|
|
153
|
-
|---------|-------------|
|
|
154
|
-
| `forge build` | Compile contracts |
|
|
155
|
-
| `forge test` | Run unit, integration, and attack tests |
|
|
156
|
-
| `forge test -vvv` | Run tests with full stack traces |
|
|
157
|
-
| `npm run deploy:mainnets` | Propose mainnet deployment via Sphinx |
|
|
158
|
-
| `npm run deploy:testnets` | Propose testnet deployment via Sphinx |
|
|
64
|
+
## Deployment Notes
|
|
159
65
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
```toml
|
|
163
|
-
# foundry.toml
|
|
164
|
-
[profile.default]
|
|
165
|
-
solc = '0.8.26'
|
|
166
|
-
evm_version = 'cancun'
|
|
167
|
-
via_ir = true
|
|
168
|
-
optimizer_runs = 200
|
|
169
|
-
|
|
170
|
-
[fuzz]
|
|
171
|
-
runs = 4096
|
|
172
|
-
```
|
|
66
|
+
This repo assumes the 721 hook, address registry, buyback hook, suckers, ownable, and core packages are already available. Matching salts from the same sender keep sucker addresses deterministic across chains.
|
|
173
67
|
|
|
174
68
|
## Repository Layout
|
|
175
69
|
|
|
176
|
-
```
|
|
70
|
+
```text
|
|
177
71
|
src/
|
|
178
|
-
JBOmnichainDeployer.sol
|
|
72
|
+
JBOmnichainDeployer.sol
|
|
179
73
|
interfaces/
|
|
180
|
-
IJBOmnichainDeployer.sol # Public interface
|
|
181
74
|
structs/
|
|
182
|
-
JBDeployerHookConfig.sol # Custom hook config (dataHook + flags)
|
|
183
|
-
JBOmnichain721Config.sol # 721 hook deployment config
|
|
184
|
-
JBSuckerDeploymentConfig.sol # Sucker deployment params
|
|
185
|
-
JBTiered721HookConfig.sol # Per-ruleset 721 hook config
|
|
186
75
|
test/
|
|
187
|
-
|
|
188
|
-
JBOmnichainDeployerGuard.t.sol # Ruleset ID prediction tests
|
|
189
|
-
OmnichainDeployerAttacks.t.sol # Adversarial security tests
|
|
190
|
-
OmnichainDeployerEdgeCases.t.sol # Edge case tests (weight, cashout, mint)
|
|
191
|
-
OmnichainDeployerReentrancy.t.sol # Reentrancy tests
|
|
192
|
-
TestAuditGaps.sol # Audit gap coverage tests
|
|
193
|
-
Tiered721HookComposition.t.sol # 721 hook + custom hook composition tests
|
|
194
|
-
fork/
|
|
195
|
-
OmnichainForkTestBase.sol # Shared fork test base
|
|
196
|
-
TestOmnichain721QueueAndAdjust.t.sol # Fork: queue and adjust 721 tiers
|
|
197
|
-
TestOmnichainCashOutFork.t.sol # Fork: cash out flows
|
|
198
|
-
TestOmnichainStressFork.t.sol # Fork: stress / load tests
|
|
199
|
-
TestOmnichainWeightFork.t.sol # Fork: weight decay tests
|
|
200
|
-
TestSuckerDeploymentFork.t.sol # Fork: sucker deployment
|
|
201
|
-
invariants/
|
|
202
|
-
OmnichainDeployerInvariant.t.sol # Invariant tests
|
|
203
|
-
handlers/
|
|
204
|
-
OmnichainDeployerHandler.sol # Invariant handler
|
|
205
|
-
regression/
|
|
206
|
-
HookOwnershipTransfer.t.sol # Hook ownership transfer regression
|
|
207
|
-
ValidateController.t.sol # Controller validation regression
|
|
76
|
+
unit, attack, invariant, fork, audit, and regression coverage
|
|
208
77
|
script/
|
|
209
|
-
Deploy.s.sol
|
|
78
|
+
Deploy.s.sol
|
|
210
79
|
helpers/
|
|
211
|
-
DeployersDeploymentLib.sol # Deployment address helper
|
|
212
80
|
```
|
|
213
81
|
|
|
214
|
-
##
|
|
215
|
-
|
|
216
|
-
| Permission | ID | Required For |
|
|
217
|
-
|------------|-----|-------------|
|
|
218
|
-
| `DEPLOY_SUCKERS` | `JBPermissionIds.DEPLOY_SUCKERS` | `deploySuckersFor` |
|
|
219
|
-
| `QUEUE_RULESETS` | `JBPermissionIds.QUEUE_RULESETS` | `launchRulesetsFor`, `queueRulesetsOf` |
|
|
220
|
-
| `SET_TERMINALS` | `JBPermissionIds.SET_TERMINALS` | `launchRulesetsFor` |
|
|
221
|
-
| `MAP_SUCKER_TOKEN` | `JBPermissionIds.MAP_SUCKER_TOKEN` | Granted to `SUCKER_REGISTRY` globally (projectId=0) at construction |
|
|
222
|
-
|
|
223
|
-
Note: `launchProjectFor` requires no permissions -- anyone can launch a project to any owner.
|
|
224
|
-
|
|
225
|
-
## Risks
|
|
82
|
+
## Risks And Notes
|
|
226
83
|
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
- **Ownership transfer timing**: The 721 hook's ownership is transferred to the project after the project NFT is minted. In `launchProjectFor`, the hook is deployed before the project exists, and ownership is transferred after `controller.launchProjectFor` returns. If the controller call reverts, the hook exists but is owned by the deployer (the whole transaction reverts, so this is safe).
|
|
84
|
+
- ruleset ID prediction is part of the implementation strategy and should be treated as a real assumption
|
|
85
|
+
- hook composition order matters because the 721 hook runs before any extra custom hook
|
|
86
|
+
- using the default empty-tier 721 config is convenient, but teams should still decide explicitly whether the hook participates in cash-out behavior
|
|
87
|
+
- deterministic salts help with cross-chain address alignment, but only when the sender and configuration match exactly
|
package/RISKS.md
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Omnichain Deployers Risk Register
|
|
2
|
+
|
|
3
|
+
This file focuses on the risks in the deployer layer that launches Juicebox projects across chains while composing 721 hooks, suckers, and optional custom data hooks.
|
|
4
|
+
|
|
5
|
+
## How to use this file
|
|
6
|
+
|
|
7
|
+
- Read `Priority risks` first; they capture the highest-blast-radius deployment and cash-out assumptions.
|
|
8
|
+
- Use the detailed sections for access control, integration, and reentrancy analysis.
|
|
9
|
+
- Treat `Invariants to Verify` as required checks for any new omnichain deployment flow.
|
|
10
|
+
|
|
11
|
+
## Priority risks
|
|
12
|
+
|
|
13
|
+
| Priority | Risk | Why it matters | Primary controls |
|
|
14
|
+
|----------|------|----------------|------------------|
|
|
15
|
+
| P0 | Registry-trusted sucker bypass | This deployer gives suckers privileged cash-out behavior based on registry answers. A bad registry entry can affect many projects. | Registry allowlists, deployment verification, and explicit registry scrutiny. |
|
|
16
|
+
| P1 | Cross-chain deployment drift | Omnichain assumptions fail if chain-specific wiring, peers, or composed hooks do not match. | Deterministic deploy ordering, parity checks, and post-deploy peer verification. |
|
|
17
|
+
| P1 | Data-hook composition mistakes | The deployer wraps or forwards custom data hooks; a bad composition can alter pay or cash-out semantics unexpectedly. | Integration tests for wrapped hooks and careful review of forwarding logic. |
|
|
18
|
+
|
|
2
19
|
|
|
3
20
|
## 1. Trust Assumptions
|
|
4
21
|
|
|
5
22
|
- **Trusted forwarder.** ERC-2771 `_msgSender()` is trusted to append the real sender. A compromised forwarder can impersonate any address for `deploySuckersFor`, `launchProjectFor`, `queueRulesetsOf`, and `launchRulesetsFor`.
|
|
6
23
|
- **Sucker registry.** `SUCKER_REGISTRY.isSuckerOf()` is the sole gatekeeper for 0% cashout tax and mint permission. A compromised or malicious registry lets any address bypass cashout taxes and mint tokens freely.
|
|
7
|
-
- **Controller trust.** The deployer passes an arbitrary `IJBController controller` parameter. `_validateController` checks `controller.DIRECTORY().controllerOf(projectId)` (a reflexive lookup through the controller's own directory reference), but during `launchProjectFor` the project does not yet exist
|
|
8
|
-
- **Extra data hooks.** Arbitrary `IJBRulesetDataHook` addresses from ruleset metadata are stored and
|
|
24
|
+
- **Controller trust.** The deployer passes an arbitrary `IJBController controller` parameter. `_validateController` checks `controller.DIRECTORY().controllerOf(projectId)` (a reflexive lookup through the controller's own directory reference), but during `launchProjectFor` the project does not yet exist, so no pre-launch controller validation is possible. The post-launch safeguard is the explicit project-ID match check on `controller.launchProjectFor(...)`.
|
|
25
|
+
- **Extra data hooks.** Arbitrary `IJBRulesetDataHook` addresses from ruleset metadata are stored and called directly from the deployer's pay/cash-out wrapper paths. A malicious hook can return arbitrary weight, cashout tax rate, or hook specifications, and can also consume all available gas or revert.
|
|
9
26
|
|
|
10
27
|
## 2. Economic / Manipulation Risks
|
|
11
28
|
|
|
@@ -22,13 +39,13 @@
|
|
|
22
39
|
## 4. DoS Vectors
|
|
23
40
|
|
|
24
41
|
- **Ruleset ID collision.** `_setup721` stores hook configs at `block.timestamp + i`. If `latestRulesetIdOf >= block.timestamp` (rulesets already queued this block), `queueRulesetsOf` reverts with `RulesetIdsUnpredictable`. An attacker who queues rulesets in the same block as the legitimate owner can front-run and block their queue attempt. Gas impact: `queueRulesetsOf` costs ~200-400k gas per ruleset queued. The collision only occurs when two transactions queue rulesets in the same block for the same project — race condition window is one block (~12 seconds on L1, 2 seconds on L2).
|
|
25
|
-
- **External hook revert.** `beforePayRecordedWith` and `beforeCashOutRecordedWith` call external hooks without try-catch. A reverting hook blocks all payments or cashouts for that project/ruleset. For cash-outs, if the 721 hook reverts, the custom hook is never reached (the revert propagates before it). Gas impact: the
|
|
42
|
+
- **External hook revert.** `beforePayRecordedWith` and `beforeCashOutRecordedWith` call external hooks without try-catch. A reverting hook blocks all payments or cashouts for that project/ruleset. For cash-outs, if the 721 hook reverts, the custom hook is never reached (the revert propagates before it). Gas impact: the direct call into the extra data hook has no gas limit, so a gas-griefing hook can consume the entire transaction gas. The 721 hook call is similarly unbounded.
|
|
26
43
|
- **721 hook deployment revert.** `HOOK_DEPLOYER.deployHookFor` is called without try-catch. A failing deployment blocks the entire project launch.
|
|
27
44
|
|
|
28
45
|
## 5. Reentrancy Surface
|
|
29
46
|
|
|
30
47
|
- **`launchProjectFor` external call chain.** The function makes external calls to: (1) `_deploy721Hook()` via `HOOK_DEPLOYER.deployHookFor()` (deploys 721 hook clone), (2) `controller.launchProjectFor()` (creates project, deploys rulesets), (3) `JBOwnable(hook).transferOwnershipToProject()` (transfers hook ownership to the new project), (4) `SUCKER_REGISTRY.deploySuckersFor()` (deploys suckers if configured), (5) `PROJECTS.transferFrom()` (transfers the project NFT to the owner). None of these calls are try-catch wrapped — a revert in any of them fails the entire launch. Reentrancy from the controller callback during project creation could call back into `launchProjectFor`, but the new project would get a different ID (monotonically incrementing), so state corruption is not possible.
|
|
31
|
-
- **`beforePayRecordedWith` delegates to external hooks.** Calls `IJBRulesetDataHook(tiered721Hook).beforePayRecordedWith(context)` (not try-caught) and optionally
|
|
48
|
+
- **`beforePayRecordedWith` delegates to external hooks.** Calls `IJBRulesetDataHook(tiered721Hook).beforePayRecordedWith(context)` (not try-caught) and optionally calls the extra data hook directly. The 721 hook and extra hook can execute arbitrary code. At callback time, no deployer state has been modified (the deployer is stateless during payments — it only routes). Reentrancy through the pay path processes as an independent payment.
|
|
32
49
|
- **`beforeCashOutRecordedWith` delegates to external hooks.** Same pattern as pay: calls the 721 hook (not try-caught), then optionally the extra data hook. Sucker check via `SUCKER_REGISTRY.isSuckerOf` is a view call. No deployer state is modified during cashouts.
|
|
33
50
|
- **No `ReentrancyGuard`.** Safe because the deployer is effectively stateless during pay/cashout operations — it reads `_tiered721HookOf` and `_extraDataHookOf` mappings but never writes them outside of deployment functions.
|
|
34
51
|
|
|
@@ -53,7 +70,7 @@
|
|
|
53
70
|
|
|
54
71
|
### 8.1 Controller validation skipped during `launchProjectFor` (by design)
|
|
55
72
|
|
|
56
|
-
`_validateController` checks `controller.DIRECTORY().controllerOf(projectId)` to verify the provided controller matches the project's registered controller. During `launchProjectFor`, the project does not yet exist, so no directory entry exists.
|
|
73
|
+
`_validateController` checks `controller.DIRECTORY().controllerOf(projectId)` to verify the provided controller matches the project's registered controller. During `launchProjectFor`, the project does not yet exist, so no directory entry exists and no pre-launch validation is possible. The accepted safeguard is the explicit `ProjectIdMismatch` check immediately after `controller.launchProjectFor()` returns. This is accepted because: (1) the project is created atomically within the same transaction, (2) the caller provides the controller address, so they choose their own trust boundary, and (3) validating against a non-existent project would always fail, making the check useless.
|
|
57
74
|
|
|
58
75
|
### 8.2 Suckers receive 0% cashout tax (shared with revnet-core)
|
|
59
76
|
|