@bananapus/suckers-v6 0.0.25 → 0.0.26

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,211 +1,85 @@
1
1
  # Administration
2
2
 
3
- Admin privileges and their scope in nana-suckers-v6.
4
-
5
3
  ## At A Glance
6
4
 
7
5
  | Item | Details |
8
- |------|---------|
9
- | Scope | Cross-chain bridge deployment, token mapping, deprecation, emergency hatch controls, and layer-specific bridge configurator setup. |
10
- | Operators | Registry owner, project owners and delegates, sucker deployers, token mappers, safety admins, deprecation admins, and one-time bridge configurators. |
11
- | Highest-risk actions | Mapping the wrong remote token, enabling an emergency hatch, setting deprecation, or misconfiguring bridge singletons and chain constants. |
12
- | Recovery posture | The normal recovery path is to deploy or registry-enable a new sucker/deployer and migrate traffic; several safety actions are intentionally irreversible. |
13
-
14
- ## Routine Operations
15
-
16
- - Allow only the sucker deployer implementations you are prepared to support operationally on the registry side.
17
- - Map remote tokens carefully and early, before outbox activity makes mistakes harder to unwind.
18
- - Use deprecation timestamps to create a safe runway for shutdown rather than trying to disable a bridge abruptly.
19
- - Treat emergency hatch activation as a last-resort safety action when the normal bridge path is no longer trustworthy.
20
- - Verify one-time deployer configuration (`configureSingleton`, `setChainSpecificConstants`) before using a deployer in production.
6
+ | --- | --- |
7
+ | Scope | Registry-managed sucker deployment plus project-local bridge mapping, deprecation, and safety control |
8
+ | Control posture | Mixed registry-owner, project-owner, and one-time deployer-configurator control |
9
+ | Highest-risk actions | Wrong token mapping, emergency hatch activation, unsafe deprecation handling, and misconfigured bridge constants |
10
+ | Recovery posture | Recovery usually means replacement sucker paths or deployers rather than in-place reversal |
21
11
 
22
- ## One-Way Or High-Risk Actions
12
+ ## Purpose
23
13
 
24
- - `enableEmergencyHatchFor` is irreversible for the affected token mapping.
25
- - Once a sucker reaches `SENDING_DISABLED` or `DEPRECATED`, the lifecycle cannot be reversed.
26
- - Deployer singleton and chain-specific constant configuration are one-time setup operations.
27
- - A bad token mapping can strand users or route liquidity to the wrong remote asset.
14
+ `nana-suckers-v6` has a layered control plane: registry ownership, project-local permissioned actions, and one-time deployer configuration for each bridge family. The most dangerous admin actions are token mapping, deprecation, emergency hatch activation, and deployer bridge-constant setup.
28
15
 
29
- ## Recovery Notes
16
+ ## Control Model
30
17
 
31
- - If a bridge path is unhealthy, prefer deprecating the affected sucker and standing up a replacement rather than forcing traffic through a degraded route.
32
- - If a token is emergency-hatched, users recover through the documented exit flow; you cannot simply re-enable the old path afterward.
18
+ - `JBSuckerRegistry` is globally `Ownable`.
19
+ - Project-local authority flows through `JBPermissions`.
20
+ - `MAP_SUCKER_TOKEN`, `DEPLOY_SUCKERS`, `SUCKER_SAFETY`, and `SET_SUCKER_DEPRECATION` are the critical project-level permissions.
21
+ - Bridge deployers have a one-time configurator role for singleton and chain constants.
33
22
 
34
23
  ## Roles
35
24
 
36
- ### Registry Owner
37
-
38
- - **How assigned:** Set at `JBSuckerRegistry` construction via the `initialOwner` parameter. Transferable via OpenZeppelin `Ownable`.
39
- - **Scope:** Controls which sucker deployer contracts are approved for use by the registry, and sets the global `toRemoteFee` applied to all suckers. Initially expected to be JuiceboxDAO (project ID 1).
40
- - **Permission ID:** None (uses `onlyOwner` modifier from OpenZeppelin `Ownable`).
41
-
42
- ### Project Owner
43
-
44
- - **How assigned:** Owner of the ERC-721 project NFT in `JBProjects`. All project-scoped permissions are checked against `PROJECTS.ownerOf(projectId)` or `DIRECTORY.PROJECTS().ownerOf(projectId)`.
45
- - **Scope:** Controls sucker deployment, token mapping, deprecation, and emergency hatch for their project. Can delegate any of these permissions to other addresses via `JBPermissions`.
46
- - **Permission ID:** N/A (is the root authority that grants the permission IDs below).
47
-
48
- ### Sucker Deployer (delegated role)
49
-
50
- - **How assigned:** Granted `DEPLOY_SUCKERS` permission by the project owner via `JBPermissions`.
51
- - **Scope:** Can deploy new suckers for a specific project through the registry.
52
- - **Permission ID:** `JBPermissionIds.DEPLOY_SUCKERS`
53
-
54
- ### Token Mapper (delegated role)
55
-
56
- - **How assigned:** Granted `MAP_SUCKER_TOKEN` permission by the project owner via `JBPermissions`. Commonly granted to the `JBSuckerRegistry` address so it can call `mapTokens` during deployment.
57
- - **Scope:** Can map or disable local-to-remote token pairs on a specific sucker.
58
- - **Permission ID:** `JBPermissionIds.MAP_SUCKER_TOKEN`
59
-
60
- For existing-project deployments, this permission is part of the same operational path as `DEPLOY_SUCKERS`: the registry deploys the sucker and then immediately calls `mapTokens` on it. If the project delegates deployment without arranging mapping authority for the registry, the single deployment transaction will revert during configuration.
61
-
62
- ### Safety Admin (delegated role)
63
-
64
- - **How assigned:** Granted `SUCKER_SAFETY` permission by the project owner via `JBPermissions`.
65
- - **Scope:** Can open the emergency hatch for specific tokens on a sucker. This is an irreversible action.
66
- - **Permission ID:** `JBPermissionIds.SUCKER_SAFETY`
67
-
68
- ### Deprecation Admin (delegated role)
69
-
70
- - **How assigned:** Granted `SET_SUCKER_DEPRECATION` permission by the project owner via `JBPermissions`.
71
- - **Scope:** Can set or cancel the deprecation timestamp on a sucker.
72
- - **Permission ID:** `JBPermissionIds.SET_SUCKER_DEPRECATION`
73
-
74
- ### Layer-Specific Configurator
75
-
76
- - **How assigned:** Set at deployer construction via the `configurator` parameter. Stored as the immutable `LAYER_SPECIFIC_CONFIGURATOR` address on each `JBSuckerDeployer`.
77
- - **Scope:** Can call `setChainSpecificConstants` and `configureSingleton` exactly once each on the deployer. These are one-time setup functions that configure bridge-specific addresses (messenger, bridge, router, inbox) and the singleton implementation used for cloning.
78
- - **Permission ID:** None (uses direct `_msgSender()` check against `LAYER_SPECIFIC_CONFIGURATOR`).
79
-
80
- ## Privileged Functions
81
-
82
- ### JBSuckerRegistry
83
-
84
- | Function | Required Role | Permission ID | Scope | What It Does |
85
- |----------|--------------|---------------|-------|--------------|
86
- | `allowSuckerDeployer(deployer)` | Registry Owner | N/A (`onlyOwner`) | Global | Adds a deployer contract to the allowlist, enabling it to be used when deploying suckers. |
87
- | `allowSuckerDeployers(deployers)` | Registry Owner | N/A (`onlyOwner`) | Global | Batch version: adds multiple deployer contracts to the allowlist. |
88
- | `removeSuckerDeployer(deployer)` | Registry Owner | N/A (`onlyOwner`) | Global | Removes a deployer contract from the allowlist, preventing future sucker deployments through it. |
89
- | `setToRemoteFee(fee)` | Registry Owner | N/A (`onlyOwner`) | Global | Sets the `toRemoteFee` applied to all suckers. The fee must be <= `MAX_TO_REMOTE_FEE` (0.001 ether). Emits `ToRemoteFeeChanged`. |
90
- | `deploySuckersFor(projectId, salt, configs)` | Project owner or delegate | `DEPLOY_SUCKERS` | Per-project | Deploys one or more suckers for a project using allowlisted deployers. Hashes salt with `_msgSender()` (which equals `msg.sender` for direct calls, or the relayed sender for ERC-2771 meta-transactions) for deterministic cross-chain addresses. Also calls `mapTokens` on each newly created sucker, so the registry must be able to satisfy the corresponding `MAP_SUCKER_TOKEN` checks for the project. |
91
- | `removeDeprecatedSucker(projectId, sucker)` | Anyone | None | Per-project | Removes a fully `DEPRECATED` sucker from the registry. Permissionless but only succeeds if the sucker is in the `DEPRECATED` state. |
92
-
93
- ### JBSucker
94
-
95
- | Function | Required Role | Permission ID | Scope | What It Does |
96
- |----------|--------------|---------------|-------|--------------|
97
- | `mapToken(map)` | Project owner or delegate | `MAP_SUCKER_TOKEN` | Per-sucker | Maps a local terminal token to a remote token, enabling bridging. Setting `remoteToken` to `bytes32(0)` disables bridging and sends a final root to flush remaining outbox entries. |
98
- | `mapTokens(maps)` | Project owner or delegate | `MAP_SUCKER_TOKEN` | Per-sucker | Batch version: maps multiple local-to-remote token pairs. Each mapping requires the same permission. |
99
- | `enableEmergencyHatchFor(tokens)` | Project owner or delegate | `SUCKER_SAFETY` | Per-sucker | Opens the emergency hatch for specified tokens (irreversible). Sets `emergencyHatch = true` and `enabled = false` on each token's remote mapping. Allows users to exit through the outbox on the chain they deposited on. |
100
- | `setDeprecation(timestamp)` | Project owner or delegate | `SET_SUCKER_DEPRECATION` | Per-sucker | Sets the timestamp after which the sucker becomes fully deprecated. Must be at least `_maxMessagingDelay()` (14 days) in the future. Set to `0` to cancel a pending deprecation. Reverts if already in `SENDING_DISABLED` or `DEPRECATED` state. |
25
+ | Role | How Assigned | Scope | Notes |
26
+ | --- | --- | --- | --- |
27
+ | Registry owner | `Ownable(initialOwner)` | Global | Controls approved deployers and global `toRemoteFee` |
28
+ | Project owner | `JBProjects.ownerOf(projectId)` | Per project | May delegate project-local sucker permissions |
29
+ | Project operator | `JBPermissions` grant | Per project | Typically `DEPLOY_SUCKERS`, `MAP_SUCKER_TOKEN`, `SUCKER_SAFETY`, `SET_SUCKER_DEPRECATION` |
30
+ | Deployer configurator | Constructor `configurator` | Per deployer | One-time setup role for chain constants and singleton |
101
31
 
