@bananapus/suckers-v6 0.0.71 → 0.0.73

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/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  - [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — scope, entrypoints, and reading order for security review.
14
14
  - [SKILLS.md](./SKILLS.md) — reusable patterns and gotchas distilled from this codebase.
15
15
  - [STYLE_GUIDE.md](./STYLE_GUIDE.md) — Solidity conventions used across this repo.
16
- - [CHANGELOG.md](./CHANGELOG.md) versioned change history.
16
+ - [CHANGELOG.md](./CHANGELOG.md) - V5 to V6 migration changelog.
17
17
 
18
18
  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.
19
19
 
@@ -31,7 +31,7 @@ Use this repo when the requirement is canonical project-token movement across ch
31
31
 
32
32
  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."
33
33
 
34
- ## Key Contracts
34
+ ## Key contracts
35
35
 
36
36
  | Contract | Role |
37
37
  | --- | --- |
@@ -39,7 +39,7 @@ The main idea is not "bridge the token contract." The main idea is "bridge a Jui
39
39
  | `JBSuckerRegistry` | Registry for per-project sucker deployments, deployer allowlists, and shared bridge fee settings. |
40
40
  | Chain-specific suckers | Transport-specific implementations for OP Stack, Arbitrum, CCIP, and related environments. |
41
41
 
42
- ## Mental Model
42
+ ## Mental model
43
43
 
44
44
  Each sucker pair has two jobs:
45
45
 
@@ -51,7 +51,7 @@ That means every bridge path has two trust surfaces:
51
51
  - the shared sucker accounting and Merkle logic
52
52
  - the bridge-specific transport implementation
53
53
 
54
- ## Read These Files First
54
+ ## Read these files first
55
55
 
56
56
  1. `src/JBSucker.sol`
57
57
  2. `src/JBSuckerRegistry.sol`
@@ -59,7 +59,7 @@ That means every bridge path has two trust surfaces:
59
59
  4. the matching deployer under `src/deployers/`
60
60
  5. `src/utils/MerkleLib.sol`
61
61
 
62
- ## Integration Traps
62
+ ## Integration traps
63
63
 
64
64
  - do not reason about suckers as if they were generic ERC-20 bridges
65
65
  - root ordering and message delivery semantics matter as much as proof format
@@ -67,13 +67,13 @@ That means every bridge path has two trust surfaces:
67
67
  - peer contexts are merged only when they share both currency and decimals; same-currency contexts with different decimals are kept separate and valued independently at read time, never summed across precisions
68
68
  - emergency and deprecation paths are part of normal operational safety
69
69
 
70
- ## Where State Lives
70
+ ## Where state lives
71
71
 
72
72
  - per-claim and tree progression state: the sucker pair
73
73
  - deployment inventory and shared operational config: `JBSuckerRegistry`
74
74
  - bridge transport assumptions: the chain-specific implementation and its external counterparties
75
75
 
76
- ## High-Signal Tests
76
+ ## High-signal tests
77
77
 
78
78
  1. `test/unit/registry.t.sol`
79
79
  2. `test/unit/multi_chain_evolution.t.sol`
@@ -101,11 +101,11 @@ Useful scripts:
101
101
  - `npm run deploy:testnets`
102
102
  - `npm run analyze`
103
103
 
104
- ## Deployment Notes
104
+ ## Deployment notes
105
105
 
106
106
  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.
107
107
 
108
- ## Repository Layout
108
+ ## Repository layout
109
109
 
110
110
  ```text
111
111
  src/
@@ -125,14 +125,14 @@ script/
125
125
  helpers/
126
126
  ```
127
127
 
128
- ## Risks And Notes
128
+ ## Risks and notes
129
129
 
130
130
  - out-of-order root delivery can make some claims unavailable until an operator uses an emergency path
131
131
  - bridge-specific transport assumptions matter as much as the shared sucker logic
132
132
  - token mapping and deprecation controls are governance-sensitive surfaces
133
133
  - a bridge that stays live operationally still may not be economically safe for every asset or chain pair
134
134
 
135
- ## For AI Agents
135
+ ## For AI agents
136
136
 
137
137
  - Do not summarize this repo as a generic token bridge.
138
138
  - Always separate shared sucker logic from bridge-specific transport behavior.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/suckers-v6",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,7 +11,8 @@
11
11
  "references/",
12
12
  "remappings.txt",
13
13
  "script/",
14
- "src/"
14
+ "src/",
15
+ "!src/archive/"
15
16
  ],
