@bananapus/suckers-v6 0.0.20 → 0.0.22

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
@@ -2,6 +2,35 @@
2
2
 
3
3
  Admin privileges and their scope in nana-suckers-v6.
4
4
 
5
+ ## At A Glance
6
+
7
+ | 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.
21
+
22
+ ## One-Way Or High-Risk Actions
23
+
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.
28
+
29
+ ## Recovery Notes
30
+
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.
33
+
5
34
  ## Roles
6
35
 
7
36
  ### Registry Owner
@@ -28,6 +57,8 @@ Admin privileges and their scope in nana-suckers-v6.
28
57
  - **Scope:** Can map or disable local-to-remote token pairs on a specific sucker.
29
58
  - **Permission ID:** `JBPermissionIds.MAP_SUCKER_TOKEN`
30
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
+
31
62
  ### Safety Admin (delegated role)
32
63
 
33
64
  - **How assigned:** Granted `SUCKER_SAFETY` permission by the project owner via `JBPermissions`.
@@ -56,17 +87,17 @@ Admin privileges and their scope in nana-suckers-v6.
56
87
  | `allowSuckerDeployers(deployers)` | Registry Owner | N/A (`onlyOwner`) | Global | Batch version: adds multiple deployer contracts to the allowlist. |
57
88
  | `removeSuckerDeployer(deployer)` | Registry Owner | N/A (`onlyOwner`) | Global | Removes a deployer contract from the allowlist, preventing future sucker deployments through it. |
58
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`. |
59
- | `deploySuckersFor(projectId, salt, configs)` | Project Owner | `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. |
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. |
60
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. |
61
92
 
62
93
  ### JBSucker
63
94
 
64
95
  | Function | Required Role | Permission ID | Scope | What It Does |
65
96
  |----------|--------------|---------------|-------|--------------|