102
- ### JBSuckerDeployer (base and all subclasses)
32
+ ## Privileged Surfaces
103
33
 
104
- | Function | Required Role | Permission ID | Scope | What It Does |
105
- |----------|--------------|---------------|-------|--------------|
106
- | `configureSingleton(singleton)` | Layer-Specific Configurator | N/A (direct address check) | Per-deployer | Sets the singleton implementation contract used to clone suckers via `LibClone`. Can only be called once. |
107
- | `setChainSpecificConstants(...)` | Layer-Specific Configurator | N/A (direct address check) | Per-deployer | Configures bridge-specific addresses (messenger, bridge, router, inbox, etc.) for the deployer. Can only be called once. Parameters vary by bridge type. |
34
+ | Contract | Function | Who Can Call | Effect |
35
+ | --- | --- | --- | --- |
36
+ | `JBSuckerRegistry` | `allowSuckerDeployer(...)`, `removeSuckerDeployer(...)`, `setToRemoteFee(...)` | Registry owner | Controls global deployer allowlist and fee |
37
+ | `JBSuckerRegistry` | `deploySuckersFor(...)` | Project owner or `DEPLOY_SUCKERS` delegate | Deploys sucker pairs for a project |
38
+ | `JBSucker` | `mapToken(...)`, `mapTokens(...)` | Project owner or `MAP_SUCKER_TOKEN` delegate | Sets or disables token mappings |
39
+ | `JBSucker` | `enableEmergencyHatchFor(...)` | Project owner or `SUCKER_SAFETY` delegate | Irreversibly opens emergency exit for tokens |
40
+ | `JBSucker` | `setDeprecation(...)` | Project owner or `SET_SUCKER_DEPRECATION` delegate | Starts or cancels deprecation while allowed |
41
+ | `JBSuckerDeployer` variants | `configureSingleton(...)`, `setChainSpecificConstants(...)` | Configurator | One-time deployer setup |
108
42
 
109
- ## Deprecation Lifecycle
43
+ ## Immutable And One-Way
110
44
 
111
- The sucker deprecation lifecycle progresses through four states, controlled by `setDeprecation(timestamp)`:
45
+ - Emergency hatch is irreversible for the affected token mapping.
46
+ - Deployer singleton and chain-constant setup are one-time.
47
+ - Deprecation becomes irreversible once the sucker reaches the disabled phase.
48
+ - Token mapping is constrained once outbox activity exists for that token.
112
49
 
113
- ```
114
- ENABLED --> DEPRECATION_PENDING --> SENDING_DISABLED --> DEPRECATED
115
- ```
50
+ ## Operational Notes
116
51
 
117
- | State | Condition | Behavior |
118
- |-------|-----------|----------|
119
- | `ENABLED` | `deprecatedAfter == 0` | Fully functional. No deprecation is set. |
120
- | `DEPRECATION_PENDING` | `block.timestamp < deprecatedAfter - _maxMessagingDelay()` | Fully functional but a warning to users that deprecation is coming. |
121
- | `SENDING_DISABLED` | `block.timestamp < deprecatedAfter` (but past the messaging delay window) | `prepare()` and `toRemote()` revert. No new outbox entries or root sends. Incoming roots from the remote peer are still accepted. Users can `claim()` incoming tokens and `exitThroughEmergencyHatch()`. |
122
- | `DEPRECATED` | `block.timestamp >= deprecatedAfter` | Fully shut down. No new inbox roots are accepted (`fromRemote` skips the update). Users can still `claim()` against previously accepted inbox roots and `exitThroughEmergencyHatch()` against outbox entries that were never sent. |
52
+ - Map remote tokens carefully before meaningful bridge traffic accumulates.
53
+ - Use deprecation to create a controlled shutdown window instead of abrupt disablement.
54
+ - Treat emergency hatch as a last resort.
55
+ - Verify deployer singleton and chain constants before approving or using a deployer operationally.
56
+ - Treat fee-payment and bridge-send paths as best-effort in some variants; certain failures degrade into retained funds or local fallback claims rather than clean global rollback.
123
57
 
124
- **Who controls transitions:**
125
- - The project owner (or holder of `SET_SUCKER_DEPRECATION` permission) sets the `deprecatedAfter` timestamp via `setDeprecation(timestamp)`.
126
- - The timestamp must be at least `_maxMessagingDelay()` (14 days) in the future to allow in-flight messages to arrive.
127
- - A pending deprecation can be cancelled by calling `setDeprecation(0)`, but only while in `ENABLED` or `DEPRECATION_PENDING` state.
128
- - Once `SENDING_DISABLED` or `DEPRECATED`, the deprecation cannot be reversed.
58
+ ## Machine Notes
129
59
 
130
- **Removing from registry:** Once a sucker reaches `DEPRECATED`, anyone can call `JBSuckerRegistry.removeDeprecatedSucker(projectId, sucker)` to remove it from the project's sucker list.
60
+ - Do not assume registry ownership implies control over project-local mapping or emergency actions.
61
+ - Treat `src/JBSucker.sol`, `src/JBSuckerRegistry.sol`, and `src/deployers/` as the minimum admin source set.
62
+ - If live leaves, token mappings, or deprecation phase disagree with the planned action, stop and re-evaluate the recovery path.
63
+ - If a sucker variant uses try/catch around fee payment or inbound swaps, inspect the variant-specific recovery behavior before assuming failed bridge-side actions fully reverted.
131
64
 
132
- ## Emergency Hatch
65
+ ## Recovery
133
66
 