16
17
  "engines": {
17
18
  "node": ">=20.0.0"
@@ -26,18 +27,18 @@
26
27
  },
27
28
  "dependencies": {
28
29
  "@arbitrum/nitro-contracts": "3.2.0",
29
- "@bananapus/core-v6": "^0.0.81",
30
- "@bananapus/permission-ids-v6": "^0.0.28",
30
+ "@bananapus/core-v6": "^0.0.82",
31
+ "@bananapus/permission-ids-v6": "^0.0.30",
31
32
  "@chainlink/contracts-ccip": "1.6.4",
32
- "@chainlink/local": "0.2.7",
33
33
  "@openzeppelin/contracts": "5.6.1",
34
- "@prb/math": "4.1.1",
35
- "@uniswap/v3-core": "github:Uniswap/v3-core#6562c52e8f75f0c10f9deaf44861847585fc8129",
36
- "@uniswap/v3-periphery": "github:Uniswap/v3-periphery#b325bb0905d922ae61fcc7df85ee802e8df5e96c",
34
+ "@prb/math": "4.1.2",
37
35
  "@uniswap/v4-core": "1.0.2",
38
36
  "solady": "0.1.26"
39
37
  },
40
38
  "devDependencies": {
39
+ "@chainlink/local": "0.2.9",
40
+ "@uniswap/v3-core": "1.0.1",
41
+ "@uniswap/v3-periphery": "1.4.4",
41
42
  "@sphinx-labs/plugins": "0.33.3"
42
43
  }
43
44
  }
@@ -1,12 +1,12 @@
1
- # Suckers Operations
1
+ # Suckers operations
2
2
 
3
- ## Configuration Surface
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
6
  - Transport-specific deployers in `src/deployers/` are where chain-specific constants and bridge addresses live.
7
7
  - [`script/Deploy.s.sol`](../script/Deploy.s.sol) is where deployment-time environment wiring belongs.
8
8
 
9
- ## Change Checklist
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.
@@ -15,13 +15,13 @@
15
15
  - If you edit snapshot or claim-boundary logic, verify `numberOfClaimsSent`, peer snapshots, and emergency exit behavior together.
16
16
  - If you touch bridge-specific code, confirm whether the real bug is transport-side or shared accounting-side.
17
17
 
18
- ## Common Failure Modes
18
+ ## Common failure modes
19
19
 
20
20
  - Cross-chain issue is blamed on transport when the root or token mapping was wrong before message delivery.
21
21
  - Registry configuration drifts from what a deployer or external operator expects.
22
22
  - Emergency hatches or deprecation paths are stale because nobody exercises them until stress conditions arrive.
23
23
 
24
- ## Useful Proof Points
24
+ ## Useful proof points
25
25
 
26
26
  - [`test/SuckerAttacks.t.sol`](../test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](../test/SuckerDeepAttacks.t.sol), and [`test/TestRegressionGaps.sol`](../test/TestRegressionGaps.sol) for security-sensitive assumptions.
27
27
  - [`test/InteropCompat.t.sol`](../test/InteropCompat.t.sol) when the problem is deployment wiring rather than runtime logic.
@@ -1,20 +1,20 @@
1
- # Suckers Runtime
1
+ # Suckers runtime
2
2
 
3
- ## Core Roles
3
+ ## Core roles
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
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
8
  - Matching deployers under `src/deployers/` own clone and transport configuration.
9
9
 
10
- ## Runtime Path
10
+ ## Runtime path
11
11
 
12
12
  1. Local state is prepared into a claimable Merkle leaf.
13
13
  2. A root is relayed to the peer chain through the bridge-specific transport.
14
14
  3. The remote side records the root in its inbox state.
15
15
  4. Claimants prove inclusion and recreate their position on the destination chain.
16
16
 
17
- ## High-Risk Areas
17
+ ## High-risk areas
18
18
 
19
19
  - Token mapping: mapping mistakes break economic equivalence, not just UX.
20
20
  - Root ordering and replay protection: message sequencing is part of correctness.
@@ -22,7 +22,7 @@
22
22
  - Shared accounting vs transport logic: many incidents stem from confusing these layers.
23
23
  - Peer snapshots and `numberOfClaimsSent`: these guard against double-spend at the cost of conservative locking when timing goes wrong.
24
24
 
25
- ## Tests To Trust First
25
+ ## Tests to trust first
26
26
 
27
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
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.
@@ -34,6 +34,8 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
34
34
  // --------------------------- custom errors ------------------------- //
35
35
  //*********************************************************************//
36
36
 
