@bananapus/suckers-v6 0.0.71 → 0.0.72

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
@@ -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.72",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
 
@@ -401,7 +460,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
401
460
  }
402
461
 
403
462
  /// @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
463
+ /// deposited from, when the bridge has become permanently non-functional. Must be enabled by the project
405
464
  /// owner via `enableEmergencyHatchFor`.
406
465
  /// @param claimData The terminal token, merkle tree leaf, and proof for the claim.
407
466
  function exitThroughEmergencyHatch(JBClaim calldata claimData) external override {
@@ -683,7 +742,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
683
742
  revert JBSucker_TokenNotMapped({token: token});
684
743
  }
685
744
 
686
- // Make sure that the sucker still allows sending new messaged.
745
+ // Make sure that the sucker still allows sending new messages.
687
746
  _requireSendingEnabled();
688
747
 
689
748
  // Transfer the tokens to this contract.
@@ -711,7 +770,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
711
770
  /// 14-day buffer ensures in-flight messages have time to arrive before the sucker fully shuts down.
712
771
  /// @param timestamp The time after which the sucker will be deprecated. Or `0` to remove the upcoming deprecation.
713
772
  function setDeprecation(uint40 timestamp) external override {
714
- // As long as the sucker has not started letting users withdrawal, its deprecation time can be
773
+ // As long as the sucker has not started letting users withdraw, its deprecation time can be
715
774
  // extended/shortened.
716
775
  _requireSendingEnabled();
717
776
 
@@ -930,14 +989,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
930
989
  return JBSuckerState.DEPRECATION_PENDING;
931
990
  }
932
991
 
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.
992
+ // The sucker no longer sends new roots to the pair, but it accepts new incoming roots.
993
+ // Additionally it lets users exit here, since the sucker can no longer send roots/tokens.
935
994
  // forge-lint: disable-next-line(block-timestamp)
936
995
  if (block.timestamp < _deprecatedAfter) {
937
996
  return JBSuckerState.SENDING_DISABLED;
938
997
  }
939
998
 
940
- // The sucker is now in the final state of deprecation. It will no longer allow new roots.
999
+ // The sucker is in the final state of deprecation. It does not allow new roots.
941
1000
  return JBSuckerState.DEPRECATED;
942
1001
  }
943
1002
 
@@ -1027,7 +1086,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1027
1086
  // --------------------- internal transactions ----------------------- //
1028
1087
  //*********************************************************************//
1029
1088
 
1030
- /// @notice Adds funds to the projects balance.
1089
+ /// @notice Adds funds to the project's balance.
1031
1090
  /// @param token The terminal token to add to the project's balance.
1032
1091
  /// @param amount The amount of terminal tokens to add to the project's balance.
1033
1092
  /// @param cachedProjectId The cached project ID to avoid redundant storage reads.
@@ -1125,7 +1184,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1125
1184
  )
1126
1185
  internal
1127
1186
  {
1128
- // Guard against amounts that would overflow uint128 on SVM (INTEROP-5).
1187
+ // Guard against amounts that would overflow uint128 on SVM, which caps bridged amounts at uint128.
1129
1188
  if (terminalTokenAmount > type(uint128).max) revert JBSucker_AmountExceedsUint128(terminalTokenAmount);
1130
1189
  if (projectTokenCount > type(uint128).max) revert JBSucker_AmountExceedsUint128(projectTokenCount);
1131
1190
  // Build a hash based on the token amounts, the beneficiary, and the attribution metadata.
@@ -1167,7 +1226,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1167
1226
  /// different remote token -- it can only be disabled by mapping to `address(0)`, which triggers a final root
1168
1227
  /// flush to settle outstanding claims. This permanence prevents double-spending: if a remapping were allowed
1169
1228
  /// 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
1229
+ /// misconfigured mapping therefore requires deploying a new sucker. Re-enabling a disabled mapping
1171
1230
  /// (back to the same remote token) is supported.
1172
1231
  /// @dev Remote tokens are also unique per local token within this sucker. The source side keeps separate
1173
1232
  /// outboxes/nonces per local token, but the destination side stores roots under the remote token address. Sharing
@@ -1324,7 +1383,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1324
1383
  // Ensure the token is mapped to an address on the remote chain.
1325
1384
  if (remoteToken.addr == bytes32(0)) revert JBSucker_TokenNotMapped(token);
1326
1385
 
1327
- // Make sure that the sucker still allows sending new messaged.
1386
+ // Make sure that the sucker still allows sending new messages.
1328
1387
  _requireSendingEnabled();
1329
1388
 
1330
1389
  // Drain the outbox: read balance/nonce/root, clear balance, advance nonce and numberOfClaimsSent.
@@ -1464,7 +1523,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1464
1523
  }
1465
1524
 
