@bananapus/omnichain-deployers-v6 0.0.27 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/ADMINISTRATION.md +10 -59
  2. package/ARCHITECTURE.md +11 -90
  3. package/AUDIT_INSTRUCTIONS.md +8 -63
  4. package/README.md +11 -13
  5. package/RISKS.md +57 -46
  6. package/SKILLS.md +10 -29
  7. package/USER_JOURNEYS.md +31 -110
  8. package/package.json +7 -7
  9. package/script/Deploy.s.sol +8 -1
  10. package/src/JBOmnichainDeployer.sol +20 -7
  11. package/test/JBOmnichainDeployer.t.sol +2 -12
  12. package/test/JBOmnichainDeployerGuard.t.sol +1 -0
  13. package/test/OmnichainDeployerAttacks.t.sol +3 -1
  14. package/test/OmnichainDeployerEdgeCases.t.sol +4 -11
  15. package/test/TestAuditGaps.sol +4 -18
  16. package/test/Tiered721HookComposition.t.sol +2 -4
  17. package/test/audit/AuditFixesC2H6M14.t.sol +495 -0
  18. package/test/audit/CarryForwardRejectedHook.t.sol +1 -0
  19. package/test/audit/CodexNemesisAudit.t.sol +21 -10
  20. package/test/audit/CodexNemesisDeterministicDrift.t.sol +78 -0
  21. package/test/audit/CodexNemesisDeterministicPeerDrift.t.sol +79 -0
  22. package/test/audit/CodexNemesisForwardedPermissions.t.sol +297 -0
  23. package/test/audit/CodexNemesisNftCashoutSupplyMismatch.t.sol +222 -0
  24. package/test/audit/JBOmnichainDeployer.t.sol +10 -6
  25. package/test/audit/WeightScalingComparison.t.sol +3 -1
  26. package/test/fork/OmnichainForkTestBase.sol +3 -2
  27. package/test/invariants/CrossChainDeployerInvariant.t.sol +204 -0
  28. package/test/invariants/handlers/CrossChainDeployerHandler.sol +395 -0
  29. package/test/regression/EmptyRulesetConfigurations.t.sol +3 -5
  30. package/test/regression/HookOwnershipTransfer.t.sol +2 -5
  31. package/test/regression/ValidateController.t.sol +3 -11
package/ADMINISTRATION.md CHANGED
@@ -4,75 +4,26 @@
4
4
 
5
5
  | Item | Details |
6
6
  | --- | --- |
7
- | Scope | Omnichain project launch, ruleset queuing, runtime wrapper config, and sucker deployment integration |
8
- | Control posture | Mixed deployer/wrapper control plus project-local delegated authority |
9
- | Highest-risk actions | Queuing rulesets with bad wrapper config, launching with the wrong controller assumptions, and misconfigured sucker deployment |
10
- | Recovery posture | Some config can be superseded with new rulesets; wrong immutable dependencies require replacement infra |
7
+ | Scope | Omnichain launch orchestration and wrapper behavior |
8
+ | Control posture | Mixed deployer logic, project permissions, and registry trust |
9
+ | Highest-risk actions | Wrong hook composition, wrong sucker wiring, and bad registry trust |
10
+ | Recovery posture | Often requires redeploying or re-launching around bad wiring |
11
11
 
12
12
  ## Purpose
13
13
 
14
- `nana-omnichain-deployers-v6` combines deployment authority with runtime wrapper authority. The critical admin question is not just who can launch or queue rulesets, but also who controls the wrapped hook composition and registered sucker behavior afterward.
14
+ This repo controls how omnichain projects are launched and wrapped, not the low-level runtime logic of suckers or 721 hooks.
15
15
 
16
16
  ## Control Model
17
17
 
