@bananapus/suckers-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
@@ -4,82 +4,23 @@
4
4
 
5
5
  | Item | Details |
6
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 |
7
+ | Scope | Cross-chain claim movement, token mapping, fees, and deprecation controls |
8
+ | Control posture | Mixed registry-owner, project-permission, and bridge-specific trust |
9
+ | Highest-risk actions | Wrong token mapping, wrong peer assumptions, and bad emergency or deprecation handling |
10
+ | Recovery posture | Often one-way; many recovery paths are intentionally irreversible |
11
11
 
12
12
  ## Purpose
13
13
 
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.
14
+ This repo controls the shared lifecycle around bridging project positions, not just the transport call itself.
15
15
 
16
16
  ## Control Model
17
17
 
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.
22
-
23
- ## Roles
24
-
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 |
31
-
32
- ## Privileged Surfaces
33
-
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 |
42
-
43
- ## Immutable And One-Way
44
-
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.
49
-
50
- ## Operational Notes
51
-
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.
57
-
58
- ## Machine Notes
59
-
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.
18
+ - registry owner controls shared fee settings and deployer allowlists
19
+ - project-level permissions control token mapping and safety paths
20
+ - bridge-specific implementations inherit external trust assumptions
64
21
 
65
22
  ## Recovery
66
23
 
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.
71
-
72
- ## Admin Boundaries
73
-
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.
78
-
79
- ## Source Map
24
+ - emergency hatch and deprecation are the main recovery tools
25
+ - both are intentionally conservative and often one-way
80
26
 
81
- - `src/JBSucker.sol`
82
- - `src/JBSuckerRegistry.sol`
83
- - `src/deployers/`
84
- - `src/utils/MerkleLib.sol`
85
- - `test/`
package/ARCHITECTURE.md CHANGED
@@ -2,89 +2,33 @@
2
2
 
3
3
  ## Purpose
4
4
 
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.
5
+ `nana-suckers-v6` bridges Juicebox project positions across chains by turning local burns into claimable remote mints.
6
6
 
7
7
  ## System Overview
8
8
 
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.
9
+ `JBSucker` handles prepare, relay, claim, token mapping, deprecation, and emergency exits. `JBSuckerRegistry` tracks deployments, deployer allowlists, and shared fee settings. Bridge-specific implementations handle transport details.
10
10
 
11
11
  ## Core Invariants
12
12
 
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.
19
-
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 |
13
+ - Merkle trees stay append-only
14
+ - nonce progression stays monotonic
15
+ - token mapping stays coherent across peers
16
+ - claims and emergency exits do not double-spend
17
+ - outbox balance accounting stays consistent through send and recovery flows
29
18
 
30
19
  ## Trust Boundaries
31
20
 
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
39
-
40
- ```text
41
- holder prepares a bridge
42
- -> sucker cashes out or consumes the local project-token position
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
46
- -> claimant proves inclusion against the remote inbox tree
47
- -> remote sucker releases or remints destination-side value
48
- ```
49
-
50
- ## Accounting Model
51
-
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.
53
-
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.
21
+ - shared logic lives in `JBSucker`
22
+ - transport security lives in the bridge-specific implementation and external bridge counterparties
23
+ - registry decisions can widen or constrain the allowed deployment surface
55
24
 
56
25
  ## Security Model
