@bananapus/omnichain-deployers-v6 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ADMINISTRATION.md CHANGED
@@ -1,156 +1,29 @@
1
1
  # Administration
2
2
 
3
- Admin privileges and their scope in nana-omnichain-deployers-v6.
4
-
5
3
  ## At A Glance
6
4
 
7
5
  | Item | Details |
8
- |------|---------|
9
- | Scope | Omnichain project launch, hook composition, sucker deployment, and the deployer's data-hook proxy behavior. |
10
- | Operators | Project owners and delegates, the `JBOmnichainDeployer`, and the configured `JBSuckerRegistry` with its wildcard token-mapping grant. |
11
- | Highest-risk actions | Launching a project with the wrong hook composition, terminal configuration, or cross-chain setup, then assuming it can be rewritten later. |
12
- | Recovery posture | The deployer's immutable dependencies cannot be edited in place. Project-level recovery usually means launching corrected rulesets or redeploying the broader project path. |
13
-
14
- ## Routine Operations
15
-
16
- - Validate all deploy-time hook choices, 721 settings, and sucker configuration before using `launchProjectFor` or `launchRulesetsFor`.
17
- - Keep the distinction clear between per-ruleset composed hooks and the deployer's permanent proxy role.
18
- - Use the deployer when you want its tax-free sucker and mint-permission behavior; otherwise, do not assume it is a drop-in replacement for arbitrary hook wiring.
19
-
20
- ## One-Way Or High-Risk Actions
21
-
22
- - Constructor-time wildcard permissions and immutable references on the deployer cannot be changed afterward.
23
- - Launch-time hook composition choices determine how future pay and cash-out flows are merged for that ruleset.
24
- - A bad omnichain deployment can leave a project with a cross-chain shape that is expensive to unwind operationally.
25
-
26
- ## Recovery Notes
27
-
28
- - If the project is still administratively flexible, queue new rulesets or use project-level migration paths to move to corrected hook composition.
29
- - If the deployer's own immutable assumptions are wrong, recovery means deploying a new deployer path rather than trying to hot-fix the existing one.
30
-
31
- ## Roles
32
-
33
- | Role | How Assigned | Scope |
34
- |------|-------------|-------|
35
- | Project owner | Holds the project's ERC-721 (minted by `JBProjects`) | Per-project. Can delegate via `JBPermissions`. |
36
- | Permitted operator | Granted specific permission IDs by the project owner through `JBPermissions` | Per-project, per-permission. ROOT (1) grants all. Wildcard projectId=0 grants across all projects. |
37
- | Registered sucker | Deployed via `JBSuckerRegistry.deploySuckersFor` (requires DEPLOY_SUCKERS permission) | Per-project. Gets 0% cash-out tax and mint permission automatically. |
38
- | JBSuckerRegistry | Set at construction, granted MAP_SUCKER_TOKEN for all projects (projectId=0 wildcard) | Protocol-wide. Maps tokens for sucker bridging. |
39
-
40
- ## Privileged Functions
41
-
42
- ### JBOmnichainDeployer
43
-
44
- | Function | Required Role | Permission ID | Scope | What It Does |
45
- |----------|--------------|---------------|-------|--------------|
46
- | `deploySuckersFor` | Project owner or operator | `DEPLOY_SUCKERS` | Per-project | Deploys new cross-chain suckers for an existing project via the sucker registry. The same operation also applies token mappings on the new suckers, so existing projects must already have the registry arranged as an authorized `MAP_SUCKER_TOKEN` operator for that project. |
47
- | `launchRulesetsFor` | Project owner or operator | `LAUNCH_RULESETS` + `SET_TERMINALS` | Per-project | Deploys a 721 tiers hook, launches new rulesets with terminal configuration for an existing project. Has a simplified overload without `deploy721Config`. |
48
- | `queueRulesetsOf` | Project owner or operator | `QUEUE_RULESETS` | Per-project | Queues new rulesets for an existing project. If tiers provided, deploys a new 721 hook. Otherwise, carries forward the 721 hook from the latest ruleset. Has a simplified overload without `deploy721Config`. |
49
-
50
- ### Permissionless Functions
51
-
52
- | Function | Who Can Call | What It Does |
53
- |----------|-------------|--------------|
54
- | `launchProjectFor` | Anyone | Creates a new project with a 721 tiers hook (even with 0 tiers) and suckers. The ERC-721 is minted to the specified `owner`. Returns `(projectId, hook, suckers)`. Has a simplified overload without `deploy721Config` that uses a default empty-tier 721 config. |
55
- | `beforePayRecordedWith` | JBMultiTerminal (via controller) | View function: always calls the 721 hook (when its address is non-zero) for specs, then calls the custom hook (if configured and `useDataHookForPay` is set) with the reduced amount. Merges results. |
56
- | `beforeCashOutRecordedWith` | JBMultiTerminal (via controller) | View function: returns 0% cash-out tax for registered suckers. Calls 721 hook first (from `_tiered721HookOf`, if `useDataHookForCashOut: true`), then calls custom hook (from `_extraDataHookOf`, if `useDataHookForCashOut: true`) with the updated values from the 721 hook. Both hooks' specifications are merged. If neither has the flag set, returns original values. |
57
- | `hasMintPermissionFor` | JBController | View function: returns true for registered suckers, otherwise checks the custom hook in `_extraDataHookOf`. |
58
- | `extraDataHookOf` | Anyone | View function: returns the stored `JBDeployerHookConfig` for a project/ruleset pair (the custom data hook). |
59
- | `tiered721HookOf` | Anyone | View function: returns the stored 721 hook and `useDataHookForCashOut` flag for a project/ruleset pair. |
60
-
61
- ## Deployment Administration
62
-
63
- **Who can deploy omnichain projects:** Anyone. The `launchProjectFor` function is permissionless. The caller specifies an `owner` address that receives the project ERC-721.
64
-
65
- **Deployment flow:**
66
- 1. The deployer deploys a 721 tiers hook via `HOOK_DEPLOYER` (even with 0 tiers).
67
- 2. It configures rulesets via `_setup721()`, sets itself as the data hook wrapper.
68
- 3. It calls `controller.launchProjectFor`, which mints the project ERC-721 to `address(this)`.
69
- 4. It transfers 721 hook ownership to the project (requires project NFT to exist).
70
- 5. It optionally deploys suckers via the sucker registry.
71
- 6. It transfers the project ERC-721 to the specified `owner`.
72
-
73
- **Configurable parameters at deployment:**
74
- - Ruleset configurations (duration, weight, decay, approval hooks, splits, fund access limits, metadata flags).
75
- - Terminal configurations (which terminals accept which tokens).
76
- - 721 tiers hook configuration (tier pricing, supply, metadata, categories — can be empty for 0 tiers).
77
- - Sucker deployment configuration (which chains, which deployers, token mappings).
78
- - Salt for deterministic cross-chain address matching.
79
-
80
- ## Cross-Chain Controls
81
-
82
- | Action | Who | Mechanism |
83
- |--------|-----|-----------|
84
- | Deploy suckers for existing project | Project owner or DEPLOY_SUCKERS operator | `deploySuckersFor` calls `SUCKER_REGISTRY.deploySuckersFor`; because the registry also applies the initial mappings, existing projects must pair this with a project-level `MAP_SUCKER_TOKEN` arrangement for the registry |
85
- | Deploy suckers during project launch | Project deployer (anyone) | Included in `launchProjectFor` if `salt != bytes32(0)` |
86
- | Map sucker tokens | JBSuckerRegistry | Granted MAP_SUCKER_TOKEN at construction with projectId=0 wildcard |
87
- | Grant 0% cash-out tax to suckers | Automatic | `beforeCashOutRecordedWith` checks `SUCKER_REGISTRY.isSuckerOf` |
88
- | Grant mint permission to suckers | Automatic | `hasMintPermissionFor` checks `SUCKER_REGISTRY.isSuckerOf` |
89
-
90
- **Existing-project operator note:** `deploySuckersFor` looks like a deployment-only action at the top level, but it is intentionally a deploy-and-map flow. If an existing project delegates `DEPLOY_SUCKERS` without also arranging `MAP_SUCKER_TOKEN` for the registry, the transaction will fail once the registry reaches the mapping step.
91
-
92
- **Cross-chain determinism:** The salt for sucker deployment is combined with `_msgSender()` (`keccak256(abi.encode(salt, _msgSender()))`). Deploying from the same sender address with the same salt on each chain produces matching sucker addresses.
93
-
94
- ## Data Hook Proxy Pattern
95
-
96
- `JBOmnichainDeployer` acts as a data hook proxy. When set as a project's `dataHook` in ruleset metadata, it wraps up to two inner hooks:
97
-
98
- 1. **721 tiers hook** (`_tiered721HookOf[projectId][rulesetId]`): Handles NFT-based pay/cashout logic.
99
- 2. **Extra data hook** (`_extraDataHookOf[projectId][rulesetId]`): An optional custom hook for additional pay/cashout logic.
100
-
101
- ### Call flow for `beforePayRecordedWith`:
102
-
103
- ```
104
- Terminal -> Controller -> JBOmnichainDeployer.beforePayRecordedWith()
105
- 1. Call 721 hook's beforePayRecordedWith (always, when its address is non-zero)
106
- -> Get pay hook specifications and the total split amount
107
- 2. Call extra hook's beforePayRecordedWith (if useDataHookForPay is set on extra hook config)
108
- -> Amount is reduced by what the 721 hook already allocated
109
- 3. Scale the extra hook's weight proportionally to the project's share of the payment
110
- 4. Merge both hooks' specifications and return
111
- ```
112
-
113
- ### Call flow for `beforeCashOutRecordedWith`:
114
-
115
- ```
116
- Terminal -> Controller -> JBOmnichainDeployer.beforeCashOutRecordedWith()
117
- 1. Check if caller is a registered sucker -> return 0% cash-out tax (fee-free bridging)
118
- 2. Call 721 hook's beforeCashOutRecordedWith (if useDataHookForCashOut is set on 721 config)
119
- -> Get cashout hook specifications and adjusted values
120
- 3. Call extra hook's beforeCashOutRecordedWith (if useDataHookForCashOut is set on extra config)
121
- -> Receives updated values from 721 hook
122
- 4. Merge both hooks' specifications and return
123
- ```
6
+ | --- | --- |
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 |
124
11
 