18
- - `JBOmnichainDeployer` is both deployer and live data-hook wrapper.
19
- - Project-local authority flows through project ownership and `JBPermissions`.
20
- - `SUCKER_REGISTRY` holds structural `MAP_SUCKER_TOKEN` authority granted to the deployer.
21
- - Sucker deployment requires project-local `DEPLOY_SUCKERS` permission.
22
- - Hook composition data is stored by ruleset and then used at runtime.
23
-
24
- ## Roles
25
-
26
- | Role | How Assigned | Scope | Notes |
27
- | --- | --- | --- | --- |
28
- | Project owner | `JBProjects.ownerOf(projectId)` | Per project | Can delegate through `JBPermissions` |
29
- | Project operator | `JBPermissions` grant | Per project | Often `DEPLOY_SUCKERS`, `QUEUE_RULESETS`, `LAUNCH_RULESETS`, `SET_TERMINALS` |
30
- | `JBOmnichainDeployer` | Immutable singleton | Global | Launch helper and runtime wrapper |
31
- | `SUCKER_REGISTRY` | Immutable dependency | Global | Receives wildcard `MAP_SUCKER_TOKEN` from the deployer |
32
-
33
- ## Privileged Surfaces
34
-
35
- | Contract | Function | Who Can Call | Effect |
36
- | --- | --- | --- | --- |
37
- | `JBOmnichainDeployer` | `deploySuckersFor(...)` | Project owner or `DEPLOY_SUCKERS` delegate | Extends an existing project with suckers |
38
- | `JBOmnichainDeployer` | `launchProjectFor(...)` | Anyone | Launches a new omnichain-shaped project |
39
- | `JBOmnichainDeployer` | `launchRulesetsFor(...)` | Project owner or relevant delegates | Launches rulesets for an existing project |
40
- | `JBOmnichainDeployer` | `queueRulesetsOf(...)` | Project owner or `QUEUE_RULESETS` delegate | Queues rulesets and stores runtime wrapper config |
41
-
42
- ## Immutable And One-Way
43
-
44
- - Constructor dependencies are immutable.
45
- - Ruleset-keyed hook configuration becomes the runtime source of truth once stored.
46
- - Deterministic sucker deployment assumptions depend on stable salts and deployer config.
47
- - The deployer's wildcard grant to `SUCKER_REGISTRY` is structural.
48
-
49
- ## Operational Notes
50
-
51
- - Review launch and runtime-wrapper behavior together for every admin change.
52
- - Validate controller matching on existing-project flows before launch or queue operations.
53
- - Treat hook-order changes as runtime behavior changes, not just deployment metadata changes.
54
- - Arrange token-mapping authority for the registry before using the end-to-end sucker deployment path.
55
-
56
- ## Machine Notes
57
-
58
- - Do not treat this repo as deployment-only; queued wrapper config is a live runtime input.
59
- - Inspect `src/JBOmnichainDeployer.sol` alongside project rulesets before assuming current behavior.
60
- - If directory/controller state and stored wrapper config diverge, stop and resolve the mismatch before further admin actions.
18
+ - launch paths are largely permissionless for new projects
19
+ - later ruleset changes depend on project permissions
20
+ - registry and sucker trust surfaces can widen authority if misconfigured
61
21
 
62
22
  ## Recovery
63
23
 
64
- - If a ruleset was queued with bad wrapper config, recover through new rulesets if the project still has the necessary authority.
65
- - If the wrong deterministic deployment assumptions or constructor dependencies were used, recover with replacement infra.
24
+ - bad launch wiring usually means a new deployment path rather than a local patch
66
25
 
67
26
  ## Admin Boundaries
68
27
 
69
- - The deployer cannot bypass project-local permission checks on existing-project launch, queue, or sucker deployment paths.
70
- - It cannot mutate constructor immutables after deployment.
71
- - It does not own core treasury accounting or project ownership semantics outside the flows it wraps.
72
-
73
- ## Source Map
28
+ - this repo does not override locked runtime behavior in sibling repos
74
29
 
75
- - `src/JBOmnichainDeployer.sol`
76
- - `src/interfaces/IJBOmnichainDeployer.sol`
77
- - `src/structs/`
78
- - `test/`
package/ARCHITECTURE.md CHANGED
@@ -2,109 +2,30 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- `nana-omnichain-deployers-v6` launches Juicebox projects that are ready for both tiered NFTs and cross-chain suckers from day one. It is also a live wrapper data hook that composes a 721 hook with an optional extra hook while giving registered suckers the bridge-safe path they need.
5
+ `nana-omnichain-deployers-v6` packages a Juicebox project, a 721 hook, and sucker deployment into one omnichain launch surface.
6
6
 
7
7
  ## System Overview
8
8
 
9
- `JBOmnichainDeployer` is both deployer and runtime wrapper; those roles are inseparable. At launch time it can deploy a project, install itself as the ruleset data hook, deploy or carry forward the 721 hook, and optionally deploy sucker pairs with deterministic salts. At runtime it composes hook specs and grants special behavior to registered suckers so bridging does not get trapped behind project-specific cash-out policy.
9
+ `JBOmnichainDeployer` launches the project, stores per-ruleset hook composition, and wraps sucker behavior so bridge-triggered flows can bypass project-specific logic where intended.
10
10
 
11
11
  ## Core Invariants
12
12
 