37
+ /// @notice Thrown when the provided transport payment is insufficient to cover the cost of the Arbitrum retryable
38
+ /// ticket.
37
39
  error JBArbitrumSucker_NotEnoughGas(uint256 payment, uint256 cost);
38
40
 
39
41
  //*********************************************************************//
@@ -36,11 +36,22 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
36
36
  // --------------------------- custom errors ------------------------- //
37
37
  //*********************************************************************//
38
38
 
39
+ /// @notice Thrown when the configured CCIP router address is the zero address.
39
40
  error JBCCIPSucker_InvalidRouter(address router);
41
+
42
+ /// @notice Thrown when an incoming root message claims a positive amount but no tokens were delivered with it.
40
43
  error JBCCIPSucker_PositiveRootWithoutDelivery(uint256 rootAmount);
44
+
45
+ /// @notice Thrown when the amount of tokens delivered is less than the amount declared in the root message.
41
46
  error JBCCIPSucker_UnderDeliveredAmount(uint256 delivered, uint256 rootAmount);
47
+
48
+ /// @notice Thrown when an incoming message delivers an unexpected number of token transfers.
42
49
  error JBCCIPSucker_UnexpectedDeliveredTokens(uint256 count);
50
+
51
+ /// @notice Thrown when an incoming message has an unrecognized message type prefix.
43
52
  error JBCCIPSucker_UnknownMessageType(uint8 messageType);
53
+
54
+ /// @notice Thrown when the token delivered with an incoming message does not match the expected token.
44
55
  error JBCCIPSucker_WrongDeliveredToken(address delivered, address expected);
45
56
 
46
57
  //*********************************************************************//
@@ -76,6 +76,7 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
76
76
 
77
77
  /// @notice Checks if the `sender` (`_msgSender()`) is a valid representative of the remote peer.
78
78
  /// @param sender The message's sender.
79
+ /// @return valid A flag if the sender is a valid representative of the remote peer.
79
80
  function _isRemotePeer(address sender) internal override returns (bool valid) {
80
81
  return sender == address(OPMESSENGER) && _toBytes32(OPMESSENGER.xDomainMessageSender()) == peer();
81
82
  }
package/src/JBSucker.sol CHANGED
@@ -68,31 +68,88 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
68
68
  // --------------------------- custom errors ------------------------- //
69
69
  //*********************************************************************//
70
70
 
71
+ /// @notice Thrown when a terminal-token or project-token amount being bridged exceeds the `uint128` cap enforced
72
+ /// for cross-VM compatibility.
71
73
  error JBSucker_AmountExceedsUint128(uint256 amount);
74
+
75
+ /// @notice Thrown when a token mapping specifies a bridging gas limit below the minimum required to safely deliver
76
+ /// an ERC-20 on the remote chain.
72
77
  error JBSucker_BelowMinGas(uint256 minGas, uint256 minGasLimit);
78
+
79
+ /// @notice Thrown when an outbound action is attempted while the sucker is deprecated (or no longer accepting
80
+ /// sends).
73
81
  error JBSucker_Deprecated(JBSuckerState state);
82
+
83
+ /// @notice Thrown when a deprecation is scheduled for a time sooner than the minimum allowed delay.
74
84
  error JBSucker_DeprecationTimestampTooSoon(uint256 givenTime, uint256 minimumTime);
85
+
86
+ /// @notice Thrown when a native token transport payment is required but no `msg.value` was sent.
75
87
  error JBSucker_ExpectedMsgValue(uint256 msgValue);
88
+
89
+ /// @notice Thrown when a merkle leaf index is greater than or equal to the maximum number of leaves the tree can
90
+ /// hold.
76
91
  error JBSucker_IndexOutOfRange(uint256 index);
92
+
93
+ /// @notice Thrown when the amount to add to the project's balance exceeds the funds available to the sucker.
77
94
  error JBSucker_InsufficientBalance(uint256 amount, uint256 balance);
95
+
96
+ /// @notice Thrown when the `msg.value` sent is less than the required `toRemoteFee`.
78
97
  error JBSucker_InsufficientMsgValue(uint256 received, uint256 expected);
98
+
99
+ /// @notice Thrown when an incoming bridge message has a format version that does not match the expected version.
79
100
  error JBSucker_InvalidMessageVersion(uint8 received, uint8 expected);
101
+
102
+ /// @notice Thrown when the native token is mapped to a remote token that is neither the native token nor the zero
103
+ /// address.
80
104
  error JBSucker_InvalidNativeRemoteAddress(bytes32 remoteToken);