134
- The emergency hatch is a per-token escape mechanism for when a bridge becomes non-functional for specific tokens.
135
-
136
- **Activation:** The project owner (or holder of `SUCKER_SAFETY` permission) calls `enableEmergencyHatchFor(tokens)` on the sucker. This is irreversible -- once opened for a token, that token can never be bridged by this sucker again.
137
-
138
- **Effect:** Sets `emergencyHatch = true` and `enabled = false` on each specified token's `JBRemoteToken` mapping. This:
139
- - Prevents new `prepare()` calls for the token (the token is no longer mapped/enabled).
140
- - Prevents `toRemote()` calls for the token (reverts with `JBSucker_TokenHasInvalidEmergencyHatchState`).
141
- - Prevents `mapToken()` from remapping or re-enabling the token (reverts with `JBSucker_TokenHasInvalidEmergencyHatchState`).
142
- - Enables `exitThroughEmergencyHatch()` for users whose outbox entries were never sent to the remote peer (entries with `index >= numberOfClaimsSent`).
143
-
144
- **Who can exit:** Any user with a valid outbox merkle proof for a leaf that was never sent over the bridge. The exit mints project tokens to the beneficiary and returns the terminal tokens to the project's terminal balance. The beneficiary can then cash out the minted project tokens through the normal Juicebox mechanism if desired.
145
-
146
- **Safety constraint:** Only leaves that were never communicated to the remote peer (i.e., `index >= outbox.numberOfClaimsSent`) can be emergency-exited. Leaves already sent over the bridge (index < `numberOfClaimsSent`) cannot be emergency-exited because they may have already been claimed on the remote chain.
147
-
148
- ## Immutable Configuration
149
-
150
- The following values are set at deploy time and cannot be changed:
151
-
152
- | Property | Contract | Set By | Description |
153
- |----------|----------|--------|-------------|
154
- | `DIRECTORY` | `JBSucker`, `JBSuckerRegistry`, all deployers | Constructor | The Juicebox directory contract. |
155
- | `FEE_PROJECT_ID` | `JBSucker` | Constructor | The project that receives `toRemoteFee` payments via `terminal.pay()` on each `toRemote()` call. Typically project ID 1 (the protocol project). Best-effort: fee is silently skipped if the fee project has no native token terminal or if `terminal.pay()` reverts. |
156
- | `REGISTRY` | `JBSucker` | Constructor | The `JBSuckerRegistry` that this sucker reads the `toRemoteFee` from. |
157
- | `MAX_TO_REMOTE_FEE` | `JBSuckerRegistry` | Constant | Hard cap on what `toRemoteFee` can be set to (0.001 ether). Prevents the registry owner from setting an excessively high fee. |
158
- | `TOKENS` | `JBSucker`, all deployers | Constructor | The Juicebox token management contract. |
159
- | `PROJECTS` | `JBSuckerRegistry` | Derived from `DIRECTORY.PROJECTS()` | The ERC-721 project ownership contract. |
160
- | `OPBRIDGE` | `JBOptimismSucker`, `JBBaseSucker`, `JBCeloSucker` | Constructor (from deployer callback) | The OP Standard Bridge address. |
161
- | `OPMESSENGER` | `JBOptimismSucker`, `JBBaseSucker`, `JBCeloSucker` | Constructor (from deployer callback) | The OP Cross-Domain Messenger address. |
162
- | `ARBINBOX` | `JBArbitrumSucker` | Constructor (from deployer callback) | The Arbitrum Inbox address. |
163
- | `GATEWAYROUTER` | `JBArbitrumSucker` | Constructor (from deployer callback) | The Arbitrum Gateway Router address. |
164
- | `LAYER` | `JBArbitrumSucker` | Constructor (from deployer callback) | Whether this is an L1 or L2 sucker. |
165
- | `CCIP_ROUTER` | `JBCCIPSucker` | Constructor (from deployer callback) | The Chainlink CCIP Router address. |
166
- | `REMOTE_CHAIN_ID` | `JBCCIPSucker` | Constructor (from deployer callback) | The remote chain's chain ID. |
167
- | `REMOTE_CHAIN_SELECTOR` | `JBCCIPSucker` | Constructor (from deployer callback) | The CCIP chain selector for the remote chain. |
168
- | `WRAPPED_NATIVE` | `JBCeloSucker` | Constructor (from deployer callback) | The wrapped native token (WETH) on the local chain. |
169
- | `LAYER_SPECIFIC_CONFIGURATOR` | All deployers | Constructor | The address authorized to call one-time setup functions. |
170
- | `peer()` | `JBSucker` | Deterministic (returns `bytes32` via `_toBytes32(address(this))`) | The remote peer sucker address as `bytes32`. Defaults to the local address, relying on CREATE2 giving the same address on both chains. |
171
- | `deployer` | `JBSucker` | Set once in `initialize()` by `msg.sender` | The address that deployed this sucker clone (the deployer contract). |
172
- | `projectId()` | `JBSucker` | Set once in `initialize()` | The local project ID this sucker serves. |
173
- | Bridge/messenger/router addresses | All deployers | `setChainSpecificConstants()` (one-time) | Bridge infrastructure addresses, set once by the configurator. |
174
- | Singleton implementation | All deployers | `configureSingleton()` (one-time) | The singleton contract used as the clone template. |
67
+ - The normal recovery path is a new sucker path or a new deployer, not trying to re-enable an unsafe one.
68
+ - Emergency-hatched tokens recover through the defined local exit flow.
69
+ - Bad bridge-constant configuration generally means replacement deployers or replacement sucker instances.
70
+ - Some failure modes intentionally preserve liveness over strict rollback, so recovery may mean reconciling retained funds or retryable local claims rather than undoing the original send.
175
71
 
176
72
  ## Admin Boundaries
177
73
 
178
- What admins **cannot** do:
179
-
180
- - **Cannot remap tokens after outbox activity.** Once a token has outbox tree entries (`_outboxOf[token].tree.count != 0`), it cannot be remapped to a different remote token. It can only be disabled (set to `bytes32(0)`) or re-enabled to the same remote address. This prevents double-spending across two different remote tokens.
181
-
182
- - **Cannot reverse an emergency hatch.** Once `enableEmergencyHatchFor` is called for a token, `emergencyHatch` is permanently `true`. The token can never be re-enabled for bridging on this sucker. A new sucker must be deployed if bridging needs to resume.
183
-
184
- - **Cannot reverse deprecation past `SENDING_DISABLED`.** Once the sucker enters `SENDING_DISABLED` or `DEPRECATED` state, `setDeprecation()` reverts. The deprecation cannot be cancelled.
185
-
186
- - **Cannot bypass bridge security.** Cross-chain message authentication is enforced by each bridge implementation's `_isRemotePeer()` check. The project owner has no way to override this -- only messages from the legitimate remote peer (verified by the OP Messenger, Arbitrum Bridge/Outbox, or CCIP Router) are accepted by `fromRemote()`.
187
-
188
- - **Cannot reconfigure deployers.** `setChainSpecificConstants()` and `configureSingleton()` can each only be called once per deployer. Bridge addresses and the singleton implementation are permanent after initial configuration.
189
-
190
- - **Cannot steal user funds via emergency hatch.** Emergency exit only returns tokens to the original beneficiary specified in the outbox merkle tree, and only for leaves that were never sent over the bridge (index >= `numberOfClaimsSent`). Leaves already bridged cannot be double-claimed.
191
-
192
- - **Cannot claim on behalf of others.** While `claim()` and `exitThroughEmergencyHatch()` are permissionless (anyone can call them), the project tokens are always minted to the beneficiary encoded in the merkle tree leaf, not to the caller.
193
-
194
- - **Cannot change the peer address.** The peer is determined by deterministic deployment (CREATE2 with sender-specific salt). There is no admin function to change which remote address is trusted.
195
-
196
- - **Cannot change the project ID.** The `projectId` is set once during `initialize()` and is immutable thereafter (enforced by OpenZeppelin `Initializable`).
197
-
198
- - **Cannot change the fee project.** `FEE_PROJECT_ID` is set at construction and is immutable. If the fee project's terminal changes or is removed, fee payments silently stop (best-effort design), but `toRemote()` still works.
199
-
200
- - **Can adjust the fee amount, within bounds.** The registry owner can call `setToRemoteFee()` on `JBSuckerRegistry` to adjust the fee globally for all suckers, but it is capped at `MAX_TO_REMOTE_FEE` (0.001 ether).
201
-
202
- ## Bridge Infrastructure Risks
203
-
204
- Sucker security ultimately depends on the underlying bridge infrastructure (OP Cross-Domain Messenger, Arbitrum Inbox/Outbox, CCIP Router). Each sucker's `_isRemotePeer()` check trusts these bridges to faithfully report the sender of cross-chain messages. If a bridge itself is compromised, an attacker could forge messages that pass the peer check, potentially minting unbacked project tokens on the destination chain.
205
-
206
- **Mitigations available to project owners:**
74
+ - Registry owners cannot override project-local mapping or safety decisions directly.
75
+ - Project operators cannot reverse an emergency hatch.
76
+ - Project operators cannot force already sent leaves through the emergency hatch path.
77
+ - Nobody can mutate constructor immutables on live suckers or deployers.
207
78
 
208
- - **Emergency hatch.** If a bridge is suspected to be compromised for a specific token, the project owner (or `SUCKER_SAFETY` delegate) can call `enableEmergencyHatchFor()` to immediately disable bridging for that token and allow users to exit with their outbox entries locally.
209
- - **Deprecation.** If the bridge is broadly compromised, the project owner (or `SET_SUCKER_DEPRECATION` delegate) can deprecate the entire sucker via `setDeprecation()`, shutting down all new outbound and eventually inbound activity.
79
+ ## Source Map
210
80
 
211
- **Fee delivery is best-effort.** The `toRemoteFee` is collected by calling `terminal.pay()` to the fee project during `toRemote()`. If this call reverts (e.g., the fee project has no native token terminal or its terminal is misconfigured), the fee is silently skipped and `toRemote()` proceeds normally. This ensures bridging availability is never blocked by fee infrastructure issues, but it also means fee revenue can be lost without any on-chain signal.
81
+ - `src/JBSucker.sol`
82
+ - `src/JBSuckerRegistry.sol`
83
+ - `src/deployers/`
84
+ - `src/utils/MerkleLib.sol`
85
+ - `test/`
package/ARCHITECTURE.md CHANGED
@@ -2,59 +2,89 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- `nana-suckers-v6` moves Juicebox project token value across chains. A sucker pair lets a holder burn or cash out locally, bridge the resulting terminal token plus the latest merkle root, and then claim equivalent project-token value on the remote chain.
5
+ `nana-suckers-v6` moves Juicebox project-token value across chains. A sucker pair lets a holder destroy or consume a local project-token position, bridge the corresponding terminal-side value plus a Merkle root, and later claim equivalent value on the remote chain.
6
6
 
7
- ## Boundaries
7
+ ## System Overview
8
8
 
9
- - `JBSucker` owns the chain-agnostic lifecycle.
10
- - Chain-specific subclasses own transport details for OP Stack, Arbitrum, CCIP, and Celo variants.
11
- - The registry and deployers own pair creation and global policy.
12
- - Core protocol accounting still happens through project terminals on each chain.
9
+ `JBSucker` defines the chain-agnostic prepare, relay, and claim lifecycle. Chain-specific implementations such as `JBOptimismSucker`, `JBArbitrumSucker`, `JBCCIPSucker`, `JBSwapCCIPSucker`, `JBBaseSucker`, and `JBCeloSucker` handle transport-specific details. `JBSuckerRegistry` governs deployment inventory and shared policy, while deployers create deterministic clones for each supported transport family.
13
10
 
14
- ## Main Components
11
+ ## Core Invariants
15
12
 