57
26
 
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.
63
-
64
- ## Safe Change Guide
65
-
66
- - Review every cross-chain change from both sides of the pair.
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`
27
+ - the biggest risks are non-atomic cross-chain state, bad token mapping, and broken peer assumptions
28
+ - bridge liveness and correct peer identity are real trust assumptions
81
29
 
82
30
  ## Source Map
83
31
 
84
32
  - `src/JBSucker.sol`
85
33
  - `src/JBSuckerRegistry.sol`
86
- - `src/deployers/`
87
34
  - `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`
@@ -1,110 +1,30 @@
1
1
  # Audit Instructions
2
2
 
3
- This repo bridges Juicebox project tokens and associated terminal assets across chains. Audit it as a conservation and replay-prevention system.
3
+ Audit this repo as cross-chain claim and recovery logic, not as a generic ERC-20 bridge.
4
4
 
5
5
  ## Audit Objective
6
6
 
7
7
  Find issues that:
8
- - allow double claim, replay, or claim on the wrong destination
9
- - lose or strand bridged backing assets
10
- - let deprecated or emergency paths violate intended safety rules
11
- - mis-handle root ordering, especially across asynchronous bridge transports
12
- - grant mapping or safety privileges more broadly than intended
8
+
9
+ - break Merkle-root or nonce progression
10
+ - allow bad token mapping or peer assumptions
11
+ - permit double-claim or bad emergency exit behavior
12
+ - make non-atomic bridge semantics unsafe
13
13
 
14
14
  ## Scope
15
15
 
16
16
  In scope:
17
- - all Solidity under `src/`
18
- - deployer contracts under `src/deployers/`
17
+
18
+ - `src/JBSucker.sol`
19
+ - `src/JBSuckerRegistry.sol`
20
+ - bridge-specific implementations and deployers
19
21
  - `src/utils/MerkleLib.sol`
20
- - libraries, enums, interfaces, and structs under `src/`
21
- - deployment scripts in `script/`
22
22
 
23
23
  ## Start Here
24
24
 
25
- Read in this order:
26
- - the shared flow in `JBSucker`
27
- - claim validation and execution tracking
28
- - token mapping and emergency-hatch logic
29
- - one native bridge implementation
30
- - `JBCCIPSucker`
31
- - deployers and registry assumptions
32
-
33
- That order gets you from the shared conservation model to the transport-specific deviations.
34
-
35
- ## Security Model
36
-
37
- The bridge flow is:
38
- - burn or prepare project-token value on source chain
39
- - record a leaf into an outbox tree
40
- - send a merkle root and backing assets over a chain-specific transport
41
- - receive the root on the remote chain
42
- - claim by proving inclusion against the current inbox root
43
-
44
- This repo supports multiple transport implementations:
45
- - OP Stack variants
46
- - Arbitrum
47
- - CCIP
48
- - related deployers and registries
49
-
50
- One non-obvious property to audit explicitly:
51
- - roots and assets do not always arrive in a perfectly ordered, synchronous way
52
- - the system is intentionally designed to survive some transport mismatch without deadlocking
53
- - those recovery choices are exactly where conservation bugs tend to hide
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
-
70
- ## Critical Invariants
71
-
72
- 1. Cross-chain conservation
73
- For any prepared transfer, destination claimable value must not exceed what the source side actually prepared and backed.
74
-
75
- 2. Single execution
76
- Each bridged leaf must be claimable at most once on the destination and at most once via emergency exit.
77
-
78
- 3. Peer authenticity
79
- Only the intended remote peer and messenger path may update inbox roots.
80
-
81
- 4. Deprecation safety
82
- Deprecation and emergency-hatch controls must not let callers bypass intended restrictions or steal in-flight funds.
83
-
84
- 5. Token mapping integrity
85
- Remote token mappings must be immutable or mutable only exactly where the design allows.
86
-
87
- 6. Nonce progression is monotonic in the way each transport expects
88
- Later roots must not silently invalidate earlier user claims unless the protocol explicitly intends that recovery path.
89
-
90
- ## Attack Surfaces
91
-
92
- - `prepare`, `toRemote`, `fromRemote`, and `claim`
93
- - bitmap execution tracking
94
- - root and nonce handling
95
- - token mapping and registry trust
96
- - chain-specific messenger authentication
97
- - deployer address derivation and clone setup
98
-
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
104
-
105
- ## Accepted Risks Or Behaviors
106
-
107
- - Out-of-order arrival is part of the intended model, not an edge case.
25
+ 1. `src/JBSucker.sol`
26
+ 2. `src/JBSuckerRegistry.sol`
27
+ 3. the relevant bridge-specific implementation
108
28
 
109
29
  ## Verification
110
30
 
package/README.md CHANGED
@@ -24,7 +24,7 @@ The base implementation is extended for multiple bridge families so the same pro
24
24
 
25
25
  Use this repo when the requirement is canonical project-token movement across chains. Do not use it if the project is single-chain or if the bridge assumptions for the target networks are unacceptable.
26
26
 
27
- The main idea is not "bridge the token contract." The main idea is "bridge a Juicebox cash-out claim plus enough information to recreate the project-token position on the remote chain."
27
+ The main idea is not "bridge the token contract." The main idea is "bridge a Juicebox claim plus enough information to recreate the project-token position on the remote chain."
28
28
 
29
29
  ## Key Contracts
30
30
 
@@ -32,11 +32,7 @@ The main idea is not "bridge the token contract." The main idea is "bridge a Jui
32
32
  | --- | --- |
33
33
  | `JBSucker` | Base bridge logic for prepare, relay, claim, token mapping, and lifecycle controls. |
34
34
  | `JBSuckerRegistry` | Registry for per-project sucker deployments, deployer allowlists, and shared bridge fee settings. |
35
- | `JBOptimismSucker` | OP Stack bridge implementation. |
36
- | `JBBaseSucker` | Base-flavored OP Stack implementation. |
37
- | `JBCeloSucker` | OP Stack implementation adapted for Celo's native asset behavior. |
38
- | `JBArbitrumSucker` | Arbitrum bridge implementation. |
39
- | `JBCCIPSucker` | Chainlink CCIP-based implementation for CCIP-connected chains. |
35
+ | Chain-specific suckers | Transport-specific implementations for OP Stack, Arbitrum, CCIP, and related environments. |
40
36
 
41
37
  ## Mental Model
42
38
 
@@ -50,28 +46,6 @@ That means every bridge path has two trust surfaces:
50
46
  - the shared sucker accounting and Merkle logic
51
47
  - the bridge-specific transport implementation
52
48
 
53
- The shortest useful reading order is:
54
-
55
- | Contract | Description |
56
- |----------|-------------|
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. |
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). |
59
- | [`JBOptimismSucker`](src/JBOptimismSucker.sol) | Extends `JBSucker`. Bridges via OP Standard Bridge + OP Messenger. No `msg.value` required for transport. |
60
- | [`JBBaseSucker`](src/JBBaseSucker.sol) | Thin wrapper around `JBOptimismSucker` with Base chain IDs (Ethereum 1 <-> Base 8453, Sepolia 11155111 <-> Base Sepolia 84532). |
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). |
62
- | [`JBArbitrumSucker`](src/JBArbitrumSucker.sol) | Extends `JBSucker`. Bridges via Arbitrum Inbox + Gateway Router. Uses `unsafeCreateRetryableTicket` for L1->L2 (to avoid address aliasing of refund address) and `ArbSys.sendTxToL1` for L2->L1. Requires `msg.value` for L1->L2 transport payment. |
63
- | [`JBSuckerRegistry`](src/JBSuckerRegistry.sol) | Tracks all suckers per project. Manages deployer allowlist (owner-only). Entry point for `deploySuckersFor`. Can remove deprecated suckers via `removeDeprecatedSucker`. Owns the global `toRemoteFee` (ETH fee in wei, capped at `MAX_TO_REMOTE_FEE` = 0.001 ether), adjustable by the registry owner via `setToRemoteFee()`. All sucker clones read this fee from the registry. Existing-project deployments are deploy-and-map operations, so the registry also needs to be arranged as an authorized `MAP_SUCKER_TOKEN` operator for those projects. |
64
- | [`JBSuckerDeployer`](src/JBSuckerDeployer.sol) | Abstract base deployer. Clones a singleton sucker via `LibClone.cloneDeterministic` and initializes it. Two-phase setup: `setChainSpecificConstants` then `configureSingleton`. |
65
- | [`JBCCIPSuckerDeployer`](src/deployers/JBCCIPSuckerDeployer.sol) | Deployer for `JBCCIPSucker`. Stores CCIP router, remote chain ID, and CCIP chain selector. |
66
- | [`JBOptimismSuckerDeployer`](src/deployers/JBOptimismSuckerDeployer.sol) | Deployer for `JBOptimismSucker`. Stores OP Messenger and OP Bridge addresses. |
67
- | [`JBBaseSuckerDeployer`](src/deployers/JBBaseSuckerDeployer.sol) | Thin wrapper around `JBOptimismSuckerDeployer` for Base. |
68
- | [`JBCeloSuckerDeployer`](src/deployers/JBCeloSuckerDeployer.sol) | Deployer for `JBCeloSucker`. Extends `JBOptimismSuckerDeployer` with `wrappedNative` (`IWrappedNativeToken`) storage for the local chain's WETH address. |
69
- | [`JBArbitrumSuckerDeployer`](src/deployers/JBArbitrumSuckerDeployer.sol) | Deployer for `JBArbitrumSucker`. Stores Arbitrum Inbox, Gateway Router, and layer (`JBLayer.L1` or `JBLayer.L2`). |
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()`. |
71
- | [`CCIPHelper`](src/libraries/CCIPHelper.sol) | CCIP router addresses, chain selectors, and WETH addresses for the chain set currently encoded in this repo. |
72
- | [`ARBAddresses`](src/libraries/ARBAddresses.sol) | Arbitrum bridge contract addresses (Inbox, Gateway Router) for mainnet and Sepolia. |
73
- | [`ARBChains`](src/libraries/ARBChains.sol) | Arbitrum chain ID constants. |
74
-
75
49
  ## Read These Files First