13
- - Registered suckers must be able to bridge without getting blocked by custom cash-out or mint policy.
14
- - Hook order is intentional: the 721 hook runs first, then the optional extra hook.
15
- - Ruleset ID prediction must stay aligned with `JBRulesets`; stored hook config keys depend on it.
16
- - Every project launched through this repo gets a 721-hook surface, even if it starts with zero tiers.
17
- - This wrapper always becomes the on-chain ruleset data hook and forces both pay and cash-out callbacks through itself before delegating internally.
18
- - Extra pay hooks must see only the post-721 project amount and the 721-adjusted weight, not the raw terminal payment context.
19
- - Queue-path carry-forward must prefer the latest approved queued ruleset over the current active ruleset when recovering hook config.
20
-
21
- ## Modules
22
-
23
- | Module | Responsibility | Notes |
24
- | --- | --- | --- |
25
- | `JBOmnichainDeployer` | Launch, queue rulesets, compose hooks, and grant sucker-safe behavior | Deployer and runtime wrapper |
26
- | config structs | 721 config, extra hook config, and sucker deployment config | Launch-time inputs |
27
- | `IJBOmnichainDeployer` | External launch and inspection interface | Public surface |
13
+ - launch wiring must match the intended omnichain project shape
14
+ - hook composition must stay consistent with the created ruleset IDs
15
+ - sucker-specific privileged paths must remain limited to trusted suckers
16
+ - project NFT ownership and hook ownership must end in the intended place
28
17
 
29
18
  ## Trust Boundaries
30
19
 
31
- - Accounting and project ownership transfer remain rooted in `nana-core-v6`.
32
- - Tier behavior comes from `nana-721-hook-v6`.
33
- - Cross-chain transport comes from `nana-suckers-v6`.
34
- - Project-following ownership behavior comes from `nana-ownable-v6`.
35
-
36
- ## Critical Flows
37
-
38
- ### Launch
39
-
40
- ```text
41
- caller
42
- -> launches a project or queues rulesets through the deployer
43
- -> deployer installs itself as the ruleset data hook
44
- -> deploys or carries forward the 721 hook
45
- -> optionally deploys sucker pairs with deterministic salts
46
- -> transfers project ownership to the intended owner
47
- ```
48
-
49
- ### Queue With Carry-Forward
50
-
51
- ```text
52
- caller
53
- -> queues a new ruleset without new tiers
54
- -> deployer selects carry-forward hook config from the latest approved queued ruleset when present
55
- -> otherwise falls back to the current active ruleset
56
- -> preserves useDataHookForCashOut from the chosen source ruleset
57
- ```
58
-
59
- ### Runtime Wrapping
60
-
61
- ```text
62
- runtime callback
63
- -> if the actor is a registered sucker, return the bridge-safe tax-free and mint-enabled path
64
- -> otherwise run the 721 hook first when configured
65
- -> pass the extra pay hook only the post-split project amount and 721-adjusted weight
66
- -> then run the optional extra hook
67
- -> merge and return the resulting specs
68
- ```
69
-
70
- ## Accounting Model
71
-
72
- This repo does not own the treasury ledger. Its critical state is hook configuration and ruleset-keyed carry-forward data that determine how downstream accounting hooks are composed.
73
-
74
- The wrapper also computes cross-chain cash-out context for non-sucker paths by augmenting local supply and surplus with remote sucker snapshots. Inner hooks may adjust tax rate or counts, but this repo keeps the omnichain supply/surplus view authoritative.
20
+ - bridge runtime trust lives in `nana-suckers-v6`
21
+ - 721 runtime trust lives in `nana-721-hook-v6`
22
+ - this repo mainly owns orchestration and wrapper semantics
75
23
 
76
24
  ## Security Model
77
25
 
78
- - The largest risk is silent drift between deploy-time assumptions and runtime wrapper behavior.
79
- - Ruleset ID prediction is storage-key critical.
80
- - The sucker exception path intentionally short-circuits normal composition and should stay easy to reason about.
81
- - Suckers have two privileged behaviors by design: tax-free bridge cash-outs and unconditional mint permission. Those exceptions must remain tightly scoped to registry-recognized sucker addresses.
82
- - Salt derivation includes `_msgSender()` for replay protection. Cross-chain deterministic matching therefore depends on using the same sender on each chain.
83
- - Because this repo is both deployer and runtime hook, permission or hook-order changes can break already-launched projects, not just future launches.
84
-
85
- ## Safe Change Guide
86
-
87
- - Review launch logic and runtime wrapper logic together.
88
- - If hook composition changes, test payment and cash-out ordering explicitly.
89
- - If ruleset prediction changes, test same-block and queued-ruleset edge cases.
90
- - If sucker exceptions or mint-permission behavior change, re-check bridge flows against normal custom-hook policy.
91
- - If salt derivation changes, re-check deterministic cross-chain deployment expectations and replay resistance together.
92
- - If wrapper metadata behavior changes, re-check the forced `useDataHookForPay/useDataHookForCashOut` install path and the extra-hook context shaping together.
93
- - Keep salt handling stable across chains when deterministic address expectations matter.
94
-
95
- ## Canonical Checks
96
-
97
- - hook ordering and composition correctness:
98
- `test/Tiered721HookComposition.t.sol`
99
- - carry-forward and queued-ruleset recovery behavior:
100
- `test/audit/CarryForwardRejectedHook.t.sol`
101
- - wrapper invariants under adversarial sequences:
102
- `test/invariants/OmnichainDeployerInvariant.t.sol`
26
+ - the main risks are hook composition, ruleset ID prediction, and registry-trusted sucker bypasses
27
+ - this repo is not the source of underlying bridge or 721 behavior, but it can wire them together incorrectly
103
28
 