66
- | `mapToken(map)` | Project Owner | `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. |
67
- | `mapTokens(maps)` | Project Owner | `MAP_SUCKER_TOKEN` | Per-sucker | Batch version: maps multiple local-to-remote token pairs. Each mapping requires the same permission. |
68
- | `enableEmergencyHatchFor(tokens)` | Project Owner | `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. |
69
- | `setDeprecation(timestamp)` | Project Owner | `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. |
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. |
70
101
 
71
102
  ### JBSuckerDeployer (base and all subclasses)
72
103
 
package/ARCHITECTURE.md CHANGED
@@ -1,121 +1,60 @@
1
- # nana-suckers-v6 — Architecture
1
+ # Architecture
2
2
 
3
3
  ## Purpose
4
4
 
5
- Cross-chain token bridging for Juicebox V6. Allows project tokens and funds to move between EVM chains via Optimism, Base, Celo (OP Stack), Arbitrum, and Chainlink CCIP bridges. Uses dual merkle trees (outbox/inbox) for claim verification.
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.
6
6
 
7
- ## Contract Map
7
+ ## Boundaries
8
8
 
9
- ```
10
- src/
11
- ├── JBSucker.sol — Abstract base: merkle tree management, prepare/claim logic, FEE_PROJECT_ID, reads toRemoteFee from REGISTRY (immutable IJBSuckerRegistry)
12
- ├── JBOptimismSucker.sol — OP Stack bridge implementation (Optimism, Base, Celo)
13
- ├── JBBaseSucker.sol — Base chain sucker (extends JBOptimismSucker, overrides peerChainId for Base ↔ Ethereum)
14
- ├── JBCeloSucker.sol — Celo sucker (extends JBOptimismSucker, wraps ETH → WETH for bridging, custom gas token handling)
15
- ├── JBArbitrumSucker.sol — Arbitrum bridge implementation
16
- ├── JBCCIPSucker.sol — Chainlink CCIP bridge implementation
17
- ├── JBSuckerRegistry.sol — Registry of suckers per project, deployment permissions, centralized toRemoteFee (owner-controlled, applies to all suckers)
18
- ├── deployers/
19
- │ ├── JBSuckerDeployer.sol — Abstract base deployer (LibClone, singleton pattern)
20
- │ ├── JBOptimismSuckerDeployer.sol
21
- │ ├── JBBaseSuckerDeployer.sol
22
- │ ├── JBCeloSuckerDeployer.sol
23
- │ ├── JBArbitrumSuckerDeployer.sol
24
- │ └── JBCCIPSuckerDeployer.sol
25
- ├── enums/
26
- │ ├── JBSuckerState.sol — ENABLED → DEPRECATION_PENDING → SENDING_DISABLED → DEPRECATED
27
- │ └── JBLayer.sol — L1 / L2 indicator (used by JBArbitrumSucker)
28
- ├── interfaces/ — IJBSucker, IJBSuckerRegistry, IJBSuckerDeployer, bridge-specific interfaces (OP, Arb, CCIP)
29
- ├── libraries/
30
- │ ├── ARBAddresses.sol — Arbitrum precompile/contract addresses
31
- │ ├── ARBChains.sol — Arbitrum chain ID constants
32
- │ └── CCIPHelper.sol — CCIP chain selector lookups
33
- ├── structs/ — JBClaim, JBLeaf, JBTokenMapping, JBRemoteToken, JBOutboxTree, JBMessageRoot, etc.
34
- └── utils/
35
- └── MerkleLib.sol — Append-only merkle tree operations
36
- ```
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.
37
13
 
38
- ## Key Data Flows
14
+ ## Main Components
39
15
 
40
- ### Outbound (Prepare + Bridge)
41
- ```
42
- User JBSucker.prepare()
43
- Cash out project tokens (0% tax via sucker privilege)
44
- Insert {beneficiary, token, amount} into outbox merkle tree
45
-
46
- User JBSucker.toRemote{value}(token)
47
- → Pay toRemoteFee (ETH) to fee project via terminal.pay() — caller gets fee project tokens
48
- → Remaining msg.value forwarded as transport payment to the bridge
49
- → Send merkle root + bridged tokens to remote sucker via OP/Arb/CCIP messenger
50
- ```
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 |
51
23
 
52
- The `toRemoteFee` is a global ETH fee (max 0.001 ETH) set by the registry owner via `setToRemoteFee()`. The caller must send at least `toRemoteFee` as `msg.value` (reverts otherwise). The fee is paid into `FEE_PROJECT_ID` (typically project 1) through the project's primary native-token terminal. If the `pay()` call reverts or no terminal exists, the fee is silently added to the transport payment instead (best-effort).
24
+ ## Runtime Model
53
25
 
54
- ### Inbound (Claim)
55
- ```
56
- Remote root arrives stored in inbox tree
57
- User JBSucker.claim(proof)
58
- Check leaf not already claimed (bitmap), mark as executed
59
- Verify merkle proof against inbox root
60
- Add bridged terminal tokens to project balance (via terminal.addToBalanceOf)
61
- → Mint project tokens to beneficiary (via controller.mintTokensOf)
26
+ ```text
27
+ holder prepares a bridge
28
+ -> 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
31
+ -> claimant proves inclusion against the remote inbox tree
32
+ -> remote sucker releases or remints the destination-side value
62
33
  ```
63
34
 
64
- ### Deprecation Lifecycle
65
- ```
66
- ENABLED → DEPRECATION_PENDING → SENDING_DISABLED → DEPRECATED
67
- (owner) (pending period) (no new outbox) (fully disabled)
68
- ```
69
-
70
- ## Token Mapping
71
-
72
- Token mappings link a local terminal token to a remote token address, enabling bridging for that pair. Mappings are managed via `mapToken()` / `mapTokens()` (requires `MAP_SUCKER_TOKEN` permission).
73
-
74
- **Immutability constraint:** 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 by mapping to `address(0)`. This prevents double-spending: if remapping were allowed after outbox activity, the same local funds could be claimed against two different remote tokens on the remote chain.
75
-
76
- A disabled mapping can be re-enabled to the *same* remote token. A misconfigured mapping requires deploying a new sucker.
35
+ ## Critical Invariants
77
36
 
78
- When a mapping is disabled (set to `address(0)`) and unsent leaves remain in the outbox, the sucker automatically flushes the remaining root to the remote chain to settle outstanding claims.
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.
79
41
 
80
- ## Emergency Hatch
42
+ ## Where Complexity Lives
81
43
 
82
- The emergency hatch allows users to reclaim their tokens on the chain they deposited on when the bridge is no longer functional.
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.
83
47
 