105
+
106
+ /// @notice Thrown when a claim's merkle proof does not validate against the stored inbox root.
81
107
  error JBSucker_InvalidProof(bytes32 root, bytes32 inboxRoot);
108
+
109
+ /// @notice Thrown when a leaf at the given index has already been executed for the given token.
82
110
  error JBSucker_LeafAlreadyExecuted(address token, uint256 index);
111
+
112
+ /// @notice Thrown when no terminal can be found for the given project and token.
83
113
  error JBSucker_NoTerminalForToken(uint256 projectId, address token);
114
+
115
+ /// @notice Thrown when the caller is not a valid representative of the remote peer sucker.
84
116
  error JBSucker_NotPeer(bytes32 caller);
117
+
118
+ /// @notice Thrown when a send is attempted but there is nothing new in the outbox to bridge.
85
119
  error JBSucker_NothingToSend(address token, uint256 outboxBalance, uint256 treeCount, uint256 numberOfClaimsSent);
120
+
121
+ /// @notice Thrown when an account attempts to claim a retained failed-fee refund but is owed nothing.
86
122
  error JBSucker_NoRetainedToRemoteFee(address account);
123
+
124
+ /// @notice Thrown when an account attempts to claim a retained failed transport-payment refund but is owed nothing.
87
125
  error JBSucker_NoRetainedTransportPaymentRefund(address account);
126
+
127
+ /// @notice Thrown when a native token refund transfer to the beneficiary fails.
88
128
  error JBSucker_RefundFailed(address beneficiary, uint256 amount);
129
+
130
+ /// @notice Thrown when a token mapping targets a remote token that another local token has already reserved.
89
131
  error JBSucker_RemoteTokenAlreadyMapped(bytes32 remoteToken, address localToken);
132
+
133
+ /// @notice Thrown when remapping a local token whose outbox tree already has entries, which is no longer permitted.
90
134
  error JBSucker_TokenAlreadyMapped(address localToken, bytes32 mappedTo);
135
+
136
+ /// @notice Thrown when an emergency-hatch action is attempted for a token whose emergency hatch state does not
137
+ /// allow it.
91
138
  error JBSucker_TokenHasInvalidEmergencyHatchState(address token);
139
+
140
+ /// @notice Thrown when an action references a token that has not been mapped to a remote token.
92
141
  error JBSucker_TokenNotMapped(address token);
142
+
143
+ /// @notice Thrown when `msg.value` is sent for an action that expects none.
93
144
  error JBSucker_UnexpectedMsgValue(uint256 value);
145
+
146
+ /// @notice Thrown when a required beneficiary address is the zero address.
94
147
  error JBSucker_ZeroBeneficiary(bytes32 beneficiary);
148
+
149
+ /// @notice Thrown when bridging is attempted for a project that has no ERC-20 token deployed.
95
150
  error JBSucker_ZeroERC20Token(uint256 projectId);
151
+
152
+ /// @notice Thrown when a bridge is queued with zero project tokens.
96
153
  error JBSucker_ZeroProjectTokenCount();
97
154
 
98
155
  //*********************************************************************//
@@ -171,14 +228,16 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
171
228
  uint256 public retainedTransportPaymentRefundBalance;
172
229
 
173
230
  /// @notice The retained failed-fee ETH owed to each original `toRemote` caller.
231
+ /// @custom:param account The address owed the retained ETH.
174
232
  mapping(address account => uint256 amount) public retainedToRemoteFeeOf;
175
233
 
176
234
  /// @notice The retained failed transport-payment refund ETH owed to each original bridge caller.
235
+ /// @custom:param account The address owed the retained ETH.
177
236
  mapping(address account => uint256 amount) public retainedTransportPaymentRefundOf;
178
237
 
179
238
  /// @notice The source chain freshness key for the most recent accepted peer snapshot.
180
239
  /// @dev Only snapshots with a strictly newer source freshness key are accepted, preventing stale rollbacks.
181
- /// The historical name is retained for ABI compatibility with the `JBMessageRoot.sourceTimestamp` field.
240
+ /// Named to align with the `JBMessageRoot.sourceTimestamp` field it tracks.
182
241
  /// Returns 0 if no snapshot has been received yet.
183
242
  uint256 public snapshotTimestamp;
184
243
 
@@ -252,10 +311,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
252
311
  /// @dev A zero value preserves the default same-address deterministic peer.
253
312
  bytes32 private _peer;
254
313
 