104
29
  ## Source Map
105
30
 
106
31
  - `src/JBOmnichainDeployer.sol`
107
- - `src/structs/`
108
- - `test/Tiered721HookComposition.t.sol`
109
- - `test/audit/CarryForwardRejectedHook.t.sol`
110
- - `test/invariants/OmnichainDeployerInvariant.t.sol`
@@ -1,81 +1,26 @@
1
1
  # Audit Instructions
2
2
 
3
- This repo launches projects that are immediately composed with 721 hooks and sucker deployments. Audit it as a privileged deployer and runtime data-hook participant.
3
+ Audit this repo as an orchestration layer that composes 721 hooks, suckers, and optional extra data hooks.
4
4
 
5
5
  ## Audit Objective
6
6
 
7
7
  Find issues that:
8
- - launch projects with incorrect rulesets, terminals, or hook ownership
9
- - grant cash-out or mint privileges to non-suckers
10
- - mis-scale weight or tax behavior during omnichain-specific data-hook flows
11
- - leave deployed hook or sucker ownership in the wrong hands
12
- - create inconsistent behavior between local-only and omnichain project launches
8
+
9
+ - launch the wrong project shape
10
+ - miscompose hooks or wrapper behavior
11
+ - grant privileged sucker behavior to the wrong addresses
12
+ - create cross-chain drift or bad deterministic assumptions
13
13
 
14
14
  ## Scope
15
15
 
16
16
  In scope:
17
- - `src/JBOmnichainDeployer.sol`
18
- - `src/interfaces/`
19
- - `src/structs/`
20
- - deployment scripts in `script/`
21
17
 
22
- Key dependencies:
23
- - `nana-core-v6`
24
- - `nana-721-hook-v6`
25
- - `nana-suckers-v6`
18
+ - `src/JBOmnichainDeployer.sol`
19
+ - related tests under `test/`
26
20
 
27
21
  ## Start Here
28
22
 
29
23
  1. `src/JBOmnichainDeployer.sol`
30
- 2. `script/Deploy.s.sol`
31
- 3. `script/helpers/DeployersDeploymentLib.sol`
32
-
33
- ## Security Model
34
-
35
- `JBOmnichainDeployer` is a launch surface that can:
36
- - create a new Juicebox project
37
- - deploy and configure a 721 hook
38
- - configure rulesets and terminals
39
- - deploy suckers and register them for the project
40
- - participate in pay or cash-out accounting as a data hook where needed
41
-
42
- ## Roles And Privileges
43
-
44
- | Role | Powers | How constrained |
45
- |------|--------|-----------------|
46
- | Launch caller | Supply desired project configuration | Should receive exactly the requested state |
47
- | Omnichain deployer | Create hooks, projects, and sucker composition | Must relinquish setup authority after launch |
48
- | Sucker registry | Grant omnichain-specific privileges | Must not bless arbitrary contracts |
49
-
50
- ## Integration Assumptions
51
-
52
- | Dependency | Assumption | What breaks if wrong |
53
- |------------|------------|----------------------|
54
- | `nana-core-v6` | Launch and ruleset surfaces are authentic | Deployed economics drift from requested config |
55
- | `nana-721-hook-v6` | Hook ownership and tier setup complete correctly | Collection state or authority is wrong |
56
- | `nana-suckers-v6` | Registry identifies genuine peers | Fee or mint exemptions widen incorrectly |
57
-
58
- ## Critical Invariants
59
-
60
- 1. Launch configuration is faithful
61
- The deployed project must end up with the exact hook, ruleset, and ownership configuration the caller requested.
62
-
63
- 2. Sucker privileges stay restricted
64
- Zero-tax or mint-permission behavior intended for legitimate suckers must not be reachable by arbitrary contracts or stale registry entries.
65
-
66
- 3. Weight and accounting scaling are correct
67
- If the deployer proxies or modifies hook outputs, the resulting project token issuance and reclaim math must still match intended economics.
68
-
69
- 4. Ownership transfer is complete
70
- Deployer-created hooks and helper contracts must not retain silent control after initialization.
71
-
72
- ## Attack Surfaces
73
-
74
- - malformed launch configuration
75
- - hook and sucker ownership transfer
76
- - registry-based privilege spoofing
77
- - reentrancy around launch and initialization
78
- - local-only launches versus omnichain launches with optional components disabled
79
24
 