16
- | Component | Responsibility |
17
- | --- | --- |
18
- | `JBSucker` | Prepare, root management, claim verification, token mapping, and deprecation controls |
19
- | chain-specific suckers | Bridge transport and token-wrapping details for each supported network family |
20
- | `JBSuckerRegistry` | Tracks deployed suckers, allowlists deployers, and stores global bridge-fee policy |
21
- | deployers | Clone and initialize deterministic sucker instances |
22
- | `MerkleLib` and helper libraries | Incremental tree logic plus chain-specific constants |
13
+ - Inbox and outbox trees must remain append-only and proof-compatible across chains.
14
+ - Token mapping is part of economic correctness; a wrong mapping is a value-loss bug.
15
+ - Deprecation or emergency controls must not break already-bridged claims.
16
+ - Roots may arrive out of order. Newer nonces replace older inbox roots, so claims must stay provable against the latest append-only tree.
17
+ - Root reception and token mapping are intentionally decoupled. Accepting a root for an unmapped token is valid if later mapping is what makes the claim redeemable.
18
+ - Transport-specific implementations may differ operationally, but they must preserve the same logical prepare-to-claim lifecycle.
23
19
 
24
- ## Runtime Model
20
+ ## Modules
21
+
22
+ | Module | Responsibility | Notes |
23
+ | --- | --- | --- |
24
+ | `JBSucker` | Prepare, root management, claim verification, token mapping, deprecation | Chain-agnostic base |
25
+ | chain-specific suckers | Transport details for OP Stack, Arbitrum, CCIP, Base, and Celo | Bridge-specific subclasses |
26
+ | `JBSuckerRegistry` | Deployer allowlist, inventory, and global bridge-fee policy | Shared policy surface |
27
+ | deployers | Deterministic clone deployment and initialization | One per transport family |
28
+ | `MerkleLib` and helper libraries | Incremental tree logic and chain constants | Proof-critical |
29
+
30
+ ## Trust Boundaries
31
+
32
+ - Project-token semantics and local terminal accounting remain rooted in `nana-core-v6`.
33
+ - Transport assumptions come from native bridge infrastructure for each chain family.
34
+ - Permission IDs come from `nana-permission-ids-v6`.
35
+
36
+ ## Critical Flows
37
+
38
+ ### Prepare, Relay, Claim
25
39
 
26
40
  ```text
27
41
  holder prepares a bridge
28
42
  -> sucker cashes out or consumes the local project-token position
29
- -> sucker inserts a merkle leaf into the outbox tree
30
- -> someone bridges funds and the latest root to the remote sucker
43
+ -> sucker inserts a Merkle leaf into the outbox tree
44
+ -> someone relays funds and the latest root to the remote sucker
45
+ -> remote sucker may accept a later nonce before an earlier one, updating shared cross-chain snapshots to the freshest project-wide message
31
46
  -> claimant proves inclusion against the remote inbox tree
32
- -> remote sucker releases or remints the destination-side value
47
+ -> remote sucker releases or remints destination-side value
33
48
  ```
34
49
 
35
- ## Critical Invariants
36
-
37
- - Inbox and outbox tree updates must remain append-only and proof-compatible across chains.
38
- - Token mapping is part of bridge correctness; a wrong remote token mapping is a value-loss bug.
39
- - Deprecation and emergency controls must not compromise already-bridged claims.
40
- - The chain-specific transport layers may differ, but they must preserve the same logical prepare-to-claim lifecycle.
50
+ ## Accounting Model
41
51
 
42
- ## Where Complexity Lives
52
+ The repo does not replace local treasury accounting. It owns bridge-specific claim accounting: outbox leaves, inbox roots, token mappings, replay protection, and the transition from local destruction to remote claimability.
43
53
 
44
- - The abstract lifecycle is simple, but every transport backend has its own failure modes and native-token quirks.
45
- - Tree state, token mapping, and claim replay protection all carry cross-chain blast radius.
46
- - Registry policy matters because deployment mistakes are hard to repair after pairs exist on multiple chains.
54
+ `JBSwapCCIPSucker` adds another accounting layer on top of the base lifecycle: nonce-indexed conversion rates. A claim can be temporarily blocked while a failed swap is pending retry, and successful claims are scaled against the conversion rate recorded for that batch's nonce.
47
55
 
48
- ## Dependencies
56
+ ## Security Model
49
57
 
50
- - `nana-core-v6` terminals and project-token semantics
51
- - Native bridge infrastructure for OP Stack, Arbitrum, CCIP, and supported variants
52
- - `nana-permission-ids-v6` for deploy, map, safety, and deprecation permissions
58
+ - Tree state, token mapping, and replay protection have cross-chain blast radius.
59
+ - Each transport backend has distinct failure modes and native-token quirks.
60
+ - Out-of-order message delivery is part of the trust model, not an exception path. Proof generation and monitoring must tolerate stale-root rejection and regenerated proofs against the newest root.
61
+ - Emergency hatch and deprecation flows are designed to preserve already-bridged exits. Post-deprecation root acceptance is intentional so in-flight messages do not strand users.
62
+ - Registry policy matters because bad deployments are hard to repair once pairs exist on multiple chains.
53
63
 
54
64
  ## Safe Change Guide
55
65
 
56
66
  - Review every cross-chain change from both sides of the pair.
57
- - Do not change merkle leaf encoding casually; proofs and remote compatibility depend on it.
58
- - Keep registry policy, deployer configuration, and singleton logic aligned.
59
- - Test chain-specific native-token wrapping paths separately from the abstract lifecycle.
60
- - Assume bridge-edge behavior is the primary review target, not the happy path.
67
+ - Do not change Merkle leaf encoding casually.
68
+ - Keep registry policy, deployer configuration, and singleton initialization aligned.
69
+ - If you change root or snapshot nonce handling, re-check out-of-order delivery behavior and whether older claims remain provable against the newest root.
70
+ - If you change CCIP swap handling, re-check pending-swap claim blocking and per-batch conversion-rate lookups together.
71
+ - Test chain-specific wrapping and native-token handling separately from the abstract lifecycle.
72
+
73
+ ## Canonical Checks
74
+
75
+ - peer snapshot and remote-state synchronization:
76
+ `test/audit/codex-PeerSnapshotDesync.t.sol`
77
+ - deprecation and stranded-destination handling:
78
+ `test/audit/DeprecatedSuckerDestination.t.sol`
79
+ - peer-chain state accounting:
80
+ `test/unit/peer_chain_state.t.sol`
81
+
82
+ ## Source Map
83
+
84
+ - `src/JBSucker.sol`
85
+ - `src/JBSuckerRegistry.sol`
86
+ - `src/deployers/`
87
+ - `src/utils/MerkleLib.sol`
88
+ - `test/audit/codex-PeerSnapshotDesync.t.sol`
89
+ - `test/audit/DeprecatedSuckerDestination.t.sol`
90
+ - `test/unit/peer_chain_state.t.sol`
@@ -2,7 +2,7 @@
2
2
 
3
3
  This repo bridges Juicebox project tokens and associated terminal assets across chains. Audit it as a conservation and replay-prevention system.
4
4
 
5
- ## Objective
5
+ ## Audit Objective
6
6
 
7
7
  Find issues that:
8
8
  - allow double claim, replay, or claim on the wrong destination
@@ -32,7 +32,7 @@ Read in this order:
32
32
 
33
33
  That order gets you from the shared conservation model to the transport-specific deviations.
34
34
 
35
- ## System Model
35
+ ## Security Model
36
36
 
37
37
  The bridge flow is:
38
38
  - burn or prepare project-token value on source chain
@@ -52,6 +52,21 @@ One non-obvious property to audit explicitly:
52
52
  - the system is intentionally designed to survive some transport mismatch without deadlocking
53
53
  - those recovery choices are exactly where conservation bugs tend to hide
54
54
 
55
+ ## Roles And Privileges
56
+
57
+ | Role | Powers | How constrained |
58
+ |------|--------|-----------------|
59
+ | Source-side caller | Prepare and bridge value to a remote chain | Must not create more claimable value than was prepared |
60
+ | Remote peer and messenger | Install new roots and deliver assets | Must be authenticated per transport |
61
+ | Emergency authority | Deprecate paths or enable recovery exits | Must not be able to steal in-flight funds |
62
+
63
+ ## Integration Assumptions
64
+
65
+ | Dependency | Assumption | What breaks if wrong |
66
+ |------------|------------|----------------------|
67
+ | Bridge transport | Delivers only authenticated peer messages | Anyone can spoof remote state |
68
+ | Token mapping and registry state | Remote asset identity stays stable | Users claim the wrong asset or wrong meaning |
69
+
55
70
  ## Critical Invariants
56
71
 
57
72
  1. Cross-chain conservation
@@ -72,22 +87,7 @@ Remote token mappings must be immutable or mutable only exactly where the design
72
87
  6. Nonce progression is monotonic in the way each transport expects
73
88
  Later roots must not silently invalidate earlier user claims unless the protocol explicitly intends that recovery path.
74
89
 
75
- ## Threat Model
76
-
77
- Prioritize:
78
- - out-of-order nonce arrival
79
- - cross-sucker replay
80
- - trusted-forwarder or messenger spoofing
81
- - emergency-exit races
82
- - fee fallback and bridge-payment edge cases
83
- - deterministic deployer assumptions for peer pairing
84
-
85
- The strongest attacker models here are:
86
- - a caller trying to claim from the wrong root with a structurally valid proof
87
- - a privileged actor abusing token mapping or emergency controls after users already prepared transfers
88
- - a transport delivering messages out of order and exposing assumptions hidden in the happy path
89
-
90
- ## Hotspots
90
+ ## Attack Surfaces
91
91
 
92
92
  - `prepare`, `toRemote`, `fromRemote`, and `claim`
93
93
  - bitmap execution tracking
@@ -96,33 +96,18 @@ The strongest attacker models here are:
96
96
  - chain-specific messenger authentication
97
97
  - deployer address derivation and clone setup