1466
1525
  /// @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
1526
+ /// @dev This is a virtual function to allow tests to override the behavior; it should never be overridden
1468
1527
  /// otherwise.
1469
1528
  /// @param expectedRoot The expected merkle root to validate against.
1470
1529
  /// @param leafHash The precomputed leaf hash (`_buildTreeHash` output) for the leaf being validated.
@@ -1489,8 +1548,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1489
1548
  }
1490
1549
  }
1491
1550
 
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).
1551
+ /// @notice Validates a leaf as being in the outbox merkle tree and not having been sent over the amb, and registers
1552
+ /// the leaf as executed (to prevent double-spending).
1494
1553
  /// @dev Reverts if the leaf is invalid.
1495
1554
  /// @dev IMPORTANT: Emergency exit safety depends on `numberOfClaimsSent` being accurately tracked.
1496
1555
  /// `numberOfClaimsSent` is updated in `_sendRoot` to equal `outbox.tree.count` at the time the root is sent
@@ -1513,7 +1572,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1513
1572
  /// @param terminalToken The terminal token that the project tokens were cashed out for.
1514
1573
  /// @param terminalTokenAmount The amount of terminal tokens reclaimed by the cash out.
1515
1574
  /// @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.
1575
+ /// @param index The index of the leaf to prove in the terminal token's outbox tree.
1517
1576
  /// @param leaves The leaves that prove that the leaf at the `index` is in the tree (i.e. the merkle branch that the
1518
1577
  /// leaf is on).
1519
1578
  function _validateForEmergencyExit(
@@ -1540,10 +1599,10 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1540
1599
  }
1541
1600
 
1542
1601
  // 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.
1602
+ // If the root that this leaf is in was already sent then we can not let the user claim here.
1544
1603
  // As it could have also been received by the peer sucker, which would then let the user claim on each side.
1545
1604
  // 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.
1605
+ // A count of 0 means that no root has ever been sent for this token, so everyone can claim.
1547
1606
  JBOutboxTree storage outboxOfToken = _outboxOf[terminalToken];