80
25
  ## Verification
81
26
 
package/README.md CHANGED
@@ -24,8 +24,6 @@ The wrapper exists so sucker-triggered flows can be exempted from project-specif
24
24
 
25
25
  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.
26
26
 
27
- 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.
28
-
29
27
  ## Key Contract
30
28
 
31
29
  | Contract | Role |
@@ -49,17 +47,17 @@ This repo owns orchestration plus runtime wrapping:
49
47
 
50
48
  ## Integration Traps
51
49
 
52
- - this repo wraps hooks and bridge flows together, so ownership and hook-order assumptions matter as much as the deployment salt
53
- - ruleset ID prediction is an implementation dependency and should be reviewed as an actual invariant
54
- - the deployer can carry forward an existing 721 hook shape, so stale assumptions about hook config can leak across deployments
55
- - bridge-safe wrapper behavior is part of the runtime trust model, not just deployment ergonomics
50
+ - this repo wraps hooks and bridge flows together, so ownership and hook-order assumptions matter as much as deployment salt
51
+ - ruleset ID prediction is a real implementation dependency
52
+ - the deployer can carry forward an existing 721 hook shape, so stale hook assumptions can leak across deployments
53
+ - bridge-safe wrapper behavior is part of the runtime trust model
56
54
 
57
55
  ## Where State Lives
58
56
 
59
- - orchestration and wrapper logic live in `JBOmnichainDeployer`
60
- - bridge runtime state lives in `nana-suckers-v6`
61
- - 721 tier state lives in `nana-721-hook-v6`
62
- - any extra hook behavior lives in the additional repo composed into the deployment
57
+ - orchestration and wrapper logic: `JBOmnichainDeployer`
58
+ - bridge runtime state: `nana-suckers-v6`
59
+ - 721 tier state: `nana-721-hook-v6`
60
+ - extra hook behavior: the additional repo composed into the deployment
63
61
 
64
62
  ## Install
65
63
 
@@ -100,10 +98,10 @@ script/
100
98
 
101
99
  ## Risks And Notes
102
100
 
103
- - ruleset ID prediction is part of the implementation strategy and should be treated as a real assumption
101
+ - ruleset ID prediction is part of the implementation strategy
104
102
  - hook composition order matters because the 721 hook runs before any extra custom hook
105
- - using the default empty-tier 721 config is convenient, but teams should still decide explicitly whether the hook participates in cash-out behavior
106
- - deterministic salts help with cross-chain address alignment, but only when the sender and configuration match exactly
103
+ - default empty-tier 721 config is convenient, but teams should still decide explicitly whether the hook participates in cash-out behavior
104
+ - deterministic salts only help with cross-chain address alignment when sender and configuration match exactly
107
105
 
108
106
  ## For AI Agents
109
107
 
package/RISKS.md CHANGED
@@ -1,81 +1,92 @@
1
1
  # Omnichain Deployers Risk Register
2
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.
3
+ This file covers the risks in the deployer layer that launches Juicebox projects across chains while composing 721 hooks, suckers, and optional custom data hooks.
4
4
 
5
- ## How to use this file
5
+ ## How To Use This File
6
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.
7
+ - Read `Priority risks` first. They capture the highest-blast-radius deployment and cash-out assumptions.
8
+ - Treat `Invariants to verify` as required checks for any new omnichain deployment flow.
10
9
 
11
- ## Priority risks
10
+ ## Priority Risks
12
11
 
13
12
  | Priority | Risk | Why it matters | Primary controls |
14
13
  |----------|------|----------------|------------------|
15
14
  | 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
15
  | 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
-
16
+ | 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 and careful forwarding review. |
19
17
 
20
18
  ## 1. Trust Assumptions