98
98
 
99
- ## Sequences Worth Replaying
100
-
101
- 1. Prepare multiple leaves -> send multiple roots -> receive them out of order -> attempt claims for each.
102
- 2. Prepare -> deprecate or enable emergency hatch -> claim and exit attempts racing each other.
103
- 3. Map token -> prepare transfer -> attempt remap or peer mismatch after value is already in flight.
104
- 4. Same logical transfer across different sucker implementations to check for replay or identity confusion.
99
+ Replay these sequences:
100
+ 1. prepare multiple leaves, send multiple roots, receive them out of order, and attempt each claim
101
+ 2. prepare, deprecate or enable emergency hatch, then race claim and exit paths
102
+ 3. map a token, prepare a transfer, then attempt remap or peer mismatch after value is in flight
103
+ 4. replay the same logical transfer across different sucker implementations
105
104
 
106
- ## Finding Bar
105
+ ## Accepted Risks Or Behaviors
107
106
 
108
- The strongest findings here usually show one of these:
109
- - a user can claim against value that was never actually prepared
110
- - a valid prepare becomes permanently unclaimable without the recovery path the protocol expects
111
- - transport-specific authentication is weaker than the shared model assumes
112
- - a privileged mapping or safety control can rewrite the meaning of already in-flight value
107
+ - Out-of-order arrival is part of the intended model, not an edge case.
113
108
 
114
- ## Build And Verification
109
+ ## Verification
115
110
 
116
- Standard workflow:
117
111
  - `npm install`
118
112
  - `forge build`
119
113
  - `forge test`
120
-
121
- The current tests already target:
122
- - deep attack and regression scenarios
123
- - trusted-forwarder spoofing
124
- - fee fallback behavior
125
- - deterministic deployment
126
- - chain-specific fork flows
127
-
128
- High-value findings here show a break in conservation, replay resistance, or trusted-peer boundaries.
package/README.md CHANGED
@@ -3,7 +3,12 @@
3
3
  `@bananapus/suckers-v6` provides cross-chain bridging for Juicebox project tokens and the terminal assets that back them. A pair of suckers lets users burn on one chain, move value across a bridge, and mint the same project token representation on another chain.
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
  The codebase includes multiple bridge variants, but the canonical deployment and discovery tooling in this repo is narrower than the full runtime surface. Treat the deployment scripts and helper libraries as the source of truth for what is operationally supported today.
9
14
 
@@ -50,7 +55,7 @@ The shortest useful reading order is:
50
55
  | Contract | Description |
51
56
  |----------|-------------|
52
57
  | [`JBSucker`](src/JBSucker.sol) | Abstract base. Manages outbox/inbox merkle trees, `prepare`/`toRemote`/`claim` lifecycle, token mapping, deprecation, and emergency hatch. Deployed as clones via `Initializable`. Uses `ERC2771Context` for meta-transactions. Has immutable `FEE_PROJECT_ID` (typically project ID 1) and immutable `REGISTRY` reference. Reads the `toRemoteFee` from the registry via `REGISTRY.toRemoteFee()` on each `toRemote()` call. |
53
- | [`JBCCIPSucker`](src/JBCCIPSucker.sol) | Extends `JBSucker`. Bridges via Chainlink CCIP (`ccipSend`/`ccipReceive`). Supports any CCIP-connected chain pair. Wraps native ETH to WETH before bridging (CCIP only transports ERC-20s) and unwraps on the receiving end. Can map `NATIVE_TOKEN` to ERC-20 addresses on the remote chain (unlike OP/Arbitrum suckers). |
58
+ | [`JBCCIPSucker`](src/JBCCIPSucker.sol) | Extends `JBSucker`. Bridges via Chainlink CCIP (`ccipSend`/`ccipReceive`) for chain pairs whose router, selector, and token mapping are configured. Wraps native ETH to WETH before bridging (CCIP only transports ERC-20s) and unwraps on the receiving end. Can map `NATIVE_TOKEN` to ERC-20 addresses on the remote chain (unlike OP/Arbitrum suckers). |
54
59
  | [`JBOptimismSucker`](src/JBOptimismSucker.sol) | Extends `JBSucker`. Bridges via OP Standard Bridge + OP Messenger. No `msg.value` required for transport. |
55
60
  | [`JBBaseSucker`](src/JBBaseSucker.sol) | Thin wrapper around `JBOptimismSucker` with Base chain IDs (Ethereum 1 <-> Base 8453, Sepolia 11155111 <-> Base Sepolia 84532). |
