@bananapus/suckers-v6 0.0.35 → 0.0.37

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
@@ -72,8 +72,8 @@ That means every bridge path has two trust surfaces:
72
72
  1. `test/unit/registry.t.sol`
73
73
  2. `test/unit/multi_chain_evolution.t.sol`
74
74
  3. `test/ForkClaimMainnet.t.sol`
75
- 4. `test/audit/PeerSnapshotDesync.t.sol`
76
- 5. `test/audit/ToRemoteFeeIrrecoverable.t.sol`
75
+ 4. `test/regression/PeerSnapshotDesync.t.sol`
76
+ 5. `test/regression/ToRemoteFeeIrrecoverable.t.sol`
77
77
 
78
78
  ## Install
79
79
 
@@ -113,7 +113,7 @@ src/
113
113
  structs/
114
114
  utils/
115
115
  test/
116
- unit, fork, interoperability, attack, audit, and regression coverage
116
+ unit, fork, interoperability, attack, review, and regression coverage
117
117
  script/
118
118
  Deploy.s.sol
119
119
  helpers/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/suckers-v6",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "deploy:mainnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
27
27
  "deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
28
28
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-suckers-v6'",
29
- "analyze": "slither . --config-file slither-ci.config.json"
29
+ "analyze": "forge lint --deny notes"
30
30
  },