21
19
 
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`.
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.
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.
20
+ - **Trusted forwarder is trusted.**
21
+ - **Sucker registry answers are trusted.**
22
+ - **Controller trust matters.**
23
+ - **Extra data hooks are trusted code.**
26
24
 
27
- ## 2. Economic / Manipulation Risks
25
+ ## 2. Economic Risks
28
26
 
29
- - **Sucker cashout bypass.** Any address registered as a sucker for a project gets 0% cashout tax rate and full reclaim. If a malicious sucker is registered (via compromised `SUCKER_REGISTRY`), it can drain the project's surplus.
30
- - **Weight manipulation via extra data hook.** `beforePayRecordedWith` forwards to the extra data hook, which can return any `weight`. A malicious hook can inflate token minting or set weight=0 to block minting.
31
- - **721 hook amount splitting.** The deployer computes `projectAmount = context.amount.value - totalSplitAmount`. The 721 hook's returned weight (already adjusted for splits via `JB721TiersHookLib.calculateWeight`) is used directly -- no proportional scaling is applied. If the 721 hook returns a `totalSplitAmount >= context.amount.value`, `projectAmount` is set to 0 and weight becomes 0 -- no tokens are minted for the payment.
32
- - **Cross-chain sender dependence in sucker deployment salts.** `deploySuckersFor` salts deployments with `keccak256(abi.encode(userSalt, _msgSender()))`. This prevents replay collisions, but it also means the same logical project deployed by different operators on different chains will not get matching deterministic sucker addresses.
27
+ - **Sucker cashout bypass exists for registered suckers.**
28
+ - **Extra data hooks can manipulate weight or cash-out behavior.**
29
+ - **721 hook amount splitting can zero out project amount in edge cases.**
30
+ - **Cross-chain sender dependence affects deterministic sucker salts.**
33
31
 
34
32
  ## 3. Access Control
35
33
 
36
- - **Wildcard MAP_SUCKER_TOKEN permission.** Constructor grants `SUCKER_REGISTRY` the `MAP_SUCKER_TOKEN` permission with `projectId=0` (wildcard). This grants the registry token-mapping rights across all projects ever deployed through this deployer.
37
- - **Permission checks on `launchRulesetsFor`.** Requires both `LAUNCH_RULESETS` and `SET_TERMINALS` from the project owner. If an operator has one but not the other, the call reverts. No combined permission ID exists.
38
- - **No permission check on `launchProjectFor`.** Anyone can call it because a new project is being created. The `owner` parameter receives the project NFT -- verify frontends do not allow this to be set to unexpected addresses.
34
+ - **Wildcard `MAP_SUCKER_TOKEN` permission is broad.**
35
+ - **`launchRulesetsFor` requires combined permissions.**
36
+ - **`launchProjectFor` is intentionally permissionless for new projects.**
39
37
 
40
38
  ## 4. DoS Vectors
41
39
 
42
- - **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).
43
- - **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.
44
- - **721 hook deployment revert.** `HOOK_DEPLOYER.deployHookFor` is called without try-catch. A failing deployment blocks the entire project launch.
45
- - **Unexpected safe NFT receipt reverts, but non-safe transfers can still strand assets.** `onERC721Received` only accepts `PROJECTS` NFTs. Safe transfers of any other ERC-721 revert instead of being accepted. The narrower residual risk is `transferFrom` or other non-safe delivery into the deployer, which bypasses the receiver hook and has no generalized rescue path.
40
+ - **Ruleset ID collision can block queueing.**
41
+ - **External hook reverts can block pay or cash-out flows.**
42
+ - **721 hook deployment revert blocks launch.**
43
+ - **Non-safe NFT transfers can still strand assets.**
46
44
 
47
45
  ## 5. Reentrancy Surface
48
46
 
49
- - **`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.
50
- - **`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.
51
- - **`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.
52
- - **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.
47
+ - **`launchProjectFor` makes several external calls in sequence.**
48
+ - **`beforePayRecordedWith` delegates to external hooks.**
49
+ - **`beforeCashOutRecordedWith` delegates to external hooks.**
50
+ - **There is no `ReentrancyGuard`.** The deployer relies on being effectively stateless during pay and cash-out operations.
53
51
 
54
52
  ## 6. Integration Risks
55
53
 