125
- **`useDataHookForCashOut` / `useDataHookForPay` flags:** These flags control whether each hook participates in a given operation. For the **extra data hook**, the flags are stored per-ruleset in the `JBDeployerHookConfig` struct -- if the flag is `false`, that hook is skipped entirely and the original values are returned unchanged for that hook's portion. The **721 hook** behaves differently: it is **always** called during `beforePayRecordedWith` when its address is non-zero (no `useDataHookForPay` check), but for `beforeCashOutRecordedWith` it respects the `useDataHookForCashOut` flag stored in `JBTiered721HookConfig`.
12
+ ## Purpose
126
13
 
127
- **Write-once storage:** Both `_tiered721HookOf` and `_extraDataHookOf` mappings are written once during `_setup721()` and never updated. New rulesets can reference different hooks, but existing ruleset-to-hook mappings are permanent.
14
+ This repo controls how omnichain projects are launched and wrapped, not the low-level runtime logic of suckers or 721 hooks.
128
15
 
129
- ## Immutable Configuration
16
+ ## Control Model
130
17
 
131
- These values are set at deployment and cannot be changed:
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
132
21
 
133
- | Property | Type | What It Is |
134
- |----------|------|-----------|
135
- | `PROJECTS` | `IJBProjects` | The ERC-721 contract for project ownership. |
136
- | `HOOK_DEPLOYER` | `IJB721TiersHookDeployer` | The deployer for 721 tiers hooks. |
137
- | `SUCKER_REGISTRY` | `IJBSuckerRegistry` | The registry for deploying and tracking suckers. |
138
- | `PERMISSIONS` | `IJBPermissions` | The permissions contract (inherited from JBPermissioned). |
139
- | Trusted forwarder | `address` | The ERC-2771 trusted forwarder for meta-transactions. |
140
- | MAP_SUCKER_TOKEN grant | Permission | Granted to SUCKER_REGISTRY at construction for all projects (projectId=0). Cannot be revoked by this contract. |
22
+ ## Recovery
141
23
 