56
61
  | [`JBCeloSucker`](src/JBCeloSucker.sol) | Extends `JBOptimismSucker` for Celo (OP Stack, custom gas token CELO). Wraps native ETH → WETH before bridging as ERC-20. Unwraps received WETH → native ETH via `_addToBalance` override. Removes `NATIVE_TOKEN → NATIVE_TOKEN` restriction. Sends messenger messages with `nativeValue = 0` (Celo's native token is CELO, not ETH). |
@@ -63,7 +68,7 @@ The shortest useful reading order is:
63
68
  | [`JBCeloSuckerDeployer`](src/deployers/JBCeloSuckerDeployer.sol) | Deployer for `JBCeloSucker`. Extends `JBOptimismSuckerDeployer` with `wrappedNative` (`IWrappedNativeToken`) storage for the local chain's WETH address. |
64
69
  | [`JBArbitrumSuckerDeployer`](src/deployers/JBArbitrumSuckerDeployer.sol) | Deployer for `JBArbitrumSucker`. Stores Arbitrum Inbox, Gateway Router, and layer (`JBLayer.L1` or `JBLayer.L2`). |
65
70
  | [`MerkleLib`](src/utils/MerkleLib.sol) | Incremental merkle tree (depth 32, max ~4 billion leaves, modeled on eth2 deposit contract). Used for outbox/inbox trees. `insert` and `root` operate directly on `Tree storage` (not memory copies) to avoid redundant SLOAD/SSTORE round-trips. Gas-optimized with inline assembly for `root()` and `branchRoot()`. |
66
- | [`CCIPHelper`](src/libraries/CCIPHelper.sol) | CCIP router addresses, chain selectors, and WETH addresses per chain. Covers Ethereum, Optimism, Arbitrum, Base, Polygon, Avalanche, and BNB Chain (mainnet and testnets). |
71
+ | [`CCIPHelper`](src/libraries/CCIPHelper.sol) | CCIP router addresses, chain selectors, and WETH addresses for the chain set currently encoded in this repo. |
67
72
  | [`ARBAddresses`](src/libraries/ARBAddresses.sol) | Arbitrum bridge contract addresses (Inbox, Gateway Router) for mainnet and Sepolia. |
68
73
  | [`ARBChains`](src/libraries/ARBChains.sol) | Arbitrum chain ID constants. |
69
74
 
@@ -90,6 +95,14 @@ The shortest useful reading order is:
90
95
 
91
96
  When reviewing a bridge incident, check local state transition correctness before blaming the transport layer.
92
97
 
98
+ ## High-Signal Tests
99
+
100
+ 1. `test/unit/registry.t.sol`
101
+ 2. `test/unit/multi_chain_evolution.t.sol`
102
+ 3. `test/ForkClaimMainnet.t.sol`
103
+ 4. `test/audit/codex-PeerSnapshotDesync.t.sol`
104
+ 5. `test/audit/codex-ToRemoteFeeIrrecoverable.t.sol`
105
+
93
106
  ## Install
94
107
 
95
108
  ```bash
@@ -112,7 +125,7 @@ Useful scripts:
112
125
 
113
126
  ## Deployment Notes
114
127
 
115
- This package supports multiple bridge families and is intentionally split into bridge-specific deployers. It is commonly used directly and through the Omnichain and Revnet deployer packages.
128
+ This package supports multiple bridge families and is intentionally split into bridge-specific deployers. Operational support is narrower than "all theoretically bridgeable chains" and should be taken from the configured deployers, helper libraries, and deployment scripts in this repo.
116
129
 
117
130
  ## Repository Layout
118
131
 
@@ -142,3 +155,9 @@ script/
142
155
  - a bridge that stays live operationally still may not be economically safe for every asset or chain pair
143
156
 
144
157
  When debugging a bad cross-chain outcome, first decide whether the failure is in claim construction, message transport, inbox/outbox root progression, or remote settlement. Those are different bug classes.
158
+
159
+ ## For AI Agents
160
+
161
+ - Do not summarize this repo as a generic token bridge; it bridges Juicebox project positions plus transported value.
162
+ - Always separate shared sucker logic from bridge-specific transport behavior in your explanation.
163
+ - Use the chain-specific implementation and the matching deployer together when answering operational questions.
package/RISKS.md CHANGED
@@ -62,6 +62,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
62
62
  - **Renounced registry ownership risk.** If the registry owner calls `renounceOwnership()`, `setToRemoteFee()` becomes permanently uncallable and the fee is frozen at its current value across all suckers. This is a deliberate trade-off: it allows the registry owner to credibly commit to a fee level, but eliminates the ability to respond to future ETH price changes. The fee is still capped at `MAX_TO_REMOTE_FEE`, so the maximum downside is bounded.
63
63
  - **Immutable fee project.** `FEE_PROJECT_ID` is set at construction and cannot be changed. If the fee project is abandoned or its terminal removed, there is no way to redirect fees without deploying new suckers.
64
64
  - **Cross-reference: sucker registration path.** Suckers are deployed via `JBSuckerRegistry.deploySuckersFor`, which requires `DEPLOY_SUCKERS` permission from the project owner. The registry's `deploy` function uses `CREATE2` with a deployer-specific salt. The sucker's `peer()` address is deterministic — a misconfigured peer means the sucker accepts messages from the wrong remote address. See [nana-omnichain-deployers-v6 RISKS.md](../nana-omnichain-deployers-v6/RISKS.md) for deployer-level risks.
65
+ - **Registry aggregate views fail open.** `JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` sum values across active suckers but silently skip any sucker that reverts. This preserves liveness for dashboards and cross-chain estimates, but it means the returned aggregate can understate remote state whenever one peer is broken, censored, deprecated incorrectly, or simply expensive to query.
65
66
 
66
67
  ## 6. Deprecation Lifecycle
67
68
 
@@ -127,10 +128,14 @@ The registry owner can adjust `toRemoteFee` via `JBSuckerRegistry.setToRemoteFee
127
128
 
128
129
  The fee is paid to `FEE_PROJECT_ID` (the protocol project), not to the sucker's own `projectId()`. This centralizes fee collection, but it is still only best-effort: if the fee project's native terminal is missing or its `pay` call reverts, the fee ETH stays in the sucker contract and is later recoverable through the normal claim path. The sucker's project does not directly benefit from the anti-spam fee.
129
130
 
130
- ### 10.5 Hookless V4 spot pricing is sandwich-vulnerable by design
131
+ ### 10.5 Registry aggregate views prioritize liveness over completeness
132
+
133
+ `JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` intentionally use `try/catch` around each sucker and silently ignore peers that revert. This is accepted because a single bad peer should not brick every cross-chain dashboard or estimator. The trade-off is that these read surfaces are best-effort only: consumers must treat them as lower bounds, not exact reconciled totals, unless they independently verify that every active sucker responded successfully.
134
+
135
+ ### 10.6 Hookless V4 spot pricing is sandwich-vulnerable by design
131
136
 
132
137
  When the only available Uniswap pool for a cross-denomination swap is a hookless V4 pool (no V3 pool exists), `_getV4Quote` falls back to the instantaneous spot tick from `POOL_MANAGER.getSlot0()` instead of a TWAP oracle. This tick is manipulable via sandwich attacks, allowing an attacker to skew the `minAmountOut` and extract value from the swap. The sigmoid slippage model limits the damage but operates on a corrupted baseline. This is an accepted tradeoff: reverting when no TWAP is available would cause the CCIP message to fail, leaving bridged tokens stuck in the CCIP router until manual retry. Getting some value (even at a worse rate) is preferred over a stuck bridge message, especially since this path only triggers when no V3 pool with built-in TWAP exists at all. The hooked V4 path also falls back to spot if the hook's `observe()` reverts, with the same tradeoff.
133
138
 
134
- ### 10.6 `mapTokens` refunds ETH on enable-only batches
139
+ ### 10.7 `mapTokens` refunds ETH on enable-only batches
135
140
 
136
141
  `mapTokens()` only uses `msg.value` when one or more mappings are being disabled and need transport payment for the final root flush. If every mapping in the batch is enable-only (`numberToDisable == 0`), the full `msg.value` is refunded to `_msgSender()`. If the refund transfer fails (e.g., the caller is a non-payable contract), the call reverts with `JBSucker_RefundFailed`. When disables are present, any dust remainder from integer division (`msg.value % numberToDisable`) is also refunded on a best-effort basis.
package/SKILLS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ## Use This File For
4
4
 
5
5
  - Use this file when the task involves cross-chain project-token bridging, token mapping, Merkle claim flow, bridge-specific transport logic, or sucker registry behavior.
6
- - Start here, then open the base sucker, registry, chain-specific implementation, or deployer depending on which leg of the bridge path is under review.
6
+ - Start here, then decide whether the issue is in shared accounting, message authentication, token mapping, or operator/deprecation controls. Those concerns live in different layers.
7
7
 
8
8
  ## Read This Next
9
9
 
@@ -11,9 +11,11 @@
11
11
  |---|---|
12
12
  | Repo overview and bridge model | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
13
13
  | Shared bridge logic | [`src/JBSucker.sol`](./src/JBSucker.sol), [`src/JBSuckerRegistry.sol`](./src/JBSuckerRegistry.sol) |
14
- | Chain-specific transport behavior | [`src/`](./src/), [`src/deployers/`](./src/deployers/) |
14
+ | Chain-specific transport behavior | [`src/JBArbitrumSucker.sol`](./src/JBArbitrumSucker.sol), [`src/JBOptimismSucker.sol`](./src/JBOptimismSucker.sol), [`src/JBCCIPSucker.sol`](./src/JBCCIPSucker.sol), [`src/JBCeloSucker.sol`](./src/JBCeloSucker.sol) |
15
+ | Deployer and transport setup | [`src/deployers/`](./src/deployers/) |
15
16
  | Merkle and helper logic | [`src/utils/`](./src/utils/), [`src/libraries/`](./src/libraries/) |
16
- | Attack, interoperability, or regression coverage | [`test/`](./test/), [`test/regression/`](./test/regression/), [`test/fork/`](./test/fork/) |
17
+ | Interop and chain-specific fork coverage | [`test/ForkMainnet.t.sol`](./test/ForkMainnet.t.sol), [`test/ForkArbitrum.t.sol`](./test/ForkArbitrum.t.sol), [`test/ForkCelo.t.sol`](./test/ForkCelo.t.sol), [`test/ForkOPStack.t.sol`](./test/ForkOPStack.t.sol), [`test/InteropCompat.t.sol`](./test/InteropCompat.t.sol) |
18
+ | Swap, claim, attack, and regression coverage | [`test/ForkSwap.t.sol`](./test/ForkSwap.t.sol), [`test/ForkClaimMainnet.t.sol`](./test/ForkClaimMainnet.t.sol), [`test/SuckerAttacks.t.sol`](./test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](./test/SuckerDeepAttacks.t.sol), [`test/SuckerRegressions.t.sol`](./test/SuckerRegressions.t.sol), [`test/TestAuditGaps.sol`](./test/TestAuditGaps.sol) |
17
19
 
18
20
  ## Repo Map
19
21
 
@@ -37,6 +39,12 @@ Cross-chain bridge layer for Juicebox project tokens and the terminal assets tha
37
39
  ## Working Rules
38
40
 
39
41
  - Start in [`src/JBSucker.sol`](./src/JBSucker.sol) for shared accounting and claim flow, then move to the chain-specific implementation only after you know the base path is correct.
42
+ - `JBSucker` explicitly does not support fee-on-transfer or rebasing tokens. If a bug report involves those assets, treat it as an unsupported-path question first.
43
+ - Root progression, peer supply, and peer surplus snapshots are part of economic correctness, not just bridge bookkeeping.
44
+ - Token mapping is intentionally one-way after real activity starts. Disabling a mapping is allowed; remapping to a different remote asset is not.
45
+ - Peer symmetry depends on deployer and salt assumptions as well as runtime code. A bridge bug can start in deployment shape before it appears in message flow.
40
46
  - Treat token mapping, root progression, and emergency/deprecation controls as first-class runtime behavior, not admin-only side tooling.
41
47
  - When debugging a bridge incident, separate accounting correctness from transport correctness before patching.
48
+ - Message authentication is delegated to bridge-specific subclasses. When reviewing a new transport, `_isRemotePeer` is one of the first things to inspect.
49
+ - Emergency exit and deprecation behavior are intentionally conservative. Some failure modes lock funds rather than risking double-spend.
42
50
  - If a task touches project deployment shape, check whether the real source is `nana-omnichain-deployers-v6` or `revnet-core-v6` instead of the sucker implementation itself.
package/USER_JOURNEYS.md CHANGED
@@ -1,83 +1,140 @@
1
1
  # User Journeys
2
2
 
3
- ## Who This Repo Serves
3
+ ## Repo Purpose
4
+
5
+ This repo bridges Juicebox project-token positions and their treasury-backed claim semantics across chains.
6
+ It is not a generic proxy terminal and not a generic ERC-20 bridge. The important unit is the project position and the
7
+ explicit bridge lifecycle around `prepare`, `toRemote`, and claim.
8
+
9
+ ## Primary Actors
4
10
 
5
11
  - projects that want canonical cross-chain movement of project-token positions
6
12
  - operators deploying and registering sucker pairs on supported bridge families
7
13
  - users bridging a project position from one chain to another
8
14
  - teams responsible for bridge fees, token mappings, deprecation, and emergency controls
9
15
 
16
+ ## Key Surfaces
17
+
18
+ - `JBSucker`: shared base lifecycle for preparing, relaying, and claiming bridge leaves
19
+ - `JBSuckerRegistry`: registry for sucker deployments, deployer allowlists, and shared fee settings
20
+ - `JBOptimismSucker`, `JBBaseSucker`, `JBCeloSucker`, `JBArbitrumSucker`, `JBCCIPSucker`, `JBSwapCCIPSucker`: bridge-family implementations
21
+
10
22
  ## Journey 1: Launch A Cross-Chain Sucker Pair For A Project
11
23
 
12
- **Starting state:** the project exists on multiple chains or plans to, and the team has chosen the bridge family it trusts.
24
+ **Actor:** operator or deployer.
25
+
26
+ **Intent:** deploy and register the paired bridge surfaces a project will rely on across chains.
13
27
 
14
- **Success:** paired suckers are deployed, registered, and ready to transport claims between the chains they serve.
28
+ **Preconditions**
29
+ - the project exists on multiple chains or plans to
30
+ - the team has chosen the bridge family it trusts
15
31
 
16
- **Flow**
32
+ **Main Flow**
17
33
  1. Choose the chain-specific sucker implementation and deployer, such as Arbitrum, OP Stack, Celo, or CCIP.
18
34
  2. Configure token mappings, bridge counterparties, and per-project registry state in `JBSuckerRegistry`.
19
35
  3. Deploy the pair so each side knows its remote peer and expected transport assumptions.
20
36
  4. Frontends and operators can now reason about the bridge as a known project surface instead of ad hoc per-transfer logic.
21
37
 
38
+ **Failure Modes**
39
+ - paired deployments disagree about counterparties or token mappings
40
+ - teams deploy the right contracts but never register the resulting pair coherently
41
+
42
+ **Postconditions**
43
+ - paired suckers are deployed, registered, and ready to transport claims between the chains they serve
44
+
22
45
  ## Journey 2: Bridge A Position From One Chain To Another
23
46
 
24
- **Starting state:** a user holds project-token exposure on the source chain and wants the corresponding position on the destination chain.
47
+ **Actor:** user bridging a position.
25
48
 
26
- **Success:** the source position becomes a claim, the claim is relayed, and the destination position is minted after proof verification.
49
+ **Intent:** move project-token exposure from the source chain to the destination chain.
27
50
 
28
- **Flow**
51
+ **Preconditions**
52
+ - a user holds project-token exposure on the source chain
53
+ - the project has a supported destination-side sucker path
54
+
55
+ **Main Flow**
29
56
  1. The user calls `prepare` on the source-chain sucker to burn or lock the relevant local position into a claimable leaf.
30
57
  2. The source sucker appends that leaf into its Merkle outbox tree.
31
58
  3. Someone relays the new root to the remote chain using `toRemote`.
32
59
  4. The claimant proves inclusion against the remote inbox tree and receives the recreated project-token position there.
33
60
 
34
- **Failure cases that matter:** wrong token mappings, transport-layer fee shortages, root ordering mistakes, and assuming the bridge is generic ERC-20 transport when it is really project-position transport.
61
+ **Failure Modes**
62
+ - token mappings are wrong for the project or chain pair
63
+ - transport-layer fees are missing and roots never arrive
64
+ - operators assume the bridge is generic ERC-20 transport rather than project-position transport
65
+
66
+ **Postconditions**
67
+ - the source position becomes a claim, the claim is relayed, and the destination position is minted after proof verification
35
68
 
36
69
  ## Journey 3: Map Treasury Assets And Project Tokens Correctly Across Chains
37
70
 
38
- **Starting state:** the project supports multiple assets or wrappers across chains and wants users to bridge without silent economic mismatch.
71
+ **Actor:** operator mapping assets and wrappers.
39
72
 
40
- **Success:** the remote claim recreates the intended exposure instead of a superficially similar but economically different asset.
73
+ **Intent:** preserve economic meaning across chains instead of bridging into the wrong wrapped exposure.
41
74
 
42
- **Flow**
75
+ **Preconditions**
76
+ - the project supports multiple assets or wrappers across chains
77
+ - users should be able to bridge without silent economic mismatch
78
+
79
+ **Main Flow**
43
80
  1. Configure remote token metadata and mapping with the sucker pair.
44
81
  2. Make sure the destination chain can mint or settle the project-token representation the bridge expects.
45
82
  3. Audit chain-specific native-asset handling, especially on Celo or other non-identical environments.
46
83
 
84
+ **Failure Modes**
85
+ - local and remote wrappers look similar but settle into different economics
86
+ - chain-specific native-asset assumptions are copied across environments where they do not hold
87
+
88
+ **Postconditions**
89
+ - the remote claim recreates the intended exposure instead of a superficially similar but economically different asset
90
+
47
91
  ## Journey 4: Operate The Bridge Safely Over Time
48
92
 
49
- **Starting state:** the bridge is live and now needs operational stewardship rather than just deployment.
93
+ **Actor:** bridge operator.
94
+
95
+ **Intent:** keep registry config, fees, deprecation, and bridge-family assumptions coherent after launch.
50
96
 
51
- **Success:** fee policy, deprecation, trusted counterparties, and emergency paths remain coherent as conditions change.
97
+ **Preconditions**
98
+ - the bridge is live and now needs operational stewardship rather than just deployment
52
99
 
53
- **Flow**
100
+ **Main Flow**
54
101
  1. Use `JBSuckerRegistry` to manage deployer allowlists and shared operational config.
55
102
  2. Watch fee fallback paths and transport assumptions because delivery failure is part of the intended threat model.
56
103
  3. Use deprecation or emergency surfaces when a bridge family or remote destination should no longer be used.
57
104
 
105
+ **Failure Modes**
106
+ - fee policy drifts from actual transport costs and claims stop delivering
107
+ - bridge-family deprecation is delayed even after counterparties or fees become unsafe
108
+
109
+ **Postconditions**
110
+ - fee policy, deprecation, trusted counterparties, and emergency paths remain coherent as conditions change
111
+
58
112
  ## Journey 5: Recover Value Through The Emergency Hatch When Normal Delivery Breaks
59
113
 
60
- **Starting state:** a claim cannot complete through the normal inbox or remote-delivery path.
114
+ **Actor:** user or responder handling a broken delivery path.
61
115
 
62
- **Success:** users can recover through the explicit emergency mechanism without double-spending the same claim.
116
+ **Intent:** recover value when the normal bridge delivery path is unavailable.
63
117
 
64
- **Flow**
118
+ **Preconditions**
119
+ - a claim cannot complete through the normal inbox or remote-delivery path
120
+
121
+ **Main Flow**
65
122
  1. Enable or enter the emergency mode the sucker pair exposes for the affected path.
66
123
  2. Use `exitThroughEmergencyHatch(...)` with the relevant claim data.
67
124
  3. Treat emergency execution slots as distinct state that still must not allow the same economic position to be claimed twice.
68
125
 
69
- ## Journey 6: Create A Proxy Project And Route Payments Through It
70
-
71
- **Starting state:** a project exists on the home chain with an ERC-20 token deployed, and wants to let users on other chains acquire proxy tokens backed by real project tokens.
126
+ **Failure Modes**
127
+ - teams use the emergency path prematurely instead of as a documented recovery mode
128
+ - claim state is not checked carefully and responders risk inconsistent double-claim assumptions
72
129
 
73
- **Success:** a proxy project is created once, and payments routed through it mint proxy tokens 1:1 with the real tokens deposited.
130
+ **Postconditions**
131
+ - users can recover through the explicit emergency mechanism without double-spending the same claim
74
132
 
75
- **Flow**
76
- 1. Call `JBSuckerTerminal.createProxy(realProjectId, name, symbol, salt)` to launch a locked proxy project with a permanent 1:1 ruleset, backed by the real project's ERC-20 token.
77
- 2. Anyone can then call `JBSuckerTerminal.pay(proxyProjectId, token, amount, beneficiary, ...)` to pay the real project and automatically receive proxy tokens.
78
- 3. Suckers registered for the proxy project get mint permission through `JBSuckerTerminal`'s data hook, enabling cross-chain bridging of proxy token positions.
133
+ ## Trust Boundaries
79
134
 
80
- **Failure cases that matter:** calling `createProxy` on a project without an ERC-20 deployed, attempting to create a second proxy for the same project, and paying with a token that has no primary terminal on the real project.
135
+ - this repo trusts both the shared sucker accounting logic and the selected bridge-family transport
136
+ - token mapping and registry governance are part of the economic safety model
137
+ - emergency and deprecation controls are operationally important, not just last-resort tooling
81
138
 
82
139
  ## Hand-Offs
83
140
 
package/foundry.toml CHANGED
@@ -14,6 +14,7 @@ depth = 100
14
14
  fail_on_revert = false
15
15
 
16
16
  [lint]
17
+ exclude_lints = ["pascal-case-struct", "mixed-case-variable"]
17
18
  lint_on_build = false
18
19
 
19
20
  [fmt]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/suckers-v6",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,14 +3,16 @@
3
3
  ## Configuration Surface
4
4
 
5
5
  - [`src/JBSuckerRegistry.sol`](../src/JBSuckerRegistry.sol) is the first stop for deployer allowlists, shared fees, project inventory, and deprecation helpers.
6
- - Transport-specific deployers in [`src/deployers/`](../src/deployers/) are where chain-specific constants and bridge addresses live.
7
- - [`script/`](../script/) is where deployment-time environment wiring belongs.
6
+ - Transport-specific deployers in `src/deployers/` are where chain-specific constants and bridge addresses live.
7
+ - [`script/Deploy.s.sol`](../script/Deploy.s.sol) is where deployment-time environment wiring belongs.
8
8
 
9
9
  ## Change Checklist
10
10
 
11
11
  - If you edit base sucker accounting, verify claim flow across at least one chain-specific implementation.
12
12
  - If you edit token mapping logic, re-check the registry and deployer assumptions that feed it.
13
+ - If you edit token mapping semantics, verify that remapping is still impossible once outbox activity has made economic equivalence depend on permanence.
13
14
  - If you edit deprecation or emergency paths, verify the intended operator workflow still works end to end.
15
+ - If you edit snapshot or claim-boundary logic, verify `numberOfClaimsSent`, peer snapshots, and emergency exit behavior together.
14
16
  - If you touch bridge-specific code, confirm whether the real bug is transport-side or shared accounting-side.
15
17
 
16
18
  ## Common Failure Modes
@@ -21,5 +23,6 @@
21
23
 
22
24
  ## Useful Proof Points
23
25
 
24
- - [`test/audit/`](../test/audit/) for security-sensitive assumptions.
25
- - [`script/helpers/`](../script/helpers/) when the problem is deployment wiring rather than runtime logic.
26
+ - [`test/SuckerAttacks.t.sol`](../test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](../test/SuckerDeepAttacks.t.sol), and [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for security-sensitive assumptions.
27
+ - [`test/InteropCompat.t.sol`](../test/InteropCompat.t.sol) when the problem is deployment wiring rather than runtime logic.
28
+ - [`test/unit/invariants.t.sol`](../test/unit/invariants.t.sol), [`test/unit/peer_chain_state.t.sol`](../test/unit/peer_chain_state.t.sol), and [`test/audit/codex-PeerSnapshotDesync.t.sol`](../test/audit/codex-PeerSnapshotDesync.t.sol) when shared accounting or snapshot boundaries are in doubt.
@@ -4,8 +4,8 @@
4
4
 
5
5
  - [`src/JBSucker.sol`](../src/JBSucker.sol) owns the shared prepare, relay, claim, token-mapping, and lifecycle logic.
6
6
  - [`src/JBSuckerRegistry.sol`](../src/JBSuckerRegistry.sol) owns project-to-sucker inventory, deployer allowlists, and shared remote-fee settings.
7
- - Chain-specific sucker contracts under [`src/`](../src/) own the transport-specific message delivery and verification path.
8
- - Matching deployers under [`src/deployers/`](../src/deployers/) own clone and transport configuration.
7
+ - Chain-specific sucker contracts such as [`src/JBArbitrumSucker.sol`](../src/JBArbitrumSucker.sol), [`src/JBOptimismSucker.sol`](../src/JBOptimismSucker.sol), [`src/JBCCIPSucker.sol`](../src/JBCCIPSucker.sol), and [`src/JBCeloSucker.sol`](../src/JBCeloSucker.sol) own transport-specific delivery and verification.
8
+ - Matching deployers under `src/deployers/` own clone and transport configuration.
9
9
 
10
10
  ## Runtime Path
11
11
 
@@ -20,9 +20,11 @@
20
20
  - Root ordering and replay protection: message sequencing is part of correctness.
21
21
  - Emergency and deprecation paths: these are operational safety surfaces that must remain reliable.
22
22
  - Shared accounting vs transport logic: many incidents stem from confusing these layers.
23
+ - Peer snapshots and `numberOfClaimsSent`: these guard against double-spend at the cost of conservative locking when timing goes wrong.
23
24
 
24
25
  ## Tests To Trust First
25
26
 
26
- - [`test/fork/`](../test/fork/) for real transport assumptions.
27
- - [`test/regression/`](../test/regression/) for pinned cross-chain edge cases.
28
- - [`test/`](../test/) broadly when the bug could involve base logic, registry behavior, or a specific bridge implementation.
27
+ - [`test/ForkMainnet.t.sol`](../test/ForkMainnet.t.sol), [`test/ForkArbitrum.t.sol`](../test/ForkArbitrum.t.sol), [`test/ForkCelo.t.sol`](../test/ForkCelo.t.sol), and [`test/ForkOPStack.t.sol`](../test/ForkOPStack.t.sol) for real transport assumptions.
28
+ - [`test/ForkSwap.t.sol`](../test/ForkSwap.t.sol), [`test/ForkClaimMainnet.t.sol`](../test/ForkClaimMainnet.t.sol), and [`test/SuckerRegressions.t.sol`](../test/SuckerRegressions.t.sol) for pinned cross-chain edge cases.
29
+ - [`test/unit/invariants.t.sol`](../test/unit/invariants.t.sol), [`test/unit/peer_chain_state.t.sol`](../test/unit/peer_chain_state.t.sol), and [`test/unit/registry.t.sol`](../test/unit/registry.t.sol) for shared-accounting invariants.
30
+ - [`test/SuckerAttacks.t.sol`](../test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](../test/SuckerDeepAttacks.t.sol), [`test/audit/codex-PeerSnapshotDesync.t.sol`](../test/audit/codex-PeerSnapshotDesync.t.sol), and [`test/audit/codex-PeerDeterminism.t.sol`](../test/audit/codex-PeerDeterminism.t.sol) when the bug could involve base logic, registry behavior, or a specific bridge implementation.
@@ -205,7 +205,6 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
205
205
  address token,
206
206
  uint256 amount,
207
207
  JBRemoteToken memory remoteToken,
208
- // forge-lint: disable-next-line(mixed-case-variable)
209
208
  JBMessageRoot memory sucker_message
210
209
  )
211
210
  internal
@@ -512,7 +512,6 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
512
512
  address token,
513
513
  uint256 amount,
514
514
  JBRemoteToken memory remoteToken,
515
- // forge-lint: disable-next-line(mixed-case-variable)
516
515
  JBMessageRoot memory sucker_message
517
516
  )