56
- - **Hook config keyed by predicted rulesetId.** Configs stored at `block.timestamp + i` must match the actual rulesetId assigned by the controller. If the controller assigns different IDs (e.g., due to approval hook delays), the stored configs become unreachable -- payments/cashouts fall through to default behavior (no 721 handling, no extra hook).
57
- - **Carried-forward 721 hook on queue.** When `tiers.length == 0`, `queueRulesetsOf` carries forward the hook from a previous ruleset. The source is selected by first checking `latestQueuedOf(projectId)` — if the queued ruleset's approval status is `Approved` or `Empty` and it has a stored hook config, that config is used. Otherwise it falls back to `currentOf(projectId)`. If neither has a hook deployed through this deployer, the mapping is empty and the call reverts with `JBOmnichainDeployer_InvalidHook`. The `useDataHookForCashOut` flag is also preserved from whichever source ruleset is selected.
58
- - **ERC721Receiver restriction.** `onERC721Received` only accepts from `PROJECTS`. Non-project NFTs sent via `safeTransferFrom` revert cleanly; non-safe transfers can still become stuck because the deployer has no generalized rescue function.
59
- - **Empty simplified launch config reverts.** The convenience overload that derives a default 721 config from `rulesetConfigurations[0]` reverts with `JBOmnichainDeployer_NoRulesetConfigurations()` when called with an empty ruleset array.
60
- - **Cross-reference: sucker registration.** The deployer grants `MAP_SUCKER_TOKEN` to `SUCKER_REGISTRY` with `projectId=0` (wildcard). This means the registry can map tokens for ALL projects deployed through this deployer. See [nana-suckers-v6 RISKS.md](../nana-suckers-v6/RISKS.md) for the full sucker lifecycle risks.
61
- - **Cross-reference: core reentrancy.** The deployer delegates to `JBController` and `JBMultiTerminal` for all fund operations. See [nana-core-v6 RISKS.md](../nana-core-v6/RISKS.md) section 3 for the reentrancy surface of these contracts.
54
+ - **Hook config is keyed by predicted ruleset ID.**
55
+ - **Carried-forward 721 hook behavior on queue depends on prior ruleset state.**
56
+ - **ERC721Receiver restrictions are narrow but non-safe transfers can still strand assets.**
57
+ - **Empty simplified launch config reverts.**
62
58
 
63
- ## 7. Invariants to Verify
59
+ ## 7. Invariants To Verify
64
60
 
65
- - For any project launched through this deployer, `DIRECTORY.controllerOf(projectId)` matches the controller used during launch.
66
- - `_tiered721HookOf[projectId][rulesetId]` is non-zero for every rulesetId created through this deployer.
67
- - Sucker cashouts always receive 0% tax rate (no path where `isSuckerOf` returns true but tax > 0).
68
- - `beforePayRecordedWith` uses the 721 hook's weight directly (already split-adjusted by `JB721TiersHookLib.calculateWeight`), so no additional scaling is applied.
69
- - Self-reference prevention: `rulesetConfigurations[i].metadata.dataHook` cannot be `address(this)` after `_setup721`.
70
- - Project NFT ownership: after `_launchProjectFor`, the project NFT is owned by `owner`, not the deployer.
71
- - `deploySuckersFor` uses the same `_msgSender()` on every chain when deterministic cross-chain peer symmetry is expected.
61
+ - launched projects point at the intended controller
62
+ - stored 721 hook config exists for every ruleset created through this deployer
63
+ - sucker cashouts always get the intended zero-tax path
64
+ - self-reference prevention holds after setup
65
+ - the project NFT ends owned by the intended owner
72
66
 
73
67
  ## 8. Accepted Behaviors
74
68
 
75
- ### 8.1 Controller validation skipped during `launchProjectFor` (by design)
69
+ ### 8.1 Controller validation is skipped during initial launch
70
+
71
+ Pre-launch controller validation is impossible because the project does not yet exist. The accepted safeguard is the post-launch project ID match check.
72
+
73
+ ### 8.2 Registered suckers receive 0% cashout tax
74
+
75
+ This is intentional and shares the same trust boundary as the sucker registry.
76
+
77
+ ## 9. Accepted Security Risks
78
+
79
+ Documented risks that were reviewed and accepted.
80
+
81
+ ### Configuration Risks
82
+
83
+ **Unvalidated extra data hooks can brick live flows.** *(Minor)*
84
+ Extra data hooks provided by the project owner in `_setup721` configuration can fail and brick live pay/cashout flows. Accepted because this is self-inflicted misconfiguration — only the project owner can set these hooks.
76
85
 