76
50
 
77
51
  1. `src/JBSucker.sol`
@@ -82,18 +56,16 @@ The shortest useful reading order is:
82
56
 
83
57
  ## Integration Traps
84
58
 
85
- - do not reason about suckers as if they were generic ERC-20 bridges; they are project-token plus treasury-state bridges
86
- - root ordering and message delivery semantics matter as much as the claim proof format
87
- - token mapping is part of the economic invariant, not just a convenience config
88
- - emergency and deprecation paths are not edge tooling; they are part of normal operational safety
59
+ - do not reason about suckers as if they were generic ERC-20 bridges
60
+ - root ordering and message delivery semantics matter as much as proof format
61
+ - token mapping is part of the economic invariant
62
+ - emergency and deprecation paths are part of normal operational safety
89
63
 
90
64
  ## Where State Lives
91
65
 
92
- - per-claim and tree progression state live in the sucker pair itself
93
- - deployment inventory and shared operational config live in `JBSuckerRegistry`
94
- - bridge transport assumptions live in the chain-specific implementation and its external counterparties
95
-
96
- When reviewing a bridge incident, check local state transition correctness before blaming the transport layer.
66
+ - per-claim and tree progression state: the sucker pair
67
+ - deployment inventory and shared operational config: `JBSuckerRegistry`
68
+ - bridge transport assumptions: the chain-specific implementation and its external counterparties
97
69
 