142
- **Data hook mappings** (`_tiered721HookOf[projectId][rulesetId]` and `_extraDataHookOf[projectId][rulesetId]`) are write-once per ruleset ID. They are set during `_setup721()` and never updated or deleted.
24
+ - bad launch wiring usually means a new deployment path rather than a local patch
143
25
 
144
26
  ## Admin Boundaries
145
27
 
146
- What admins **cannot** do:
28
+ - this repo does not override locked runtime behavior in sibling repos
147
29
 
148
- - **Cannot upgrade the deployer.** JBOmnichainDeployer has no upgrade mechanism, proxy pattern, or self-destruct.
149
- - **Cannot change immutable references.** PROJECTS, HOOK_DEPLOYER, SUCKER_REGISTRY, PERMISSIONS, and the trusted forwarder are all immutable.
150
- - **Cannot modify stored data hooks.** Once a ruleset's hooks are stored in `_tiered721HookOf` and `_extraDataHookOf`, they cannot be changed. New rulesets can use different hooks, but existing mappings are permanent.
151
- - **Cannot bypass permission checks.** All post-deployment admin functions require JBPermissions verification against the project owner.
152
- - **Cannot revoke sucker privileges.** Once a sucker is registered in JBSuckerRegistry, it automatically gets 0% cash-out tax and mint permission for its project. Revocation must happen at the registry level.
153
- - **Cannot set the deployer as its own data hook.** `_setup721()` explicitly reverts with `JBOmnichainDeployer_InvalidHook` if a hook is `address(this)`.
154
- - **Cannot use a controller that doesn't match the project.** `_validateController` reverts with `JBOmnichainDeployer_ControllerMismatch` if the provided controller is not the project's actual controller in the directory.
155
- - **Cannot steal project ownership during deployment.** The deployer holds the project ERC-721 only transiently and transfers it to the specified owner in the same transaction.
156
- - **Cannot drain funds.** The deployer never holds or manages token balances. It only orchestrates configuration.
package/ARCHITECTURE.md CHANGED
@@ -2,79 +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 also acts as a wrapper data hook so it can compose a 721 hook with an optional extra data hook while granting suckers tax-free cash outs and mint permission.
5
+ `nana-omnichain-deployers-v6` packages a Juicebox project, a 721 hook, and sucker deployment into one omnichain launch surface.
6
6
 
7
- ## Boundaries
7
+ ## System Overview
8
8
 
9
- - `JBOmnichainDeployer` is both a deployer and a live hook wrapper. Those two roles are inseparable.
10
- - The repo composes `nana-721-hook-v6` and `nana-suckers-v6`; it should not duplicate their internal logic.
11
- - Project accounting still happens in the core protocol.
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.
12
10
 
13
- ## Main Components
11
+ ## Core Invariants
14
12
 
15
- | Component | Responsibility |
16
- | --- | --- |
17
- | `JBOmnichainDeployer` | Project launch, ruleset queueing, hook composition, and sucker-safe cash-out policy |
18
- | config structs | 721 hook config, extra hook config, and sucker deployment config |
19
- | `IJBOmnichainDeployer` | Public deployer and inspection interface |
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
20
17
 
21
- ## Runtime Model
18
+ ## Trust Boundaries
22
19
 
23
- ### Launch
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
24
23
 
25
- ```text
26
- caller
27
- -> launch project or queue rulesets through the deployer
28
- -> deployer installs itself as the ruleset data hook
29
- -> deployer deploys or carries forward the 721 hook
30
- -> deployer optionally deploys sucker pairs with deterministic salts
31
- -> project ownership is transferred to the intended owner
32
- ```
24
+ ## Security Model
33
25
 
34
- ### 721 Hook Carry-Forward (Queue Path)
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
35
28
 
36
- When `queueRulesetsOf` is called without new tiers, the deployer carries the existing 721 hook forward. The source ruleset is chosen with this precedence:
29
+ ## Source Map
37
30
 