77
- `_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.
86
+ **Missing hook721 alias check enables double invocation.** *(Minor)*
87
+ If the project owner configures the 721 hook as both the primary hook and as an extra data hook, it could be invoked twice. Accepted because this is self-inflicted misconfiguration — the deployer correctly processes each hook independently.
78
88
 
79
- ### 8.2 Suckers receive 0% cashout tax (shared with revnet-core)
89
+ ### Cross-Chain Deployment
80
90
 
81
- `beforeCashOutRecordedWith` returns `cashOutTaxRate = 0` for addresses registered in `SUCKER_REGISTRY`. This is the same trust model as REVDeployer. The security boundary is the sucker registry — only addresses deployed through authorized deployers receive this privilege. See [revnet-core-v6 RISKS.md](../revnet-core-v6/RISKS.md) section 4 for the full sucker bypass analysis.
91
+ **`_msgSender()` in deployment salt breaks cross-chain determinism.** *(Minor)*
92
+ `deploySuckersFor` includes `_msgSender()` in the CREATE2 salt, which means the same deployment from different callers produces different addresses across chains. Accepted because this is intentional replay protection — prevents frontrunning of cross-chain deployments.
package/SKILLS.md CHANGED
@@ -2,43 +2,24 @@
2
2
 
3
3
  ## Use This File For
4
4
 
5
- - Use this file when the task involves deploying core projects with suckers and optional 721 or custom data-hook composition in one flow.
6
- - Start here, then decide whether the question is about deterministic deployment, ruleset-to-hook storage, wrapper pay/cash-out delegation, or sucker deployment authority. Those are distinct sources of failure here.
5
+ - Use this file when the task involves omnichain project launch, sucker deployment, or wrapped 721-hook composition.
6
+ - Start here, then decide whether the issue is in launch orchestration, hook composition, or bridge-specific runtime behavior.
7
7
 
8
8
  ## Read This Next
9
9
 
10
10
  | If you need... | Open this next |
11
11
  |---|---|
12
- | Repo overview and launch model | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
13
- | Deployment implementation | [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol), [`script/Deploy.s.sol`](./script/Deploy.s.sol) |
14
- | Runtime wrapper and config assumptions | [`references/runtime.md`](./references/runtime.md), [`references/operations.md`](./references/operations.md) |
15
- | Input and output types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
16
- | Guard rails, attacks, and hook-composition behavior | [`test/JBOmnichainDeployerGuard.t.sol`](./test/JBOmnichainDeployerGuard.t.sol), [`test/OmnichainDeployerAttacks.t.sol`](./test/OmnichainDeployerAttacks.t.sol), [`test/OmnichainDeployerReentrancy.t.sol`](./test/OmnichainDeployerReentrancy.t.sol), [`test/Tiered721HookComposition.t.sol`](./test/Tiered721HookComposition.t.sol) |
17
- | Baseline deployment and pinned edge cases | [`test/JBOmnichainDeployer.t.sol`](./test/JBOmnichainDeployer.t.sol), [`test/OmnichainDeployerEdgeCases.t.sol`](./test/OmnichainDeployerEdgeCases.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol) |
18
-
19
- ## Repo Map
20
-
21
- | Area | Where to look |
22
- |---|---|
23
- | Main contract | [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol) |
24
- | Types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
25
- | Scripts | [`script/`](./script/) |
26
- | Tests | [`test/`](./test/) |
12
+ | Repo overview and architecture | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
13
+ | Main deployer | [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol) |
14
+ | Bridge runtime | [`../nana-suckers-v6/src/JBSucker.sol`](../nana-suckers-v6/src/JBSucker.sol) |
15
+ | 721 hook runtime | [`../nana-721-hook-v6/src/JB721TiersHook.sol`](../nana-721-hook-v6/src/JB721TiersHook.sol) |
27
16
 
28
17
  ## Purpose
29
18
 
30
- Single-transaction deployment and wrapper layer for Juicebox projects that need a 721 hook, cross-chain sucker support, and optional extra data-hook composition from day one.
31
-
32
- ## Reference Files
33
-
34
- - Open [`references/runtime.md`](./references/runtime.md) when you need hook-composition order, sucker exemptions, per-ruleset storage behavior, or the main runtime invariants.
35
- - Open [`references/operations.md`](./references/operations.md) when you need launch and queue behavior, permission and salt assumptions, test breadcrumbs, or the common stale-data traps.
19
+ Orchestration and wrapper layer for launching projects with suckers and a 721 hook already wired in.
36
20
 
37
21
  ## Working Rules
38
22
 
39
- - Start in [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol) for both deployment and runtime wrapping behavior. This repo is orchestration plus a live data-hook wrapper, not a pure deploy script package.
40
- - Treat ruleset ID prediction, hook-carry-forward logic, and salt derivation as high-risk. Small changes there can orphan config or break deterministic deployment.
41
- - The deployer stores hook config per predicted ruleset ID. Same-block queue assumptions and carry-forward behavior are core correctness issues, not edge cases.
42
- - When a bug mentions suckers, tax-free cash outs, or mint permission, verify whether the early-return wrapper behavior is the real cause before changing downstream hooks.
43
- - Existing-project sucker deployment is not just “deploy clones”; it also depends on token-mapping authority landing correctly through the registry path.
44
- - If a task mentions 721 or custom-hook behavior, confirm whether the issue lives here or in the composed repo before patching wrapper logic.
23
+ - Start in [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol).
24
+ - Treat ruleset ID prediction as a real implementation dependency.
25
+ - Keep wrapper behavior and the underlying 721 or sucker behavior separate in your reasoning.