98
70
  ## High-Signal Tests
99
71
 
@@ -149,15 +121,13 @@ script/
149
121
 
150
122
  ## Risks And Notes
151
123
 
152
- - out-of-order root delivery can make some claims unclaimable until an operator uses an emergency path
124
+ - out-of-order root delivery can make some claims unavailable until an operator uses an emergency path
153
125
  - bridge-specific transport assumptions matter as much as the shared sucker logic
154
126
  - token mapping and deprecation controls are governance-sensitive surfaces
155
127
  - a bridge that stays live operationally still may not be economically safe for every asset or chain pair
156
128
 
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
129
  ## For AI Agents
160
130
 
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.
131
+ - Do not summarize this repo as a generic token bridge.
132
+ - Always separate shared sucker logic from bridge-specific transport behavior.
133
+ - Use the chain-specific implementation and matching deployer together when answering operational questions.
package/RISKS.md CHANGED
@@ -24,6 +24,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
24
24
  - **CCIP:** trusts `CCIP_ROUTER` identity plus `any2EvmMessage.sender` and `sourceChainSelector`. The router address is immutable at deploy time -- if Chainlink rotates routers, the sucker is bricked (no upgrade path).
25
25
  - **CREATE2 peer assumption.** `peer()` defaults to `address(this)`, assuming deterministic cross-chain deployment. Breaks if deployer address, init code, or factory nonce differs across chains. Incorrect peer = permanent fund loss (messages accepted from nobody, or routed to wrong address).
26
26
  - **Controller/terminal must exist on destination chain.** `_handleClaim` calls `controllerOf(projectId).mintTokensOf()`. If the project does not exist or has no controller on the remote chain, all claims permanently revert -- funds are stuck.
27
+ - **Registry allowlisting does not verify deployer singleton provenance.** `JBSuckerRegistry.deploySuckersFor()` only checks the deployer allowlist, not the singleton implementation behind the deployer. `configureSingleton()` can point an approved deployer at any JBSucker singleton. This is a privileged-only concern (both deployer configuration and registry allowlisting require governance). Defense: validate deployer configuration during registry allowlist reviews.
27
28
  - **No reentrancy guard.** The contract relies on state ordering (mark-executed-before-external-call) rather than explicit ReentrancyGuard. Correct today, but fragile to future refactors.
28
29
 
29
30
  ## 2. Merkle Tree Risks
@@ -41,6 +42,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
41
42
  - **CCIP: no guaranteed delivery order.** CCIP does not guarantee in-order delivery. The contract handles this by accepting any nonce > current, but concurrent `toRemote` calls for the same token could result in a later root arriving first, skipping an intermediate root.
42
43
  - **OP Stack: generally ordered** but the L1-to-L2 message must be relayed by an off-chain actor. A delayed or dropped relay permanently blocks claims until someone retries the relay.