1548
1607
  if (outboxOfToken.numberOfClaimsSent != 0 && outboxOfToken.numberOfClaimsSent - 1 >= index) {
1549
1608
  revert JBSucker_LeafAlreadyExecuted({token: terminalToken, index: index});
@@ -1551,8 +1610,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1551
1610
 
1552
1611
  {
1553
1612
  // 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
1613
+ // We can not use the regular mapping, since this claim is done for tokens being sent from here to the pair.
1614
+ // where the regular mapping is for tokens that were sent on the pair to here. Even though these may seem
1556
1615
  // similar they are actually completely unrelated.
1557
1616
  address emergencyExitAddress = address(bytes20(keccak256(abi.encode(terminalToken))));
1558
1617
 
@@ -1832,7 +1891,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1832
1891
  return bytes32(uint256(uint160(addr)));
1833
1892
  }
1834
1893
 
1835
- /// @notice Allow sucker implementations to add/override mapping rules to suite their specific needs.
1894
+ /// @notice Allow sucker implementations to add/override mapping rules to suit their specific needs.
1836
1895
  /// @param map The token mapping to validate.
1837
1896
  function _validateTokenMapping(JBTokenMapping calldata map) internal pure virtual {
1838
1897
  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 ---------------------------- //
@@ -654,9 +664,9 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
654
664
  emit SuckerDeployerAllowed({deployer: deployer, caller: _msgSender()});
655
665
  }
656
666
 
657
- /// @notice Adds multiple suckers deployer to the allowlist.
667
+ /// @notice Adds multiple sucker deployers to the allowlist.
658
668
  /// @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.
669
+ /// @param deployers The addresses of the deployers to add.
660
670
  function allowSuckerDeployers(address[] calldata deployers) public override onlyOwner {
661
671
  // Cache _msgSender() to avoid redundant calls in the loop.
662
672
  address sender = _msgSender();
@@ -87,16 +87,37 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
87
87
  // --------------------------- custom errors ------------------------- //
88
88
  //*********************************************************************//
89
89
 
90
+ /// @notice Thrown when no received batch covers the claim's leaf index, so no conversion rate can be applied.
90
91
  error JBSwapCCIPSucker_BatchNotReceived(uint64 nonce);
92
+
93
+ /// @notice Thrown when the Uniswap V4 unlock callback is invoked by an address other than the pool manager.
91
94
  error JBSwapCCIPSucker_CallerNotPoolManager(address caller);
95
+
96
+ /// @notice Thrown when an incoming batch reuses a nonce that has already been received.
92
97
  error JBSwapCCIPSucker_DuplicateBatch(uint64 nonce);
98
+
99
+ /// @notice Thrown when the bridge token is the zero address or matches the wrapped native token.
93
100
  error JBSwapCCIPSucker_InvalidBridgeToken(address bridgeToken, address wrappedNativeToken);
101
+
102
+ /// @notice Thrown when a swap retry is attempted for a token and nonce that has no pending failed swap.
94
103
  error JBSwapCCIPSucker_NoPendingSwap(address localToken, uint64 nonce, bool retrySwapLocked);
104
+
105
+ /// @notice Thrown when the self-only swap entry point is called by an address other than this contract.
95
106
  error JBSwapCCIPSucker_OnlySelf(address caller, address expected);
107
+
108
+ /// @notice Thrown when an incoming root claims a positive amount but no bridge tokens were delivered with it.
96
109
  error JBSwapCCIPSucker_PositiveRootWithoutDelivery(uint256 rootAmount);
110
+
111
+ /// @notice Thrown when a swap produces no output tokens.
97
112
  error JBSwapCCIPSucker_SwapFailed(address tokenIn, address tokenOut, uint256 amountIn);
113
+
114
+ /// @notice Thrown when a claim or swap is attempted while another swap is already in progress.
98
115
  error JBSwapCCIPSucker_SwapPending(uint64 nonce);
116
+
117
+ /// @notice Thrown when an incoming message delivers an unexpected number of token transfers.
99
118
  error JBSwapCCIPSucker_UnexpectedDeliveredTokens(uint256 count);
119
+
120
+ /// @notice Thrown when the token delivered with an incoming message does not match the expected token.
100
121
  error JBSwapCCIPSucker_WrongDeliveredToken(address delivered, address expected);
101
122
 
102
123
  //*********************************************************************//
@@ -22,7 +22,10 @@ contract JBSwapCCIPSuckerDeployer is JBCCIPSuckerDeployer, IJBSwapCCIPSuckerDepl
22
22
  // --------------------------- custom errors ------------------------- //
23
23
  //*********************************************************************//
24
24
 
25
+ /// @notice Thrown when the swap configuration is set with a zero-address bridge token.
25
26
  error JBSwapCCIPSuckerDeployer_InvalidSwapConfig(address bridgeToken);
27
+
28
+ /// @notice Thrown when the swap configuration is set after it has already been configured.
26
29
  error JBSwapCCIPSuckerDeployer_SwapAlreadyConfigured(address bridgeToken);
27
30
 
28
31
  //*********************************************************************//
@@ -46,12 +46,25 @@ library JBSwapPoolLib {
46
46
  // --------------------------- custom errors ------------------------- //
47
47
  //*********************************************************************//
48
48
 
49
+ /// @notice Thrown when a swap amount exceeds the `uint128` range required by the pool interface.
49
50
  error JBSwapPoolLib_AmountOverflow(uint256 amount);
51
+
52
+ /// @notice Thrown when a swap callback is invoked by an address other than the expected pool.
50
53
  error JBSwapPoolLib_CallerNotPool(address caller);
54
+
55
+ /// @notice Thrown when a pool's oracle history is too short to serve the required TWAP window.
51
56
  error JBSwapPoolLib_InsufficientTwapHistory(address pool, uint256 availableWindow, uint256 requiredWindow);
57
+
58
+ /// @notice Thrown when the selected pool has no liquidity to swap against.
52
59
  error JBSwapPoolLib_NoLiquidity(address pool, PoolId poolId);
60
+
61
+ /// @notice Thrown when no eligible V3 or V4 pool can be found for the given token pair.
53
62
  error JBSwapPoolLib_NoPool(address tokenIn, address tokenOut);
63
+
64
+ /// @notice Thrown when a swap consumes less than the full requested input amount.
54
65
  error JBSwapPoolLib_PartialFill(uint256 consumed, uint256 requested);
66
+
67
+ /// @notice Thrown when a swap's output is below the minimum acceptable amount after slippage.
55
68
  error JBSwapPoolLib_SlippageExceeded(uint256 amountOut, uint256 minAmountOut);
56
69
 
57
70
  //*********************************************************************//
@@ -0,0 +1,5 @@
1
+ # Archived suckers (non-canonical)
2
+
3
+ The contracts in this directory are retained for reference only. They are not part of the canonical
4
+ runtime surface and are out of scope for audits, the house-style sweep, and deployment. Treat any
5
+ inline language here as historical and do not depend on these implementations.
@@ -26,11 +26,23 @@ abstract contract JBSuckerDeployer is ERC2771Context, JBPermissioned, IJBSuckerD
26
26
  // --------------------------- custom errors ------------------------- //
27
27
  //*********************************************************************//
28
28
 
29
+ /// @notice Thrown when configuration is attempted but the deployer (singleton or layer-specific config) has already
30
+ /// been configured.
29
31
  error JBSuckerDeployer_AlreadyConfigured(address singleton, bool layerSpecificConfigurationIsSet);
32
+
33
+ /// @notice Thrown when a sucker is deployed before the deployer's singleton implementation has been set.
30
34
  error JBSuckerDeployer_DeployerIsNotConfigured(address singleton);
35
+
36
+ /// @notice Thrown when the layer-specific configuration is not properly set after configuration.
31
37
  error JBSuckerDeployer_InvalidLayerSpecificConfiguration(address configurator);
38
+
39
+ /// @notice Thrown when a sucker is deployed before the layer-specific configuration has been set.
32
40
  error JBSuckerDeployer_LayerSpecificNotConfigured(address configurator);
41
+
42
+ /// @notice Thrown when the caller is not the address authorized to configure this deployer.
33
43
  error JBSuckerDeployer_Unauthorized(address caller, address expected);
44
+
45
+ /// @notice Thrown when the configurator address provided is the zero address.
34
46
  error JBSuckerDeployer_ZeroConfiguratorAddress(address configurator);
35
47
 
36
48
  //*********************************************************************//
@@ -51,9 +63,11 @@ abstract contract JBSuckerDeployer is ERC2771Context, JBPermissioned, IJBSuckerD
51
63
  //*********************************************************************//
52
64
 
53
65
  /// @notice Whether a given address was deployed by this deployer.
54
- mapping(address => bool) public override isSucker;
66
+ /// @custom:param sucker The address to check.
67
+ mapping(address sucker => bool) public override isSucker;
55
68
 
56
69
  /// @notice The singleton used to clone suckers.
70
+ /// @dev Write-once via `configureSingleton`; the clone implementation for every sucker this deployer produces.
57
71
  JBSucker public singleton;
58
72
 
59
73
  //*********************************************************************//
@@ -24,6 +24,7 @@ interface IJBSucker is IERC165 {
24
24
  /// @param projectTokenCount The number of project tokens claimed.
25
25
  /// @param terminalTokenAmount The amount of terminal tokens involved.
26
26
  /// @param index The leaf index in the inbox tree.
27
+ /// @param metadata The opaque, caller-defined payload carried by the claimed leaf.
27
28
  /// @param caller The address that performed the claim.
28
29
  event Claimed(
29
30
  bytes32 beneficiary,
@@ -52,6 +53,7 @@ interface IJBSucker is IERC165 {
52
53
  /// @param root The new outbox tree root after insertion.
53
54
  /// @param projectTokenCount The number of project tokens cashed out.
54
55
  /// @param terminalTokenAmount The amount of terminal tokens reclaimed.
56
+ /// @param metadata The opaque, caller-defined payload carried by the outbox leaf.
55
57
  /// @param caller The address that performed the insertion.
56
58
  event InsertToOutboxTree(
57
59
  bytes32 indexed beneficiary,
@@ -3,6 +3,7 @@ pragma solidity 0.8.28;
3
3
 
4
4
  /// @notice CCIP chain-specific constants used across Juicebox sucker contracts.
5
5
  library CCIPHelper {
6
+ /// @notice Thrown when a lookup is requested for a chain ID that has no configured CCIP constants.
6
7
  error CCIPHelper_UnsupportedChain(uint256 chainId);
7
8
  /// @notice The CCIP router address for Ethereum mainnet.
8
9
  address public constant ETH_ROUTER = 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D;
@@ -5,6 +5,7 @@ pragma solidity 0.8.28;
5
5
  * @title MerkleLib
6
6
  * @author Illusory Systems Inc.
7
7
  * @notice An incremental merkle tree modeled on the eth2 deposit contract.
8
+ * @dev Vendored upstream library; its Doxygen-style NatSpec deviates from the repo's `///` house style.
8
9
  *
9
10
  */
10
11
  library MerkleLib {
@@ -12,6 +13,7 @@ library MerkleLib {
12
13
  // --------------------------- custom errors ------------------------- //
13
14
  //*********************************************************************//
14
15
 
16
+ /// @notice Thrown when a leaf is inserted into a merkle tree that has already reached its maximum number of leaves.
15
17
  error MerkleLib_InsertTreeIsFull(uint256 size, uint256 maxLeaves);
16
18
 
17
19
  //*********************************************************************//
@@ -82,7 +84,7 @@ library MerkleLib {
82
84
  /**
83
85
  * @notice Inserts a given node (leaf) into the merkle tree in storage.
84
86
  * @dev Operates directly on storage, writing only the single branch entry that changes (plus count).
85
- * This avoids the 33-slot memory round-trip of the previous memory-based approach.
87
+ * This writes only the changed branch slot instead of round-tripping all 33 slots through memory.
86
88
  * @dev Reverts if the tree is already full.
87
89
  * @param tree The storage reference to the tree.
88
90
  * @param node Element to insert into tree.