38
- 1. **Latest queued ruleset** — if its approval status is `Approved` or `Empty` (no approval hook) and it has a hook config stored in the deployer.
39
- 2. **Current active ruleset** — fallback when no qualifying queued ruleset exists.
40
-
41
- This ensures that a recently queued (and approved) ruleset's hook config takes precedence over a potentially stale active ruleset. The `useDataHookForCashOut` flag is also preserved from whichever source ruleset is selected.
42
-
43
- ### Pay And Cash-Out Wrapping
44
-
45
- ```text
46
- runtime callback
47
- -> if the actor is a registered sucker, return the special tax-free / mint-enabled path
48
- -> otherwise call the 721 hook first when configured
49
- -> then call the extra data hook when configured
50
- -> merge hook specs in order and return the combined result
51
- ```
52
-
53
- ## Critical Invariants
54
-
55
- - Suckers must be able to bridge without getting trapped behind custom cash-out policies.
56
- - Hook order matters: the 721 hook runs first, and the extra hook receives the updated context.
57
- - The deployer's predicted ruleset IDs must stay aligned with `JBRulesets` behavior; the storage keys depend on it.
58
- - Every project launched through this repo gets a 721 hook surface, even if it starts with zero tiers.
59
- - Carry-forward must prefer the latest approved queued ruleset over the current ruleset to avoid losing hook config from a recently queued update.
60
-
61
- ## Where Complexity Lives
62
-
63
- - This repo hides composition complexity behind a simple launch surface, which makes stale assumptions dangerous.
64
- - Ruleset ID prediction is a subtle but central storage keying mechanism.
65
- - The sucker exception path intentionally short-circuits normal hook composition and must stay easy to reason about.
66
-
67
- ## Dependencies
68
-
69
- - `nana-core-v6` for project launch and hook interfaces
70
- - `nana-721-hook-v6` for tiered NFT behavior
71
- - `nana-suckers-v6` for cross-chain transport
72
- - `nana-ownable-v6` for project-following hook ownership
73
-
74
- ## Safe Change Guide
75
-
76
- - Review launch-time logic and runtime-hook logic together. This repo is easy to break by fixing only one side.
77
- - When changing hook composition, verify both payment and cash-out ordering.
78
- - If you touch ruleset ID prediction, test same-block and queued-ruleset edge cases explicitly.
79
- - Keep deterministic salt handling stable across chains; address predictability is part of the feature.
80
- - Treat "transparent wrapper" claims as something to prove continuously, not assume.
31
+ - `src/JBOmnichainDeployer.sol`
@@ -1,72 +1,29 @@
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
- ## Objective
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
-
22
- Key dependencies:
23
- - `nana-core-v6`
24
- - `nana-721-hook-v6`
25
- - `nana-suckers-v6`
26
-
27
- ## System Model
28
-
29
- `JBOmnichainDeployer` is a launch surface that can:
30
- - create a new Juicebox project
31
- - deploy and configure a 721 hook
32
- - configure rulesets and terminals
33
- - deploy suckers and register them for the project
34
- - participate in pay or cash-out accounting as a data hook where needed
35
-
36
- ## Critical Invariants
37
17
 
38
- 1. Launch configuration is faithful
39
- The deployed project must end up with the exact hook, ruleset, and ownership configuration the caller requested.
40
-
41
- 2. Sucker privileges stay restricted
42
- Zero-tax or mint-permission behavior intended for legitimate suckers must not be reachable by arbitrary contracts or stale registry entries.
43
-
44
- 3. Weight and accounting scaling are correct
45
- If the deployer proxies or modifies hook outputs, the resulting project token issuance and reclaim math must still match intended economics.
46
-
47
- 4. Ownership transfer is complete
48
- Deployer-created hooks and helper contracts must not retain silent control after initialization.
18
+ - `src/JBOmnichainDeployer.sol`
19
+ - related tests under `test/`
49
20
 
50
- ## Threat Model
21
+ ## Start Here
51
22
 
52
- Prioritize:
53
- - empty or malformed ruleset configurations
54
- - hook ownership transfer races
55
- - registry-based privilege spoofing
56
- - reentrancy around project launch and initialization
57
- - stale assumptions when optional sucker deployment is skipped
23
+ 1. `src/JBOmnichainDeployer.sol`
58
24
 
59
- ## Build And Verification
25
+ ## Verification
60
26
 
61
- Standard workflow:
62
27
  - `npm install`
63
28
  - `forge build`
64
29
  - `forge test`
65
-
66
- Existing tests emphasize:
67
- - reentrancy and attack paths
68
- - hook composition
69
- - weight scaling
70
- - omnichain fork and stress scenarios
71
-
72
- High-value findings typically show either a bad project launch state or a non-sucker actor receiving omnichain-only privileges.
package/README.md CHANGED
@@ -3,7 +3,12 @@
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
+ Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
7
+ User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
8
+ Skills: [SKILLS.md](./SKILLS.md)
9
+ Risks: [RISKS.md](./RISKS.md)
10
+ Administration: [ADMINISTRATION.md](./ADMINISTRATION.md)
11
+ Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
7
12
 
8
13
  ## Overview
9
14
 
@@ -12,15 +17,13 @@ The deployer wraps multiple launch concerns into one surface:
12
17
  - deploy or carry forward a tiered 721 hook
13
18
  - install itself as the project's data-hook wrapper
14
19
  - compose the 721 hook with an optional extra custom hook
15
- - grant tax-free and mint-safe behavior to project suckers
20
+ - wrap sucker flows so project-specific cash-out taxation and mint-permission logic can be handled safely
16
21
  - deploy suckers deterministically across chains
17
22
 
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.
23
+ The wrapper exists so sucker-triggered flows can be exempted from project-specific cash-out taxation and related mint-gating logic where needed while the project still keeps its own inner data hooks.
19
24
 
20
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.
21
26
 
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.
23
-
24
27
  ## Key Contract
25
28
 
26
29
  | Contract | Role |
@@ -42,6 +45,20 @@ This repo owns orchestration plus runtime wrapping:
42
45
  3. `nana-721-hook-v6/src/JB721TiersHook.sol`