255
- /// @notice The peer chain's per-currency surplus and balance from the latest snapshot. Rebuilt in full each time a
256
- /// fresher snapshot is received, so a context that dropped out of the new snapshot is simply absent — no
257
- /// per-entry
258
- /// versioning or clearing is needed. A read sums these and values them into the requested currency.
314
+ /// @notice The peer chain's per-currency surplus and balance from the latest snapshot.
315
+ /// @dev Rebuilt from each fresher snapshot; dropped contexts are absent without per-entry versioning or clearing.
316
+ /// A read sums these and values them into the requested currency.
259
317
  JBPeerChainContext[] private _peerContexts;
260
318
 
261
319
  //*********************************************************************//
@@ -401,7 +459,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
401
459
  }
402
460
 
403
461
  /// @notice Emergency escape hatch: lets a user reclaim their project tokens and terminal tokens on the chain they
404
- /// originally deposited from, when the bridge has become permanently non-functional. Must be enabled by the project
462
+ /// deposited from, when the bridge has become permanently non-functional. Must be enabled by the project
405
463
  /// owner via `enableEmergencyHatchFor`.
406
464
  /// @param claimData The terminal token, merkle tree leaf, and proof for the claim.
407
465
  function exitThroughEmergencyHatch(JBClaim calldata claimData) external override {
@@ -586,8 +644,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
586
644
 
587
645
  /// @notice Configure which remote-chain tokens each local terminal token maps to, enabling (or disabling) those
588
646
  /// tokens for cross-chain bridging. Setting a remote token to `bytes32(0)` disables bridging and flushes any
589
- /// pending outbox entries. Requires `MAP_SUCKER_TOKEN` permission from the project owner (or called by the
590
- /// registry during deployment).
647
+ /// pending outbox entries. Requires `MAP_SUCKER_TOKEN` permission from the project owner or deployment registry.
591
648
  /// @param maps A list of local and remote terminal token addresses to map, and minimum amount/gas limits for
592
649
  /// bridging them.
593
650
  function mapTokens(JBTokenMapping[] calldata maps) external payable override {
@@ -646,8 +703,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
646
703
  /// For EVM peers: the EVM address left-padded to 32 bytes via `_toBytes32`.
647
704
  /// For SVM peers: the full 32-byte Solana public key.
648
705
  /// @param minTokensReclaimed The minimum amount of terminal tokens to cash out for. If the amount cashed out is
649
- /// less
650
- /// than this, the transaction will revert.
706
+ /// less than this, the transaction will revert.
651
707
  /// @param token The address of the terminal token to cash out for.
652
708
  function prepare(
653
709
  uint256 projectTokenCount,
@@ -683,7 +739,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
683
739
  revert JBSucker_TokenNotMapped({token: token});
684
740
  }
685
741
 
686
- // Make sure that the sucker still allows sending new messaged.
742
+ // Make sure that the sucker still allows sending new messages.
687
743
  _requireSendingEnabled();
688
744
 
689
745
  // Transfer the tokens to this contract.
@@ -711,7 +767,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
711
767
  /// 14-day buffer ensures in-flight messages have time to arrive before the sucker fully shuts down.
712
768
  /// @param timestamp The time after which the sucker will be deprecated. Or `0` to remove the upcoming deprecation.
713
769
  function setDeprecation(uint40 timestamp) external override {
714
- // As long as the sucker has not started letting users withdrawal, its deprecation time can be
770
+ // As long as the sucker has not started letting users withdraw, its deprecation time can be
715
771
  // extended/shortened.
716
772
  _requireSendingEnabled();
717
773
 
@@ -845,9 +901,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
845
901
  }
846
902
 
847
903
  /// @notice The peer chain total supply bundled with the peer chain ID and snapshot freshness key.
848
- /// @dev Lets aggregators (e.g. `JBSuckerRegistry`) read the value, the peer chain it belongs to, and its
849
- /// freshness in one call instead of three separate staticcalls. The `value` is identical to
850
- /// `peerChainTotalSupply`.
904
+ /// @dev Lets aggregators (e.g. `JBSuckerRegistry`) read the value, peer chain, and freshness in one call instead
905
+ /// of three separate staticcalls. The `value` is identical to `peerChainTotalSupply`.
851
906
  /// @return A `JBPeerChainValue` with the total supply, peer chain ID, and snapshot freshness key.
852
907
  function peerChainTotalSupplyValue() external view returns (JBPeerChainValue memory) {
853
908
  return JBPeerChainValue({
@@ -930,14 +985,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
930
985
  return JBSuckerState.DEPRECATION_PENDING;
931
986
  }
932
987
 
933
- // The sucker will no longer send new roots to the pair, but it will accept new incoming roots.
934
- // Additionally it will let users exit here now that we can no longer send roots/tokens.
988
+ // The sucker no longer sends new roots to the pair, but it accepts new incoming roots.
989
+ // Additionally it lets users exit here, since the sucker can no longer send roots/tokens.
935
990
  // forge-lint: disable-next-line(block-timestamp)
936
991
  if (block.timestamp < _deprecatedAfter) {
937
992
  return JBSuckerState.SENDING_DISABLED;
938
993
  }
939
994
 
940
- // The sucker is now in the final state of deprecation. It will no longer allow new roots.
995
+ // The sucker is in the final state of deprecation. It does not allow new roots.
941
996
  return JBSuckerState.DEPRECATED;
942
997
  }
943
998
 
@@ -1015,8 +1070,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1015
1070
  deployer = _msgSender();
1016
1071
  }
1017
1072
 
1018
- /// @notice Map an ERC-20 token on the local chain to an ERC-20 token on the remote chain, allowing that token to be
1019
- /// bridged.
1073
+ /// @notice Map an ERC-20 token on the local chain to a remote-chain ERC-20 token for bridging.
1020
1074
  /// @param map The local and remote terminal token addresses to map, and minimum amount/gas limits for bridging
1021
1075
  /// them.
1022
1076
  function mapToken(JBTokenMapping calldata map) public payable override {
@@ -1027,7 +1081,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1027
1081
  // --------------------- internal transactions ----------------------- //
1028
1082
  //*********************************************************************//
1029
1083
 
1030
- /// @notice Adds funds to the projects balance.
1084
+ /// @notice Adds funds to the project's balance.
1031
1085
  /// @param token The terminal token to add to the project's balance.
1032
1086
  /// @param amount The amount of terminal tokens to add to the project's balance.
1033
1087
  /// @param cachedProjectId The cached project ID to avoid redundant storage reads.
@@ -1125,7 +1179,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1125
1179
  )
1126
1180
  internal
1127
1181
  {
1128
- // Guard against amounts that would overflow uint128 on SVM (INTEROP-5).
1182
+ // Guard against amounts that would overflow uint128 on SVM, which caps bridged amounts at uint128.
1129
1183
  if (terminalTokenAmount > type(uint128).max) revert JBSucker_AmountExceedsUint128(terminalTokenAmount);
1130
1184
  if (projectTokenCount > type(uint128).max) revert JBSucker_AmountExceedsUint128(projectTokenCount);
1131
1185
  // Build a hash based on the token amounts, the beneficiary, and the attribution metadata.
@@ -1161,13 +1215,12 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1161
1215
  /// @return valid Whether the sender is the remote peer.
1162
1216
  function _isRemotePeer(address sender) internal virtual returns (bool valid);
1163
1217
 
1164
- /// @notice Map an ERC-20 token on the local chain to an ERC-20 token on the remote chain, allowing that token to be
1165
- /// bridged or disabled.
1218
+ /// @notice Map an ERC-20 token on the local chain to a remote-chain ERC-20 token, or disable bridging.
1166
1219
  /// @dev Once a token has outbox tree entries (`_outboxOf[token].tree.count != 0`), it cannot be remapped to a
1167
1220
  /// different remote token -- it can only be disabled by mapping to `address(0)`, which triggers a final root
1168
1221
  /// flush to settle outstanding claims. This permanence prevents double-spending: if a remapping were allowed
1169
1222
  /// after outbox activity, the same local funds could be claimed against two different remote tokens. A
1170
- /// misconfigured mapping therefore requires deploying a new sucker. Re-enabling a previously disabled mapping
1223
+ /// misconfigured mapping therefore requires deploying a new sucker. Re-enabling a disabled mapping
1171
1224
  /// (back to the same remote token) is supported.
1172
1225
  /// @dev Remote tokens are also unique per local token within this sucker. The source side keeps separate
1173
1226
  /// outboxes/nonces per local token, but the destination side stores roots under the remote token address. Sharing
@@ -1324,7 +1377,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1324
1377
  // Ensure the token is mapped to an address on the remote chain.
1325
1378
  if (remoteToken.addr == bytes32(0)) revert JBSucker_TokenNotMapped(token);
1326
1379
 
1327
- // Make sure that the sucker still allows sending new messaged.
1380
+ // Make sure that the sucker still allows sending new messages.
1328
1381
  _requireSendingEnabled();
1329
1382
 
1330
1383
  // Drain the outbox: read balance/nonce/root, clear balance, advance nonce and numberOfClaimsSent.
@@ -1464,7 +1517,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1464
1517
  }
1465
1518
 
1466
1519
  /// @notice Validates a branch root against the expected root.
1467
- /// @dev This is a virtual function to allow a tests to override the behavior, it should never be overwritten
1520
+ /// @dev This is a virtual function to allow tests to override the behavior; it should never be overridden
1468
1521
  /// otherwise.
1469
1522
  /// @param expectedRoot The expected merkle root to validate against.
1470
1523
  /// @param leafHash The precomputed leaf hash (`_buildTreeHash` output) for the leaf being validated.
@@ -1489,8 +1542,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1489
1542
  }
1490
1543
  }