31
31
  "dependencies": {
32
32
  "@arbitrum/nitro-contracts": "3.2.0",
@@ -23,6 +23,6 @@
23
23
 
24
24
  ## Useful Proof Points
25
25
 
26
- - [`test/SuckerAttacks.t.sol`](../test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](../test/SuckerDeepAttacks.t.sol), and [`test/TestAuditGaps.sol`](../test/TestAuditGaps.sol) for security-sensitive assumptions.
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.
28
- - [`test/unit/invariants.t.sol`](../test/unit/invariants.t.sol), [`test/unit/peer_chain_state.t.sol`](../test/unit/peer_chain_state.t.sol), and [`test/audit/PeerSnapshotDesync.t.sol`](../test/audit/PeerSnapshotDesync.t.sol) when shared accounting or snapshot boundaries are in doubt.
28
+ - [`test/unit/invariants.t.sol`](../test/unit/invariants.t.sol), [`test/unit/peer_chain_state.t.sol`](../test/unit/peer_chain_state.t.sol), and [`test/regression/PeerSnapshotDesync.t.sol`](../test/regression/PeerSnapshotDesync.t.sol) when shared accounting or snapshot boundaries are in doubt.
@@ -27,4 +27,4 @@
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.
29
29
  - [`test/unit/invariants.t.sol`](../test/unit/invariants.t.sol), [`test/unit/peer_chain_state.t.sol`](../test/unit/peer_chain_state.t.sol), and [`test/unit/registry.t.sol`](../test/unit/registry.t.sol) for shared-accounting invariants.
30
- - [`test/SuckerAttacks.t.sol`](../test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](../test/SuckerDeepAttacks.t.sol), [`test/audit/PeerSnapshotDesync.t.sol`](../test/audit/PeerSnapshotDesync.t.sol), and [`test/audit/PeerDeterminism.t.sol`](../test/audit/PeerDeterminism.t.sol) when the bug could involve base logic, registry behavior, or a specific bridge implementation.
30
+ - [`test/SuckerAttacks.t.sol`](../test/SuckerAttacks.t.sol), [`test/SuckerDeepAttacks.t.sol`](../test/SuckerDeepAttacks.t.sol), [`test/regression/PeerSnapshotDesync.t.sol`](../test/regression/PeerSnapshotDesync.t.sol), and [`test/regression/PeerDeterminism.t.sol`](../test/regression/PeerDeterminism.t.sol) when the bug could involve base logic, registry behavior, or a specific bridge implementation.
@@ -127,7 +127,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
127
127
  internal
128
128
  {
129
129
  address peerAddress = _peerAddress();
130
- // slither-disable-next-line unused-return,calls-loop
131
130
  ARBINBOX.unsafeCreateRetryableTicket{value: callTransportCost + nativeValue}({
132
131
  to: peerAddress,
133
132
  l2CallValue: nativeValue,
@@ -144,7 +143,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
144
143
  /// @param token The ERC-20 token to approve.
145
144
  /// @param amount The amount to approve.
146
145
  function _approveGateway(address token, uint256 amount) internal {
147
- // slither-disable-next-line calls-loop
148
146
  SafeERC20.forceApprove({token: IERC20(token), spender: GATEWAYROUTER.getGateway(token), value: amount});
149
147
  }
150
148
 
@@ -168,10 +166,9 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
168
166
  bytes memory data = abi.encodeCall(JBSucker.fromRemote, (message));
169
167
 
170
168
  // Depending on which layer we are on, send the call to the other layer.
171
- // slither-disable-start out-of-order-retryable
172
169
  if (LAYER == JBLayer.L1) {
173
170
  // L1→L2 requires transport payment for retryable tickets.
174
- if (transportPayment == 0) revert JBSucker_ExpectedMsgValue();
171
+ if (transportPayment == 0) revert JBSucker_ExpectedMsgValue({msgValue: transportPayment});
175
172
  _toL2({
176
173
  token: token, transportPayment: transportPayment, amount: amount, data: data, remoteToken: remoteToken
177
174
  });
@@ -180,7 +177,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
180
177
  if (transportPayment != 0) revert JBSucker_UnexpectedMsgValue(transportPayment);
181
178
  _toL1({token: token, amount: amount, data: data, remoteToken: remoteToken});
182
179
  }
183
- // slither-disable-end out-of-order-retryable
184
180
  }
185
181
 
186
182
  /// @notice Bridge the `token` and data to the remote L1 chain.
@@ -208,7 +204,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
208
204
  _approveGateway({token: token, amount: amount});
209
205
 
210
206
  // Convert bytes32 types to address at the Arbitrum bridge API boundary.
211
- // slither-disable-next-line calls-loop,unused-return
212
207
  IArbL2GatewayRouter(address(GATEWAYROUTER))
213
208
  .outboundTransfer({
214
209
  l1Token: _toAddress(remoteToken.addr), to: peerAddress, amount: amount, data: bytes("")
@@ -220,7 +215,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
220
215
 
221
216
  // Send the message to the peer with the reclaimed ETH.
222
217
  // Address `100` is the ArbSys precompile address.
223
- // slither-disable-next-line calls-loop,unused-return
224
218
  ArbSys(address(100)).sendTxToL1{value: nativeValue}({destination: peerAddress, data: data});
225
219
  }
226
220
 
@@ -251,7 +245,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
251
245
  uint256 maxSubmissionCost;
252
246
 
253
247
  {
254
- // slither-disable-next-line calls-loop
255
248
  maxSubmissionCost =
256
249
  ARBINBOX.calculateRetryableSubmissionFee({dataLength: data.length, baseFee: maxFeePerGas});
257
250
 
@@ -267,15 +260,12 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
267
260
  {
268
261
  // Get the exact calldata length the gateway will create for the retryable ticket.
269
262
  // The Arbitrum Inbox validates maxSubmissionCost against this actual payload, not the user data.
270
- // slither-disable-next-line calls-loop
271
263
  address gateway = GATEWAYROUTER.getGateway(token);
272
- // slither-disable-next-line calls-loop
273
264
  uint256 outboundCalldataLength =
274
265
  IL1ArbitrumGateway(gateway)
275
266
  .getOutboundCalldata({
276
267
  _token: token, _from: address(this), _to: _peerAddress(), _amount: amount, _data: bytes("")
277
268
  }).length;
278
- // slither-disable-next-line calls-loop
279
269
  maxSubmissionCostERC20 = ARBINBOX.calculateRetryableSubmissionFee({
280
270
  dataLength: outboundCalldataLength, baseFee: maxFeePerGas
281
271
  });
@@ -284,7 +274,9 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
284
274
 
285
275
  // Ensure we bridge enough for gas costs on L2 side
286
276
  if (transportPayment < callTransportCost + tokenTransportCost) {
287
- revert JBArbitrumSucker_NotEnoughGas(transportPayment, callTransportCost + tokenTransportCost);
277
+ revert JBArbitrumSucker_NotEnoughGas({
278
+ payment: transportPayment, cost: callTransportCost + tokenTransportCost
279
+ });
288
280
  }
289
281
 
290
282
  {
@@ -298,8 +290,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
298
290
  _approveGateway({token: token, amount: amount});
299
291
 
300
292
  // Perform the ERC-20 bridge transfer. Convert bytes32 peer to address at the Arbitrum bridge API boundary.
301
- // slither-disable-start out-of-order-retryable
302
- // slither-disable-next-line calls-loop,unused-return
303
293
  IArbL1GatewayRouter(address(GATEWAYROUTER)).outboundTransferCustomRefund{value: tokenTransportCost}({
304
294
  token: token,
305
295
  refundTo: _msgSender(),
@@ -312,7 +302,7 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
312
302
  } else {
313
303
  // Ensure we bridge enough for gas costs on L2 side
314
304
  if (transportPayment < callTransportCost) {
315
- revert JBArbitrumSucker_NotEnoughGas(transportPayment, callTransportCost);
305
+ revert JBArbitrumSucker_NotEnoughGas({payment: transportPayment, cost: callTransportCost});
316
306
  }
317
307
 
318
308
  // If the token is the native token then we only need to do a single call.
@@ -328,7 +318,6 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
328
318
  // The above check is the same check that makes it `safeCreateRetryableTicket`.
329
319
 
330
320
  // Convert bytes32 peer to address at the Arbitrum inbox API boundary.
331
- // slither-disable-next-line calls-loop,unused-return
332
321
  _createRetryableTicket({
333
322
  callTransportCost: callTransportCost,
334
323
  nativeValue: nativeValue,
@@ -336,6 +325,5 @@ contract JBArbitrumSucker is JBSucker, IJBArbitrumSucker {
336
325
  maxFeePerGas: maxFeePerGas,
337
326
  data: data
338
327
  });
339
- // slither-disable-end out-of-order-retryable
340
328
  }
341
329
  }
@@ -163,7 +163,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
163
163
 
164
164
  // Make sure that the message came from our peer.
165
165
  if (origin != _peerAddress() || any2EvmMessage.sourceChainSelector != REMOTE_CHAIN_SELECTOR) {
166
- revert JBSucker_NotPeer(_toBytes32(origin));
166
+ revert JBSucker_NotPeer({caller: _toBytes32(origin)});
167
167
  }
168
168
 
169
169
  // Discriminate message type: abi.encode(uint8 type, bytes payload).
@@ -174,7 +174,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
174
174
  // Decode the root message from the payload.
175
175
  JBMessageRoot memory root = abi.decode(payload, (JBMessageRoot));
176
176
 
177
- // Only unwrap WETH -> ETH when the root targets native token (not when claiming WETH as ERC-20).
177
+ // Only unwrap wrapped native token when the root targets native token (not when claiming it as ERC-20).
178
178
  if (root.token == bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN)))) {
179
179
  JBCCIPLib.unwrapReceivedTokens({
180
180
  ccipRouter: CCIP_ROUTER, destTokenAmounts: any2EvmMessage.destTokenAmounts
@@ -184,7 +184,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
184
184
  // Forward the root message to this contract's fromRemote handler.
185
185
  this.fromRemote(root);
186
186
  } else {
187
- revert JBCCIPSucker_UnknownMessageType(messageType);
187
+ revert JBCCIPSucker_UnknownMessageType({messageType: messageType});
188
188
  }
189
189
  }
190
190
 
@@ -224,8 +224,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
224
224
  // Add extra gas for the ERC-20 token transfer on the remote chain.
225
225
  gasLimit += remoteToken.minGas;
226
226
 
227
- // Wrap native ETH -> WETH if needed, build the CCIP token amounts array, and approve the router.
228
- // slither-disable-next-line unused-return
227
+ // Wrap native tokens if needed, build the CCIP token amounts array, and approve the router.
229
228
  (tokenAmounts,) = JBCCIPLib.prepareTokenAmounts({ccipRouter: CCIP_ROUTER, token: token, amount: amount});
230
229
  } else {
231
230
  // No tokens to bridge — use an empty array.
@@ -239,7 +238,6 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
239
238
  address feeToken = transportPayment == 0 ? CCIPHelper.linkOfChain(block.chainid) : address(0);
240
239
 
241
240
  // Build and send the CCIP message with the root payload.
242
- // slither-disable-next-line reentrancy-events
243
241
  (bool refundFailed, uint256 refundAmount) = JBCCIPLib.sendCCIPMessage({
244
242
  ccipRouter: CCIP_ROUTER,
245
243
  remoteChainSelector: REMOTE_CHAIN_SELECTOR,
@@ -256,7 +254,6 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
256
254
  // Retain failed refunds as caller credit instead of leaving them project-addable or stranded.
257
255
  if (refundFailed) {
258
256
  // Refund accounting is isolated per caller; reentry cannot increase the retained credit.
259
- // slither-disable-next-line reentrancy-benign
260
257
  _retainTransportPaymentRefund({account: _msgSender(), amount: refundAmount});
261
258
  emit TransportPaymentRefundFailed({recipient: _msgSender(), amount: refundAmount});
262
259
  }
@@ -281,15 +278,15 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
281
278
  ///
282
279
  /// Example: ETH mainnet (native = ETH) <-> Celo (native = CELO, ETH is an ERC-20).
283
280
  /// - On mainnet: `mapToken({localToken: NATIVE_TOKEN, remoteToken: celoETH_address})`
284
- /// - Sending: `_sendRootOverAMB` wraps native ETH -> WETH, bridges WETH via CCIP.
285
- /// - Receiving: `ccipReceive` checks `root.token == NATIVE_TOKEN` to decide whether to unwrap WETH -> ETH.
281
+ /// - Sending: `_sendRootOverAMB` wraps native tokens, bridges them via CCIP.
282
+ /// - Receiving: `ccipReceive` checks `root.token == NATIVE_TOKEN` to decide whether to unwrap.
286
283
  /// If `root.token` is an ERC-20 address (like celoETH), no unwrap occurs — tokens stay as ERC-20.
287
284
  ///
288
285
  /// The base class restriction (`NATIVE_TOKEN` can only map to `NATIVE_TOKEN` or `address(0)`) is intentionally
289
286
  /// removed here. The base class retains that restriction for OP/Arbitrum where both chains share ETH as native.
290
287
  function _validateTokenMapping(JBTokenMapping calldata map) internal pure virtual override {
291
288
  // Enforce a reasonable minimum gas limit for bridging. A minimum which is too low could lead to the loss of
292
- // funds. CCIP wraps native tokens to WETH before bridging (see `_sendRootOverAMB`), so ALL tokens —
289
+ // funds. CCIP wraps native tokens before bridging (see `_sendRootOverAMB`), so ALL tokens —
293
290
  // including native — need sufficient gas for an ERC-20 transfer on the remote chain.
294
291
  if (map.minGas < MESSENGER_ERC20_MIN_GAS_LIMIT) {
295
292
  revert JBSucker_BelowMinGas({minGas: map.minGas, minGasLimit: MESSENGER_ERC20_MIN_GAS_LIMIT});
@@ -20,7 +20,7 @@ import {JBRemoteToken} from "./structs/JBRemoteToken.sol";
20
20
  import {JBTokenMapping} from "./structs/JBTokenMapping.sol";
21
21
 
22
22
  /// @notice A `JBSucker` implementation for Celo — an OP Stack chain with a custom gas token (CELO, not ETH).
23
- /// @dev ETH exists on Celo only as an ERC-20 (WETH). This sucker wraps native ETH → WETH before bridging
23
+ /// @dev ETH exists on Celo only as an ERC-20. This sucker wraps native ETH before bridging
24
24
  /// as ERC-20 via the OP standard bridge, and removes the `NATIVE_TOKEN → NATIVE_TOKEN` restriction so that
25
25
  /// native ETH can map to a remote ERC-20.
26
26
  contract JBCeloSucker is JBOptimismSucker {
@@ -28,8 +28,8 @@ contract JBCeloSucker is JBOptimismSucker {
28
28
  // --------------- public immutable stored properties ---------------- //
29
29
  //*********************************************************************//
30
30
 
31
- /// @notice The wrapped native token (WETH) on the local chain.
32
- IWrappedNativeToken public immutable WRAPPED_NATIVE;
31
+ /// @notice The ERC-20 wrapper for the chain's native token on the local chain.
32
+ IWrappedNativeToken public immutable WRAPPED_NATIVE_TOKEN;
33
33
 
34
34
  //*********************************************************************//
35
35
  // ---------------------------- constructor -------------------------- //
@@ -53,7 +53,7 @@ contract JBCeloSucker is JBOptimismSucker {
53
53
  JBOptimismSucker(deployer, directory, permissions, prices, tokens, feeProjectId, registry, trustedForwarder)
54
54
  {
55
55
  // Fetch the wrapped native token by doing a callback to the deployer contract.
56
- WRAPPED_NATIVE = JBCeloSuckerDeployer(deployer).wrappedNative();
56
+ WRAPPED_NATIVE_TOKEN = JBCeloSuckerDeployer(deployer).wrappedNative();
57
57
  }
58
58
 
59
59
  //*********************************************************************//
@@ -73,27 +73,26 @@ contract JBCeloSucker is JBOptimismSucker {
73
73
  // --------------------- internal transactions ----------------------- //
74
74
  //*********************************************************************//
75
75
 
76
- /// @notice Unwraps WETH native ETH before adding to the project's balance.
77
- /// @dev When tokens are bridged from Celo → L1 via the OP bridge, L1 WETH (ERC-20) is released to the sucker.
78
- /// But the L1 project's terminal accepts native ETH (NATIVE_TOKEN), not WETH. This override unwraps the WETH
76
+ /// @notice Unwraps wrapped native tokens before adding to the project's balance.
77
+ /// @dev When tokens are bridged from Celo → L1 via the OP bridge, wrapped native tokens (ERC-20) are released to
78
+ /// the sucker. But the L1 project's terminal accepts native ETH (NATIVE_TOKEN), not the wrapped form. This override
79
+ /// unwraps
79
80
  /// and adds native ETH to the project's balance.
80
81
  /// @param token The terminal token to add to the project's balance.
81
82
  /// @param amount The amount of terminal tokens to add to the project's balance.
82
83
  /// @param cachedProjectId The cached project ID to avoid redundant storage reads.
83
84
  function _addToBalance(address token, uint256 amount, uint256 cachedProjectId) internal override {
84
- if (token == address(WRAPPED_NATIVE)) {
85
- // Check addable amount against WETH balance before unwrapping.
85
+ if (token == address(WRAPPED_NATIVE_TOKEN)) {
86
+ // Check addable amount against wrapped native token balance before unwrapping.
86
87
  uint256 addableAmount = amountToAddToBalanceOf(token);
87
88
  if (amount > addableAmount) {
88
89
  revert JBSucker_InsufficientBalance({amount: amount, balance: addableAmount});
89
90
  }
90
91
 
91
- // Unwrap WETH → native ETH.
92
- // slither-disable-next-line calls-loop
93
- WRAPPED_NATIVE.withdraw(amount);
92
+ // Unwrap wrapped native tokens → native tokens.
93
+ WRAPPED_NATIVE_TOKEN.withdraw(amount);
94
94
 
95
95
  // Get the project's primary terminal for native token.
96
- // slither-disable-next-line calls-loop
97
96
  IJBTerminal terminal =
98
97
  DIRECTORY.primaryTerminalOf({projectId: cachedProjectId, token: JBConstants.NATIVE_TOKEN});
99
98
 
@@ -102,7 +101,6 @@ contract JBCeloSucker is JBOptimismSucker {
102
101
  }
103
102
 
104
103
  // Add native ETH to the project's balance.
105
- // slither-disable-next-line arbitrary-send-eth,calls-loop
106
104
  terminal.addToBalanceOf{value: amount}({
107
105
  projectId: cachedProjectId,
108
106
  token: JBConstants.NATIVE_TOKEN,
@@ -118,7 +116,7 @@ contract JBCeloSucker is JBOptimismSucker {
118
116
 
119
117
  /// @notice Use the `OPMESSENGER` to send the outbox tree for the `token` and the corresponding funds to the peer
120
118
  /// over the `OPBRIDGE`.
121
- /// @dev For Celo, native ETH is wrapped to WETH and bridged as ERC-20. The messenger message is sent with
119
+ /// @dev For Celo, native ETH is wrapped and bridged as ERC-20. The messenger message is sent with
122
120
  /// `nativeValue = 0` because Celo's native token is CELO (not ETH), so we never attach ETH as msg.value.
123
121
  /// @param transportPayment the amount of `msg.value` that is going to get paid for sending this message.
124
122
  /// @param token The token to bridge the outbox tree for.
@@ -139,28 +137,25 @@ contract JBCeloSucker is JBOptimismSucker {
139
137
 
140
138
  // Revert if there's a `msg.value`. The OP bridge does not expect to be paid.
141
139
  if (transportPayment != 0) {
142
- revert JBSucker_UnexpectedMsgValue(transportPayment);
140
+ revert JBSucker_UnexpectedMsgValue({value: transportPayment});
143
141
  }
144
142
 
145
143
  // Cache peer address to avoid redundant calls.
146
144
  address peerAddress = _toAddress(peer());
147
145
 
148
146
  if (amount != 0) {
149
- // Determine the local token to bridge — native ETH is wrapped to WETH first.
147
+ // Determine the local token to bridge — native ETH is wrapped first.
150
148
  address bridgeToken = token;
151
149
  if (token == JBConstants.NATIVE_TOKEN) {
152
- // Wrap native ETH → WETH so it can be bridged as ERC-20.
153
- // slither-disable-next-line arbitrary-send-eth,calls-loop
154
- WRAPPED_NATIVE.deposit{value: amount}();
155
- bridgeToken = address(WRAPPED_NATIVE);
150
+ // Wrap native tokens so they can be bridged as ERC-20.
151
+ WRAPPED_NATIVE_TOKEN.deposit{value: amount}();
152
+ bridgeToken = address(WRAPPED_NATIVE_TOKEN);
156
153
  }
157
154
 
158
155
  // Approve the bridge to spend the token.
159
- // slither-disable-next-line reentrancy-events
160
156
  SafeERC20.forceApprove({token: IERC20(bridgeToken), spender: address(OPBRIDGE), value: amount});
161
157
 
162
158
  // Bridge the ERC-20 token to the peer.
163
- // slither-disable-next-line reentrancy-events,calls-loop
164
159
  OPBRIDGE.bridgeERC20To({
165
160
  localToken: bridgeToken,
166
161
  remoteToken: _toAddress(remoteToken.addr),
@@ -174,7 +169,6 @@ contract JBCeloSucker is JBOptimismSucker {
174
169
  // Send the messenger message with nativeValue = 0.
175
170
  // Celo's native token is CELO, not ETH — we never attach ETH as msg.value on the messenger.
176
171
  // On L1, the ETH was already wrapped and bridged as ERC-20 above.
177
- // slither-disable-next-line reentrancy-events,calls-loop
178
172
  OPMESSENGER.sendMessage({
179
173
  target: peerAddress,
180
174
  message: abi.encodeCall(JBSucker.fromRemote, (message)),
@@ -183,11 +177,11 @@ contract JBCeloSucker is JBOptimismSucker {
183
177
  }
184
178
 
185
179
  /// @notice Allow `NATIVE_TOKEN` to map to any remote token (not just `NATIVE_TOKEN`).
186
- /// @dev Celo uses CELO as native gas token. ETH is an ERC-20 on Celo (WETH). So `NATIVE_TOKEN` on L1
180
+ /// @dev Celo uses CELO as native gas token. ETH is an ERC-20 on Celo. So `NATIVE_TOKEN` on L1
187
181
  /// maps to an ERC-20 address on Celo, not to `NATIVE_TOKEN`. The base class restriction is removed.
188
182
  function _validateTokenMapping(JBTokenMapping calldata map) internal pure virtual override {
189
183
  // Enforce a reasonable minimum gas limit for bridging. Since we always bridge as ERC-20
190
- // (wrapping native ETH to WETH), all tokens need sufficient gas for an ERC-20 transfer.
184
+ // (wrapping native tokens), all tokens need sufficient gas for an ERC-20 transfer.
191
185
  if (map.minGas < MESSENGER_ERC20_MIN_GAS_LIMIT) {
192
186
  revert JBSucker_BelowMinGas({minGas: map.minGas, minGasLimit: MESSENGER_ERC20_MIN_GAS_LIMIT});
193
187
  }
@@ -105,7 +105,7 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
105
105
 
106
106
  // Revert if there's a `msg.value`. The OP bridge does not expect to be paid.
107
107
  if (transportPayment != 0) {
108
- revert JBSucker_UnexpectedMsgValue(transportPayment);
108
+ revert JBSucker_UnexpectedMsgValue({value: transportPayment});
109
109
  }
110
110
 
111
111
  // Cache peer address to avoid redundant calls.
@@ -115,11 +115,9 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
115
115
  // If the amount is `0` then we do not need to bridge any ERC20.
116
116
  if (token != JBConstants.NATIVE_TOKEN && amount != 0) {
117
117
  // Approve the tokens being bridged.
118
- // slither-disable-next-line reentrancy-events
119
118
  SafeERC20.forceApprove({token: IERC20(token), spender: address(OPBRIDGE), value: amount});
120
119
 
121
120
  // Bridge the tokens to the peer sucker. Convert bytes32 types to address at the OP Bridge API boundary.
122
- // slither-disable-next-line reentrancy-events,calls-loop
123
121
  OPBRIDGE.bridgeERC20To({
124
122
  localToken: token,
125
123
  remoteToken: _toAddress(remoteToken.addr),
@@ -134,7 +132,6 @@ contract JBOptimismSucker is JBSucker, IJBOptimismSucker {
134
132
  }
135
133
 
136
134
  // Send the message to the peer with the reclaimed ETH.
137
- // slither-disable-next-line arbitrary-send-eth,reentrancy-events,calls-loop
138
135
  OPMESSENGER.sendMessage{value: nativeValue}({
139
136
  target: peerAddress,
140
137
  message: abi.encodeCall(JBSucker.fromRemote, (message)),