@bananapus/suckers-v6 0.0.36 → 0.0.38

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.36",
3
+ "version": "0.0.38",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,12 +26,12 @@
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",
33
- "@bananapus/core-v6": "0.0.39",
34
- "@bananapus/permission-ids-v6": "0.0.22",
33
+ "@bananapus/core-v6": "^0.0.44",
34
+ "@bananapus/permission-ids-v6": "^0.0.23",
35
35
  "@chainlink/contracts-ccip": "1.6.4",
36
36
  "@chainlink/local": "0.2.7",
37
37
  "@openzeppelin/contracts": "5.6.1",
@@ -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).
@@ -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
 
@@ -225,7 +225,6 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
225
225
  gasLimit += remoteToken.minGas;
226
226
 
227
227
  // Wrap native tokens if needed, build the CCIP token amounts array, and approve the router.
228
- // slither-disable-next-line unused-return
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
  }
@@ -90,11 +90,9 @@ contract JBCeloSucker is JBOptimismSucker {
90
90
  }
91
91
 
92
92
  // Unwrap wrapped native tokens → native tokens.
93
- // slither-disable-next-line calls-loop
94
93
  WRAPPED_NATIVE_TOKEN.withdraw(amount);
95
94
 
96
95
  // Get the project's primary terminal for native token.
97
- // slither-disable-next-line calls-loop
98
96
  IJBTerminal terminal =
99
97
  DIRECTORY.primaryTerminalOf({projectId: cachedProjectId, token: JBConstants.NATIVE_TOKEN});
100
98
 
@@ -103,7 +101,6 @@ contract JBCeloSucker is JBOptimismSucker {
103
101
  }
104
102
 
105
103
  // Add native ETH to the project's balance.
106
- // slither-disable-next-line arbitrary-send-eth,calls-loop
107
104
  terminal.addToBalanceOf{value: amount}({
108
105
  projectId: cachedProjectId,
109
106
  token: JBConstants.NATIVE_TOKEN,
@@ -140,7 +137,7 @@ contract JBCeloSucker is JBOptimismSucker {
140
137
 
141
138
  // Revert if there's a `msg.value`. The OP bridge does not expect to be paid.
142
139
  if (transportPayment != 0) {
143
- revert JBSucker_UnexpectedMsgValue(transportPayment);
140
+ revert JBSucker_UnexpectedMsgValue({value: transportPayment});
144
141
  }
145
142
 
146
143
  // Cache peer address to avoid redundant calls.
@@ -151,17 +148,14 @@ contract JBCeloSucker is JBOptimismSucker {
151
148
  address bridgeToken = token;
152
149
  if (token == JBConstants.NATIVE_TOKEN) {
153
150
  // Wrap native tokens so they can be bridged as ERC-20.
154
- // slither-disable-next-line arbitrary-send-eth,calls-loop
155
151
  WRAPPED_NATIVE_TOKEN.deposit{value: amount}();
156
152
  bridgeToken = address(WRAPPED_NATIVE_TOKEN);
157
153
  }
158
154
 
159
155
  // Approve the bridge to spend the token.
160
- // slither-disable-next-line reentrancy-events
161
156
  SafeERC20.forceApprove({token: IERC20(bridgeToken), spender: address(OPBRIDGE), value: amount});
162
157
 
163
158
  // Bridge the ERC-20 token to the peer.
164
- // slither-disable-next-line reentrancy-events,calls-loop
165
159
  OPBRIDGE.bridgeERC20To({
166
160
  localToken: bridgeToken,
167
161
  remoteToken: _toAddress(remoteToken.addr),
@@ -175,7 +169,6 @@ contract JBCeloSucker is JBOptimismSucker {
175
169
  // Send the messenger message with nativeValue = 0.
176
170
  // Celo's native token is CELO, not ETH — we never attach ETH as msg.value on the messenger.
177
171
  // On L1, the ETH was already wrapped and bridged as ERC-20 above.
178
- // slither-disable-next-line reentrancy-events,calls-loop
179
172
  OPMESSENGER.sendMessage({
180
173
  target: peerAddress,
181
174
  message: abi.encodeCall(JBSucker.fromRemote, (message)),
@@ -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)),