1491
1544
 
1492
- /// @notice Validates a leaf as being in the outbox merkle tree and not being send over the amb, and registers the
1493
- /// leaf as executed (to prevent double-spending).
1545
+ /// @notice Validates a leaf as being in the outbox merkle tree and not having been sent over the amb, and registers
1546
+ /// the leaf as executed (to prevent double-spending).
1494
1547
  /// @dev Reverts if the leaf is invalid.
1495
1548
  /// @dev IMPORTANT: Emergency exit safety depends on `numberOfClaimsSent` being accurately tracked.
1496
1549
  /// `numberOfClaimsSent` is updated in `_sendRoot` to equal `outbox.tree.count` at the time the root is sent
@@ -1513,7 +1566,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1513
1566
  /// @param terminalToken The terminal token that the project tokens were cashed out for.
1514
1567
  /// @param terminalTokenAmount The amount of terminal tokens reclaimed by the cash out.
1515
1568
  /// @param beneficiary The beneficiary of the project tokens (bytes32 for cross-VM compatibility).
1516
- /// @param index The index of the leaf to prove in the terminal token's inbox tree.
1569
+ /// @param index The index of the leaf to prove in the terminal token's outbox tree.
1517
1570
  /// @param leaves The leaves that prove that the leaf at the `index` is in the tree (i.e. the merkle branch that the