84
- **Who can enable it:** The project owner (or an address with the `SUCKER_SAFETY` permission) calls `enableEmergencyHatchFor(address[] tokens)`.
85
-
86
- **What it does:** Sets `emergencyHatch = true` and `enabled = false` for each token's remote mapping. This is irreversible — once the emergency hatch is enabled for a token, it cannot be disabled or remapped.
87
-
88
- **How users exit:** Users call `exitThroughEmergencyHatch(JBClaim claimData)` with a merkle proof against the *outbox* tree. The sucker validates:
89
- 1. The emergency hatch is enabled for that token (or the sucker is in `DEPRECATED` / `SENDING_DISABLED` state).
90
- 2. The leaf has not already been sent to the remote chain (`index >= numberOfClaimsSent`). Leaves whose roots were already bridged cannot be emergency-exited, since they could also be claimed remotely.
91
- 3. The leaf has not already been claimed (bitmap check).
92
-
93
- The sucker then adds the terminal tokens back to the project's balance (via `terminal.addToBalanceOf`) and mints project tokens to the beneficiary on the local chain.
94
-
95
- ## Extension Points
96
-
97
- | Point | Interface | Purpose |
98
- |-------|-----------|---------|
99
- | Bridge transport | Chain-specific sucker | OP, Arb, CCIP implementations |
100
- | Sucker deployer | `IJBSuckerDeployer` | Factory for new suckers |
101
- | Registry | `IJBSuckerRegistry` | Discovery and access control |
102
-
103
- ## Design Decisions
104
-
105
- **Dual merkle trees instead of direct bridging.** Each sucker maintains separate outbox and inbox merkle trees per token. Outbound transfers are batched into the outbox tree via `prepare()`, and a single `toRemote()` call bridges the root and accumulated funds together. This amortizes bridge costs across many users — only one cross-chain message per batch rather than one per transfer. The inbox tree on the receiving side lets users self-serve claims with merkle proofs at their own pace.
106
-
107
- **Bitmap for claim tracking.** Each leaf index is tracked in an OpenZeppelin `BitMaps.BitMap` (`_executedFor`), which packs 256 booleans per storage slot. This is far cheaper than a `mapping(uint256 => bool)` for dense sequential indices, and the merkle tree's sequential leaf indices are a natural fit.
108
-
109
- **Immutable token mappings once outbox has entries.** After a token mapping has been used (outbox tree count > 0), remapping to a different remote token is blocked. Without this, an attacker could prepare tokens mapped to token A, then remap to token B before `toRemote()` is called, allowing the same local funds to be claimed against both tokens on the remote chain. Disabling (mapping to `address(0)`) is still allowed since it flushes remaining outbox entries and stops new ones.
48
+ ## Dependencies
110
49
 
111
- **`uint128` amount cap for SVM compatibility.** Both `terminalTokenAmount` and `projectTokenCount` are capped at `type(uint128).max` in `_insertIntoTree()`. This ensures the leaf data is compatible with Solana's SVM, where token amounts are `u64`/`u128`. The `bytes32` beneficiary field similarly accommodates 32-byte Solana public keys alongside 20-byte EVM addresses.
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
112
53
 
113
- **Best-effort fee payment.** The `toRemoteFee` payment to the fee project is wrapped in a try-catch. If the fee project's terminal doesn't exist or the `pay()` call reverts, the bridge proceeds without the fee. This prevents a misconfigured or paused fee project from blocking all cross-chain transfers.
54
+ ## Safe Change Guide
114
55
 
115
- ## Dependencies
116
- - `@bananapus/core-v6` Terminal, controller, token interfaces
117
- - `@bananapus/permission-ids-v6` DEPLOY_SUCKERS, MAP_SUCKER_TOKEN, etc.
118
- - `@arbitrum/nitro-contracts` Arbitrum bridge interfaces (IInbox, IOutbox, IBridge, ArbSys, AddressAliasHelper)
119
- - `@chainlink/contracts-ccip` CCIP router and message interfaces
120
- - `@openzeppelin/contracts` — BitMaps, SafeERC20, ERC165, Initializable, ERC2771Context, Ownable, EnumerableMap
121
- - `solady` — LibClone (deterministic clone deployment for sucker deployers)
56
+ - 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.