43
44
  - **Message loss.** If an AMB silently fails (accepts the message on L1 but never delivers to L2), `numberOfClaimsSent` is incremented but the remote peer never receives the root. Those leaves are blocked from both remote claim and local emergency exit (conservative: locked, not double-spent). Recovery requires enabling the emergency hatch.
45
+ - **Aggregate balance accounting.** `amountToAddToBalanceOf(token)` computes `balanceOf(this) - outbox.balance`, making all contract-held tokens fungible claim backing. This is intentional: all funds serve the same project, so refunded ETH (from failed Arbitrum retryable tickets) and stale native deliveries correctly become project-claimable. The tradeoff is that later roots can consume liquidity from earlier failed batches, but the project is the ultimate beneficiary in all cases.
44
46
  - **`fromRemote` does not revert on stale nonce.** By design, stale/duplicate messages are silently ignored (emitting `StaleRootRejected`). This prevents fund loss on native token transfers where reverting would lose the ETH, but means monitoring must watch for this event to detect bridge issues.
45
47
 
46
48
  ## 4. Token Mapping Risks
@@ -62,7 +64,7 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
62
64
  - **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
65
  - **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
66
  - **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.
67
+ - **Registry aggregate views fail open.** `JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` include both active and deprecated suckers with per-chain deduplication (max per chain, not sum). When multiple suckers target the same peer chain (e.g., during migration), the highest reported value is used. The views 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, or simply expensive to query.
66
68
 
67
69
  ## 6. Deprecation Lifecycle
68
70
 
@@ -83,6 +85,8 @@ This file focuses on the bridge-like risks in the sucker system: merkle-root pro
83
85
  - **Claim vs emergency exit use separate bitmap slots.** Emergency exit uses `_executedFor[keccak256(abi.encode(terminalToken))]` while regular claims use `_executedFor[terminalToken]`. This means a leaf that was emergency-exited locally could theoretically also be claimed remotely if the root was already sent -- double-spend is prevented only by the `numberOfClaimsSent` check.
84
86
  - **`numberOfClaimsSent` is the critical guard.** Emergency exit reverts if `outbox.numberOfClaimsSent != 0 && outbox.numberOfClaimsSent - 1 >= index`. The `numberOfClaimsSent != 0` precondition prevents underflow when no root has ever been sent — in that case, all leaves are available for emergency exit. This means leaves at indices below `numberOfClaimsSent` cannot be emergency-exited (they may have been sent to the remote peer). If `_sendRootOverAMB` silently fails, these leaves are permanently locked.
85
87
  - **Emergency exit decrements `outbox.balance`.** If emergency exits drain the outbox balance below the amount that was already sent to the bridge, the accounting becomes inconsistent. The contract guards against this by only allowing exit for unsent leaves.
88
+ - **Emergency exit recipient is the leaf beneficiary.** `exitThroughEmergencyHatch()` refunds to `claimData.leaf.beneficiary`, not the original `prepare()` caller. The depositor chose this beneficiary when preparing the bridge; the leaf structure does not store the depositor address. If Alice prepares for Bob and the bridge fails, Bob gets the emergency refund, not Alice. This is the intended behavior: the depositor delegated their claim to the beneficiary.
89
+ - **`numberOfClaimsSent` advancement timing.** `_sendRoot()` sets `numberOfClaimsSent` before `_sendRootOverAMB()` completes. If the L1 transaction succeeds but L2 delivery fails, those leaves are blocked from emergency exit. Mitigations: Arbitrum retryable tickets can be manually re-executed on L2; Optimism messages can be re-relayed by anyone. If delivery permanently fails, `enableEmergencyHatchFor()` combined with project owner intervention can recover. Adding a rollback mechanism would introduce double-spend risk (leaf claimable on both chains). Current design is conservative: locked funds are preferable to double-spent funds.
86
90
  - **Emergency hatch + minting.** Emergency exit calls `_handleClaim`, which mints project tokens via the controller. If the controller or token contract is broken/missing, emergency exits also revert -- there is no "raw withdrawal" of terminal tokens without minting.
87
91
 
88
92
  ## 8. DoS Vectors
@@ -130,7 +134,11 @@ The fee is paid to `FEE_PROJECT_ID` (the protocol project), not to the sucker's
130
134
 
131
135
  ### 10.5 Registry aggregate views prioritize liveness over completeness