1518
1571
  /// leaf is on).
1519
1572
  function _validateForEmergencyExit(
@@ -1540,10 +1593,10 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1540
1593
  }
1541
1594
 
1542
1595
  // Check that this claim is within the bounds of who can claim.
1543
- // If the root that this leaf is in was already send then we can not let the user claim here.
1596
+ // If the root that this leaf is in was already sent then we can not let the user claim here.
1544
1597
  // As it could have also been received by the peer sucker, which would then let the user claim on each side.
1545
1598
  // NOTE: We are comparing the *count* and the *index*, so `count - 1` is the last index that was sent.
1546
- // A count of 0 means that no root has ever been send for this token, so everyone can claim.
1599
+ // A count of 0 means that no root has ever been sent for this token, so everyone can claim.
1547
1600
  JBOutboxTree storage outboxOfToken = _outboxOf[terminalToken];
1548
1601
  if (outboxOfToken.numberOfClaimsSent != 0 && outboxOfToken.numberOfClaimsSent - 1 >= index) {
1549
1602
  revert JBSucker_LeafAlreadyExecuted({token: terminalToken, index: index});
@@ -1551,8 +1604,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1551
1604
 
1552
1605
  {
1553
1606
  // We re-use the same `_executedFor` mapping but we use a different slot.
1554
- // We can not use the regular mapping, since this claim is done for tokens being send from here to the pair.
1555
- // where the regular mapping is for tokens that were send on the pair to here. Even though these may seem
1607
+ // We can not use the regular mapping, since this claim is done for tokens being sent from here to the pair.
1608
+ // where the regular mapping is for tokens that were sent on the pair to here. Even though these may seem
1556
1609
  // similar they are actually completely unrelated.
1557
1610
  address emergencyExitAddress = address(bytes20(keccak256(abi.encode(terminalToken))));
1558
1611
 
@@ -1832,7 +1885,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1832
1885
  return bytes32(uint256(uint160(addr)));
1833
1886
  }
1834
1887
 
1835
- /// @notice Allow sucker implementations to add/override mapping rules to suite their specific needs.
1888
+ /// @notice Allow sucker implementations to add/override mapping rules to suit their specific needs.
1836
1889
  /// @param map The token mapping to validate.
1837
1890
  function _validateTokenMapping(JBTokenMapping calldata map) internal pure virtual {
1838
1891
  bool isNative = map.localToken == JBConstants.NATIVE_TOKEN;
@@ -35,10 +35,19 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
35
35
  // --------------------------- custom errors ------------------------- //
36
36
  //*********************************************************************//
37
37
 
38
+ /// @notice Thrown when the owner attempts to set a `toRemoteFee` greater than the maximum allowed fee.
38
39
  error JBSuckerRegistry_FeeExceedsMax(uint256 fee, uint256 max);
40
+
41
+ /// @notice Thrown when a sucker deployment references a deployer that is not approved by this registry.
39
42
  error JBSuckerRegistry_InvalidDeployer(IJBSuckerDeployer deployer);
43
+
44
+ /// @notice Thrown when an action references a sucker that is not registered to the given project.
40
45
  error JBSuckerRegistry_SuckerDoesNotBelongToProject(uint256 projectId, address sucker);
46
+
47
+ /// @notice Thrown when a sucker is being removed from active listings but is not deprecated.
41
48
  error JBSuckerRegistry_SuckerIsNotDeprecated(address sucker, JBSuckerState suckerState);
49
+
50
+ /// @notice Thrown when a sucker reports a zero peer chain ID and cannot identify a real peer chain.
42
51
  error JBSuckerRegistry_ZeroPeerChainId(address sucker);
43
52
 
44
53
  //*********************************************************************//
@@ -82,7 +91,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
82
91
  //*********************************************************************//
83
92
 
84
93
  /// @notice Tracks whether the specified sucker deployer is approved by this registry.
85
- /// @custom:member deployer The address of the deployer to check.
94
+ /// @custom:param deployer The address of the deployer to check.
86
95
  mapping(address deployer => bool) public override suckerDeployerIsAllowed;
87
96
 
88
97
  /// @notice The ETH fee (in wei) paid into the fee project via terminal.pay() on each toRemote() call.
@@ -93,7 +102,8 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
93
102
  //*********************************************************************//
94
103
 
95
104
  /// @notice Tracks the suckers for the specified project.
96
- mapping(uint256 => EnumerableMap.AddressToUintMap) internal _suckersOf;
105
+ /// @custom:param projectId The ID of the project whose suckers are tracked.
106
+ mapping(uint256 projectId => EnumerableMap.AddressToUintMap) internal _suckersOf;
97
107
 
98
108
  //*********************************************************************//
99
109
  // -------------------------- constructor ---------------------------- //
@@ -146,8 +156,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
146
156
  return exists && (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED);
147
157
  }
148
158
 
149
- /// @notice Values one sucker's raw peer-chain balance into a currency, bundled with the peer chain ID and
150
- /// freshness.
159
+ /// @notice Values one sucker's raw peer-chain balance into a currency with peer chain ID and freshness.
151
160
  /// @dev Exposed as an external self-call boundary so `totalRemoteBalanceOf` can `try` it and drop a single sucker
152
161
  /// whose price feed is missing. A context whose currency already matches `currency` folds in at par (no feed read);
153
162
  /// a missing cross-currency feed reverts, and the aggregator catches it and skips just this sucker.
@@ -196,8 +205,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
196
205
  return JBPeerChainValue({value: value, peerChainId: chainId, snapshotTimestamp: snapshot});
197
206
  }