518
517
  internal
@@ -6,7 +6,6 @@ import {JBLeaf} from "./JBLeaf.sol";
6
6
  /// @custom:member token The token to claim.
7
7
  /// @custom:member leaf The leaf to claim from.
8
8
  /// @custom:member proof The proof to claim with. Must be of length `JBSucker._TREE_DEPTH`.
9
- // forge-lint: disable-next-line(pascal-case-struct)
10
9
  struct JBClaim {
11
10
  address token;
12
11
  JBLeaf leaf;
@@ -5,7 +5,6 @@ pragma solidity ^0.8.0;
5
5
  /// @custom:member value The amount.
6
6
  /// @custom:member currency The currency identifier (e.g. `JBCurrencyIds.ETH`).
7
7
  /// @custom:member decimals The decimal precision of `value`.
8
- // forge-lint: disable-next-line(pascal-case-struct)
9
8
  struct JBDenominatedAmount {
10
9
  uint256 value;
11
10
  uint32 currency;
@@ -6,7 +6,6 @@ pragma solidity ^0.8.0;
6
6
  /// inbox tree.
7
7
  /// @custom:member nonce Tracks the nonce of the tree. The nonce cannot decrease.
8
8
  /// @custom:member root The root of the tree.
9
- // forge-lint: disable-next-line(pascal-case-struct)
10
9
  struct JBInboxTreeRoot {
11
10
  uint64 nonce;
12
11
  bytes32 root;
@@ -6,7 +6,6 @@ pragma solidity ^0.8.0;
6
6
  /// @custom:member beneficiary The beneficiary of the leaf.
7
7
  /// @custom:member projectTokenCount The number of project tokens to claim.
8
8
  /// @custom:member terminalTokenAmount The amount of terminal tokens to claim.
9
- // forge-lint: disable-next-line(pascal-case-struct)
10
9
  struct JBLeaf {
11
10
  uint256 index;
12
11
  bytes32 beneficiary;
@@ -20,7 +20,6 @@ import {JBInboxTreeRoot} from "./JBInboxTreeRoot.sol";
20
20
  /// @custom:member snapshotNonce A project-wide counter that orders shared-state snapshots independently of per-token
21
21
  /// outbox nonces. Used by the receiving chain to reject stale surplus/balance/supply updates without blocking
22
22
  /// token-local inbox root updates.
23
- // forge-lint: disable-next-line(pascal-case-struct)
24
23
  struct JBMessageRoot {
25
24
  uint8 version;
26
25
  bytes32 token;
@@ -10,7 +10,6 @@ import {MerkleLib} from "../utils/MerkleLib.sol";
10
10
  /// claims have been sent. Packed with `nonce` into one storage slot.
11
11
  /// @custom:member balance The balance of the outbox.
12
12
  /// @custom:member tree The merkle tree.
13
- // forge-lint: disable-next-line(pascal-case-struct)
14
13
  struct JBOutboxTree {
15
14
  uint64 nonce;
16
15
  uint192 numberOfClaimsSent;
@@ -7,7 +7,6 @@ pragma solidity ^0.8.0;
7
7
  /// @custom:member emergencyHatchOpened Whether the emergency hatch is opened.
8
8
  /// @custom:member minGas The minimum gas to use when bridging.
9
9
  /// @custom:member addr The address of the token on the remote chain.
10
- // forge-lint: disable-next-line(pascal-case-struct)
11
10
  struct JBRemoteToken {
12
11
  bool enabled;
13
12
  bool emergencyHatch;
@@ -6,7 +6,6 @@ import {JBTokenMapping} from "./JBTokenMapping.sol";
6
6
 
7
7
  /// @custom:member deployer The deployer to use.
8
8
  /// @custom:member mappings The token mappings to use.
9
- // forge-lint: disable-next-line(pascal-case-struct)
10
9
  struct JBSuckerDeployerConfig {
11
10
  IJBSuckerDeployer deployer;
12
11
  JBTokenMapping[] mappings;
@@ -4,7 +4,6 @@ pragma solidity ^0.8.0;
4
4
  /// @custom:member local The local address.
5
5
  /// @custom:member remote The remote address.
6
6
  /// @custom:member remoteChainId The chain ID of the remote address.
7
- // forge-lint: disable-next-line(pascal-case-struct)
8
7
  struct JBSuckersPair {
9
8
  address local;
10
9
  bytes32 remote;
@@ -4,7 +4,6 @@ pragma solidity ^0.8.0;
4
4
  /// @custom:member localToken The local token address.
5
5
  /// @custom:member minGas The minimum gas amount to bridge.
6
6
  /// @custom:member remoteToken The remote token address.
7
- // forge-lint: disable-next-line(pascal-case-struct)
8
7
  struct JBTokenMapping {
9
8
  address localToken;
10
9
  uint32 minGas;