132
136
 
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.
137
+ `JBSuckerRegistry.remoteBalanceOf`, `remoteSurplusOf`, and `remoteTotalSupplyOf` intentionally use `try/catch` around each sucker and silently ignore peers that revert. Both active and deprecated suckers are included, with per-chain deduplication: when multiple suckers target the same peer chain (e.g., during a migration window), the highest reported value is used to avoid double-counting. 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.
138
+
139
+ ### 10.8 Zero-output swap batches route to pendingSwapOf
140
+
141
+ When `JBSwapCCIPSucker.ccipReceive` receives bridge tokens and the swap succeeds but returns zero local tokens (e.g., due to extreme price impact or dust amounts), the batch is routed to `pendingSwapOf` for later retry via `retrySwap`. Without this, claims would proceed with zero terminal backing, minting unbacked project tokens. The trade-off is that these batches require a manual `retrySwap` call once pool conditions improve. Anyone can call `retrySwap` — it is permissionless.
134
142
 
135
143
  ### 10.6 Hookless V4 spot pricing is sandwich-vulnerable by design
136
144
 
@@ -139,3 +147,7 @@ When the only available Uniswap pool for a cross-denomination swap is a hookless
139
147
  ### 10.7 `mapTokens` refunds ETH on enable-only batches
140
148
 
141
149
  `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.
150
+
151
+ ### 10.9 Zero-value `prepare()` is allowed
152
+
153
+ `prepare()` does not reject `projectTokenCount == 0`. A zero-value check would be trivially bypassed by passing `1` instead, so it provides no real protection against remap-window consumption. The cost to create a leaf with `projectTokenCount = 1` is negligible (1 wei of project tokens). The one-time remap window is protected by the token mapping's `enabled` flag and the outbox tree count, not by minimum deposit requirements.
package/SKILLS.md CHANGED
@@ -2,49 +2,24 @@
2
2
 
3
3
  ## Use This File For
4
4
 
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 decide whether the issue is in shared accounting, message authentication, token mapping, or operator/deprecation controls. Those concerns live in different layers.
5
+ - Use this file when the task involves cross-chain project-token movement, Merkle-root progression, token mapping, or emergency and deprecation flows.
6
+ - Start here, then decide whether the issue is in shared sucker logic or in a bridge-specific transport implementation.
7
7
 
8
8
  ## Read This Next
9
9
 
10
10
  | If you need... | Open this next |
11
11
  |---|---|
12
- | Repo overview and bridge model | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
12
+ | Repo overview and architecture | [`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/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/) |
16
- | Merkle and helper logic | [`src/utils/`](./src/utils/), [`src/libraries/`](./src/libraries/) |
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) |
19
-
20
- ## Repo Map
21
-
22
- | Area | Where to look |
23
- |---|---|
24
- | Base contracts | [`src/JBSucker.sol`](./src/JBSucker.sol), [`src/JBSuckerRegistry.sol`](./src/JBSuckerRegistry.sol) |
25
- | Chain-specific implementations and deployers | [`src/`](./src/), [`src/deployers/`](./src/deployers/) |
26
- | Libraries, utils, and types | [`src/libraries/`](./src/libraries/), [`src/utils/`](./src/utils/), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/), [`src/enums/`](./src/enums/) |
27
- | Scripts | [`script/`](./script/) |
28
- | Tests | [`test/`](./test/) |
14
+ | Merkle logic | [`src/utils/MerkleLib.sol`](./src/utils/MerkleLib.sol) |
15
+ | Bridge-specific behavior | the matching implementation and deployer under [`src/`](./src/) and [`src/deployers/`](./src/deployers/) |
29
16
 
30
17
  ## Purpose
31
18
 
32
- Cross-chain bridge layer for Juicebox project tokens and the terminal assets that back them. Suckers package local burn or claim state into Merkle roots, relay those roots across bridge transports, and let users recreate the position on the remote chain.
33
-
34
- ## Reference Files
35
-
36
- - Open [`references/runtime.md`](./references/runtime.md) when you need the base claim flow, registry role, token mapping model, or the main bridge invariants.
37
- - Open [`references/operations.md`](./references/operations.md) when you need deployer and transport-selection guidance, deprecation and emergency behavior, or the common stale-data traps around bridge configuration.
19
+ Canonical cross-chain movement layer for Juicebox project positions.
38
20
 
39
21
  ## Working Rules
40
22
 
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.
46
- - Treat token mapping, root progression, and emergency/deprecation controls as first-class runtime behavior, not admin-only side tooling.
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.
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.
23
+ - Start in `JBSucker` for shared lifecycle logic.
24
+ - Separate Merkle bookkeeping from bridge-specific transport assumptions.
25
+ - Treat token mapping, deprecation, and emergency hatch behavior as core safety surfaces.