43
46
  4. the extra hook repo, if the deployment composes one
44
47
 
48
+ ## Integration Traps
49
+
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
54
+
55
+ ## Where State Lives
56
+
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
61
+
45
62
  ## Install
46
63
 
47
64
  ```bash
@@ -81,7 +98,12 @@ script/
81
98
 
82
99
  ## Risks And Notes
83
100
 
84
- - 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
85
102
  - 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
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
105
+
106
+ ## For AI Agents
107
+
108
+ - Describe this repo as an orchestration and wrapper layer, not as the source of sucker or 721 runtime behavior.
109
+ - Start with `JBOmnichainDeployer`, then inspect the sibling repo that owns the behavior being questioned.
package/RISKS.md CHANGED
@@ -29,6 +29,7 @@ This file focuses on the risks in the deployer layer that launches Juicebox proj
29
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
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
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.
32
33
 
33
34
  ## 3. Access Control
34
35
 
@@ -41,6 +42,7 @@ This file focuses on the risks in the deployer layer that launches Juicebox proj
41
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).
42
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.
43
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.
44
46
 
45
47
  ## 5. Reentrancy Surface
46
48
 
@@ -53,7 +55,8 @@ This file focuses on the risks in the deployer layer that launches Juicebox proj
53
55
 
54
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).
55
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.
56
- - **ERC721Receiver restriction.** `onERC721Received` only accepts from `PROJECTS`. Any other NFTs sent to this contract are permanently lost.
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.
57
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.
58
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.
59
62
 
@@ -65,6 +68,7 @@ This file focuses on the risks in the deployer layer that launches Juicebox proj
65
68
  - `beforePayRecordedWith` uses the 721 hook's weight directly (already split-adjusted by `JB721TiersHookLib.calculateWeight`), so no additional scaling is applied.
66
69
  - Self-reference prevention: `rulesetConfigurations[i].metadata.dataHook` cannot be `address(this)` after `_setup721`.
67
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.
68
72
 
69
73
  ## 8. Accepted Behaviors
70
74
 
package/SKILLS.md CHANGED
@@ -2,39 +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 open the deployer or composition-focused tests depending on whether the question is about setup, permissions, hook wrapping, or runtime delegation.
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
- | Input and output types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
15
- | Guard rails, attacks, or hook composition behavior | [`test/JBOmnichainDeployerGuard.t.sol`](./test/JBOmnichainDeployerGuard.t.sol), [`test/OmnichainDeployerAttacks.t.sol`](./test/OmnichainDeployerAttacks.t.sol), [`test/Tiered721HookComposition.t.sol`](./test/Tiered721HookComposition.t.sol), [`test/regression/`](./test/regression/) |
16
-
17
- ## Repo Map
18
-
19
- | Area | Where to look |
20
- |---|---|
21
- | Main contract | [`src/JBOmnichainDeployer.sol`](./src/JBOmnichainDeployer.sol) |
22
- | Types | [`src/structs/`](./src/structs/), [`src/interfaces/`](./src/interfaces/) |
23
- | Scripts | [`script/`](./script/) |
24
- | 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) |
25
16
 
26
17
  ## Purpose
27
18
 
28
- 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.
29
-
30
- ## Reference Files
31
-
32
- - Open [`references/runtime.md`](./references/runtime.md) when you need hook-composition order, sucker exemptions, per-ruleset storage behavior, or the main runtime invariants.
33
- - 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.
34
20
 
35
21
  ## Working Rules
36
22
 
37
- - 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.
38
- - Treat ruleset ID prediction, hook-carry-forward logic, and salt derivation as high-risk. Small changes there can orphan config or break deterministic deployment.
39
- - 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.
40
- - 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.
package/USER_JOURNEYS.md CHANGED
@@ -1,70 +1,63 @@
1
1
  # User Journeys
2
2
 
3
- ## Who This Repo Serves
3
+ ## Repo Purpose
4
4
 
5
- - teams launching Juicebox projects that should be cross-chain from day one
6
- - deployers composing a tiered 721 hook with sucker support and optional extra hooks
7
- - operators evolving rulesets without breaking sucker-safe wrapper behavior
5
+ This repo launches projects that are omnichain from the start.
8
6
 
9
- ## Journey 1: Launch A Project Across Chains In One Flow
7
+ ## Primary Actors
10
8
 
11
- **Starting state:** the project wants an initial Juicebox launch plus 721 hook plus sucker package instead of separate setup steps.
9
+ - operators launching omnichain projects
10
+ - teams composing 721 hooks with extra data hooks
11
+ - auditors checking bridge-wrapper behavior
12
12
 
13
- **Success:** the project launches with its omnichain-capable shape already installed.
13
+ ## Journey 1: Launch An Omnichain Project
14
14
 
15
- **Flow**
16
- 1. Call `JBOmnichainDeployer` with the project launch config, 721 config, sucker deployment config, and any extra hook composition.
17
- 2. The deployer launches the base Juicebox project and either deploys or wires the tiered 721 hook.
18
- 3. It wraps the hook composition so future rulesets can keep sucker-safe behavior while still preserving project-specific hook logic.
19
- 4. Deterministic salts are used so sibling-chain deployments can be coordinated with confidence.
15
+ **Actor:** deployer.
20
16
 
21
- ## Journey 2: Coordinate Deterministic Deployment Inputs Across Chains
17
+ **Intent:** launch a project with a 721 hook and suckers already wired in.
22
18
 
23
- **Starting state:** multiple chain deployments need to line up so the same project shape exists everywhere.
19
+ **Main Flow**
20
+ 1. Build the intended ruleset and hook composition.
21
+ 2. Launch the project through `JBOmnichainDeployer`.
22
+ 3. Deploy or wire the sucker pair and transfer control to the intended owner.
24
23
 
25
- **Success:** teams can predict addresses, salts, and wrapper behavior before doing the live rollout.
24
+ **Failure Modes**
25
+ - wrong hook composition
26
+ - cross-chain deployment drift
27
+ - wrong sucker peer wiring
26
28
 
27
- **Flow**
28
- 1. Fix the deployer inputs that drive deterministic addresses for suckers and hook packaging.
29
- 2. Reuse those inputs consistently on each chain.
30
- 3. Validate that the controller, hook ownership, and sucker expectations still line up across the resulting deployments.
29
+ ## Journey 2: Deploying Suckers for an Existing Project
31
30
 
32
- ## Journey 3: Carry Forward An Existing 721 Hook While Queueing New Omnichain Rulesets
31
+ **Actor:** project owner who wants to add cross-chain sucker bridges to an already-launched project.
33
32
 
34
- **Starting state:** the project already has a 721 hook and wants future omnichain-aware rulesets without replacing that collection.
33
+ **Intent:** deploy suckers via `JBOmnichainDeployer.deploySuckersFor()` for a project that was not originally launched through the omnichain deployer (or needs additional suckers after launch).
35
34
 
36
- **Success:** the deployer carries the existing hook forward, stores it against the queued ruleset, and preserves the ownership and wrapper assumptions bridge flows need.
35
+ **Background**
37
36
 
38
- **Flow**
39
- 1. Queue the next ruleset through the deployer using the path that reuses the existing 721 hook (pass zero tiers).
40
- 2. The deployer selects the source hook: it first checks the latest queued ruleset (if approved or with no approval hook), then falls back to the current active ruleset. This prevents losing a recently queued hook config.
41
- 3. The `useDataHookForCashOut` flag is preserved from whichever source ruleset is selected.
42
- 4. Validate the controller and queued ruleset inputs before relying on the result.
43
- 5. Confirm the queued ruleset now points at the carried-forward hook rather than accidentally dropping the 721 layer.
37
+ When a project is freshly launched through the omnichain deployer, the deployer contract temporarily holds the project NFT. This means it automatically satisfies the `DEPLOY_SUCKERS` permission check because it is the project owner at that moment. After sucker deployment, it transfers the NFT to the intended owner.
44
38
 
45
- ## Journey 4: Compose A Tiered 721 Hook With A Custom Extra Hook
39
+ For projects that already exist and are owned by someone else, the deployer no longer holds the project NFT. The permission check in `deploySuckersFor()` requires that the caller has `DEPLOY_SUCKERS` permission from the project owner. Since the omnichain deployer enforces this check internally via `_requirePermissionFrom`, the project owner must explicitly grant this permission before calling the function.
46
40
 
47
- **Starting state:** the project wants standard tiered NFTs plus some extra product logic.
41
+ **Main Flow**
42
+ 1. The project owner calls `JBPermissions.setPermissionsFor()` to grant the `DEPLOY_SUCKERS` permission (from `JBPermissionIds`) to the `JBOmnichainDeployer` contract address, scoped to their project ID.
43
+ 2. The project owner (or any caller with the appropriate permission) calls `JBOmnichainDeployer.deploySuckersFor()` with the project ID and sucker deployment configuration.
44
+ 3. The deployer verifies that the caller has `DEPLOY_SUCKERS` permission from the project owner.
45
+ 4. The deployer deploys the suckers via the sucker registry and returns their addresses.
48
46
 
49
- **Success:** the extra logic composes with the 721 hook and sucker wrapper instead of overriding them unsafely.
47
+ **Failure Modes**
48
+ - Calling `deploySuckersFor()` without first granting the deployer `DEPLOY_SUCKERS` permission will revert with a permission error.
49
+ - Granting the permission to the wrong address (e.g., the caller's EOA instead of the deployer contract) will not satisfy the check, since the deployer enforces permission on behalf of the project owner against its own address.
50
+ - Forgetting to scope the permission to the correct project ID will cause the call to fail.
50
51
 
51
- **Flow**
52
- 1. Provide the extra-hook config when launching through `JBOmnichainDeployer`.
53
- 2. Let the deployer remember which composition belongs to each ruleset.
54
- 3. Make sure bridge flows still bypass or special-case the right tax and data-hook behavior.
52
+ **Key Difference from Journey 1**
55
53
 
56
- ## Journey 5: Evolve The Project After Launch Without Breaking Bridge Paths
54
+ In Journey 1 (fresh launch), permission is implicit because the deployer owns the project NFT during deployment. In Journey 2 (existing project), permission must be explicitly granted via `JBPermissions` before sucker deployment can proceed.
57
55
 
58
- **Starting state:** the project is live and future rulesets need new metadata, hook composition, or payout behavior.
56
+ ## Trust Boundaries
59
57
 
60
- **Success:** ruleset changes preserve the special wrapper assumptions that let suckers bridge cleanly.
61
-
62
- **Flow**
63
- 1. Queue the next ruleset through the omnichain-aware deployer surface.
64
- 2. Keep track of which hook stack should apply for that future ruleset.
65
- 3. Confirm that sucker flows still land on the mint-safe and tax-safe path the wrapper was designed to preserve.
58
+ - this repo wraps runtime behavior but does not replace the underlying 721 or sucker repos
66
59
 
67
60
  ## Hand-Offs
68
61
 
69
- - Use [nana-suckers-v6](../nana-suckers-v6/USER_JOURNEYS.md) for the bridge mechanics after the project has been launched with suckers.
70
- - Use [nana-721-hook-v6](../nana-721-hook-v6/USER_JOURNEYS.md) for the standard tier and resolver behavior that this repo packages into the omnichain launch.
62
+ - Use `nana-suckers-v6` for bridge runtime behavior.
63
+ - Use `nana-721-hook-v6` for tiered NFT runtime behavior.
package/foundry.toml CHANGED
@@ -14,6 +14,8 @@ runs = 1024
14
14
  depth = 100
15
15
  fail_on_revert = false
16
16
 
17
+ [lint]
18
+ exclude_lints = ["pascal-case-struct", "mixed-case-variable"]
17
19
  [fmt]
18
20
  number_underscore = "thousands"
19
21
  multiline_func_header = "all"
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
1
  {
2
- "name": "@bananapus/omnichain-deployers-v6",
3
- "version": "0.0.26",
4
- "license": "MIT",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/Bananapus/nana-omnichain-deployers-v6"
8
- },
9
- "engines": {
10
- "node": ">=20.0.0"
11
- },
12
- "scripts": {
13
- "test": "forge test",
14
- "coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
15
- "deploy:mainnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
16
- "deploy:testnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
17
- "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-omnichain-deployers-v6'"
18
- },
19
- "dependencies": {
20
- "@bananapus/721-hook-v6": "^0.0.33",
21
- "@bananapus/buyback-hook-v6": "^0.0.27",
22
- "@bananapus/core-v6": "^0.0.34",
23
- "@bananapus/ownable-v6": "^0.0.17",
24
- "@bananapus/permission-ids-v6": "^0.0.17",
25
- "@bananapus/suckers-v6": "^0.0.25",
26
- "@openzeppelin/contracts": "^5.6.1",
27
- "@uniswap/v4-core": "^1.0.2"
28
- },
29
- "devDependencies": {
30
- "@bananapus/address-registry-v6": "^0.0.17",
31
- "@sphinx-labs/plugins": "^0.33.2"
32
- }
2
+ "name": "@bananapus/omnichain-deployers-v6",
3
+ "version": "0.0.28",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/Bananapus/nana-omnichain-deployers-v6"
8
+ },
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
12
+ "scripts": {
13
+ "test": "forge test",
14
+ "coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
15
+ "deploy:mainnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
16
+ "deploy:testnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
17
+ "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-omnichain-deployers-v6'"
18
+ },
19
+ "dependencies": {
20
+ "@bananapus/721-hook-v6": "^0.0.35",
21
+ "@bananapus/buyback-hook-v6": "^0.0.27",
22
+ "@bananapus/core-v6": "^0.0.34",
23
+ "@bananapus/ownable-v6": "^0.0.17",
24
+ "@bananapus/permission-ids-v6": "^0.0.17",
25
+ "@bananapus/suckers-v6": "^0.0.25",
26
+ "@openzeppelin/contracts": "^5.6.1",
27
+ "@uniswap/v4-core": "^1.0.2"
28
+ },
29
+ "devDependencies": {
30
+ "@bananapus/address-registry-v6": "^0.0.17",
31
+ "@sphinx-labs/plugins": "^0.33.2"
32
+ }
33
33
  }
@@ -25,4 +25,4 @@
25
25
 
26
26
  - [`test/JBOmnichainDeployer.t.sol`](../test/JBOmnichainDeployer.t.sol) for baseline deploy and queue flows.
27
27
  - [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for pinned edge cases.
28
- - [`test/fork/`](../test/fork/) when cross-repo integration behavior matters more than isolated unit logic.
28
+ - [`test/Tiered721HookComposition.t.sol`](../test/Tiered721HookComposition.t.sol) when cross-repo integration behavior matters more than isolated unit logic.
@@ -25,4 +25,4 @@
25
25
  - [`test/Tiered721HookComposition.t.sol`](../test/Tiered721HookComposition.t.sol) for hook-composition behavior.
26
26
  - [`test/JBOmnichainDeployerGuard.t.sol`](../test/JBOmnichainDeployerGuard.t.sol) and [`test/OmnichainDeployerAttacks.t.sol`](../test/OmnichainDeployerAttacks.t.sol) for safety properties.
27
27
  - [`test/OmnichainDeployerReentrancy.t.sol`](../test/OmnichainDeployerReentrancy.t.sol) for wrapper security assumptions.
28
- - [`test/OmnichainDeployerEdgeCases.t.sol`](../test/OmnichainDeployerEdgeCases.t.sol), [`test/invariants/`](../test/invariants/), and [`test/regression/`](../test/regression/) for edge behavior.
28
+ - [`test/OmnichainDeployerEdgeCases.t.sol`](../test/OmnichainDeployerEdgeCases.t.sol), [`test/JBOmnichainDeployer.t.sol`](../test/JBOmnichainDeployer.t.sol), and [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for edge behavior.
@@ -412,7 +412,9 @@ contract JBOmnichainDeployer is
412
412
  // This prevents disproportionate reclaim when tokens bridge away but surplus stays.
413
413
  effectiveSurplusValue = context.surplus.value
414
414
  + SUCKER_REGISTRY.remoteSurplusOf({
415
- projectId: context.projectId, decimals: 18, currency: uint256(uint160(context.surplus.token))
415
+ projectId: context.projectId,
416
+ decimals: context.surplus.decimals,
417
+ currency: uint256(context.surplus.currency)
416
418
  });
417
419
 
418
420
  // Will hold the 721 hook's cash out specifications (always 0 or 1 element).
@@ -444,6 +446,7 @@ contract JBOmnichainDeployer is
444
446
  hookContext.cashOutTaxRate = cashOutTaxRate;
445
447
  hookContext.cashOutCount = cashOutCount;
446
448
  hookContext.totalSupply = totalSupply;
449
+ hookContext.surplus.value = effectiveSurplusValue;
447
450
 
448
451
  // Forward to the extra hook. It may further change the tax rate, count, and return hook specs.
449
452
  // We discard the inner hook's effectiveSurplusValue — this contract computes the cross-chain values.
@@ -923,7 +926,9 @@ contract JBOmnichainDeployer is
923
926
  /// @param projectId The ID of the project to validate the controller for.
924
927
  /// @param controller The controller to validate.
925
928
  function _validateController(uint256 projectId, IJBController controller) internal view {
926
- if (address(controller.DIRECTORY().controllerOf(projectId)) != address(controller)) {
929
+ address current = address(controller.DIRECTORY().controllerOf(projectId));
930
+ // Allow address(0) for fresh projects that haven't launched rulesets yet.
931
+ if (current != address(0) && current != address(controller)) {
927
932
  revert JBOmnichainDeployer_ControllerMismatch();
928
933
  }
929
934
  }
@@ -3,7 +3,6 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
5
5
 
6
- // forge-lint: disable-next-line(pascal-case-struct)
7
6
  struct JBDeployerHookConfig {
8
7
  IJBRulesetDataHook dataHook;
9
8
  bool useDataHookForPay;
@@ -8,7 +8,6 @@ import {JBDeploy721TiersHookConfig} from "@bananapus/721-hook-v6/src/structs/JBD
8
8
  /// @param useDataHookForCashOut Whether the 721 hook should handle cash outs (via beforeCashOutRecordedWith).
9
9
  /// @param salt A salt to use for the deterministic 721 hook deployment. Combined with `msg.sender` internally, so
10
10
  /// cross-chain deterministic addresses require the same sender on each chain.
11
- // forge-lint: disable-next-line(pascal-case-struct)
12
11
  struct JBOmnichain721Config {
13
12
  JBDeploy721TiersHookConfig deployTiersHookConfig;
14
13
  bool useDataHookForCashOut;
@@ -5,7 +5,6 @@ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSucker
5
5
 
6
6
  /// @custom:member deployerConfigurations The information for how to suck tokens to other chains.
7
7
  /// @custom:member salt The salt to use for creating suckers so that they use the same address across chains.
8
- // forge-lint: disable-next-line(pascal-case-struct)
9
8
  struct JBSuckerDeploymentConfig {
10
9
  JBSuckerDeployerConfig[] deployerConfigurations;
11
10
  bytes32 salt;
@@ -3,7 +3,6 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {IJB721TiersHook} from "@bananapus/721-hook-v6/src/interfaces/IJB721TiersHook.sol";
5
5
 
6
- // forge-lint: disable-next-line(pascal-case-struct)
7
6
  struct JBTiered721HookConfig {
8
7
  IJB721TiersHook hook;
9
8
  bool useDataHookForCashOut;
@@ -57,27 +57,35 @@ contract CodexNemesisAudit is Test {
57
57
  vm.mockCall(hookAddr, abi.encodeWithSelector(IJBOwnable.transferOwnershipToProject.selector), abi.encode());
58
58
  }
59
59
 
60
- function test_poc_launchRulesetsFor_revertsWhenProjectHasNoControllerYet() public {
60
+ function test_poc_launchRulesetsFor_succeedsWhenProjectHasNoControllerYet() public {
61
61
  JBOmnichainDeployer deployer =
62
62
  new JBOmnichainDeployer(mockSuckerRegistry, hookDeployer, permissions, projects, address(0));
63
63
 
64
64
  vm.mockCall(
65
65
  address(controller), abi.encodeWithSelector(IJBController.DIRECTORY.selector), abi.encode(directory)
66
66
  );
67
- // A freshly created project with no controller yet is valid for core launchRulesetsFor,
68
- // but the deployer rejects it before the controller can set itself as first controller.
67
+ // A freshly created project with no controller yet the M-14 fix allows address(0) through.
69
68
  vm.mockCall(
70
69
  address(directory),
71
70
  abi.encodeWithSelector(IJBDirectory.controllerOf.selector, PROJECT_ID),
72
71
  abi.encode(IERC165(address(0)))
73
72
  );
73
+ // Mock controller.launchRulesetsFor to return a rulesetId.
74
+ vm.mockCall(
75
+ address(controller),
76
+ abi.encodeWithSelector(IJBController.launchRulesetsFor.selector),
77
+ abi.encode(uint256(1))
78
+ );
74
79
 
75
80
  JBRulesetConfig[] memory configs = new JBRulesetConfig[](1);
76
81
  configs[0] = _makeRulesetConfig();
77
82
 
78
83
  vm.prank(projectOwner);
79
- vm.expectRevert(JBOmnichainDeployer.JBOmnichainDeployer_ControllerMismatch.selector);
80
- deployer.launchRulesetsFor(PROJECT_ID, configs, new JBTerminalConfig[](0), "memo", controller);
84
+ (uint256 rulesetId,) =
85
+ deployer.launchRulesetsFor(PROJECT_ID, configs, new JBTerminalConfig[](0), "memo", controller);
86
+
87
+ // Verify the call succeeded and returned a valid rulesetId.
88
+ assertGt(rulesetId, 0, "launchRulesetsFor should succeed for fresh project with no controller");
81
89
  }
82
90
 
83
91
  function test_poc_deploySuckersFor_requiresHiddenPermissionForDeployerItself() public {