198
207
 
199
- /// @notice Values one sucker's raw peer-chain surplus into a currency, bundled with the peer chain ID and
200
- /// freshness.
208
+ /// @notice Values one sucker's raw peer-chain surplus into a currency with peer chain ID and freshness.
201
209
  /// @dev Exposed as an external self-call boundary so `totalRemoteSurplusOf` can `try` it and drop a single sucker
202
210
  /// whose price feed is missing. A context whose currency already matches `currency` folds in at par (no feed read);
203
211
  /// a missing cross-currency feed reverts, and the aggregator catches it and skips just this sucker.
@@ -654,9 +662,9 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
654
662
  emit SuckerDeployerAllowed({deployer: deployer, caller: _msgSender()});
655
663
  }
656
664
 
657
- /// @notice Adds multiple suckers deployer to the allowlist.
665
+ /// @notice Adds multiple sucker deployers to the allowlist.
658
666
  /// @dev Can only be called by this contract's owner (initially project ID 1, or JuiceboxDAO).
659
- /// @param deployers The address of the deployer to add.
667
+ /// @param deployers The addresses of the deployers to add.
660
668
  function allowSuckerDeployers(address[] calldata deployers) public override onlyOwner {
661
669
  // Cache _msgSender() to avoid redundant calls in the loop.
662
670
  address sender = _msgSender();