@bananapus/suckers-v6 0.0.36 → 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/src/JBSucker.sol CHANGED
@@ -67,9 +67,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
67
67
 
68
68
  error JBSucker_AmountExceedsUint128(uint256 amount);
69
69
  error JBSucker_BelowMinGas(uint256 minGas, uint256 minGasLimit);
70
- error JBSucker_Deprecated();
70
+ error JBSucker_Deprecated(JBSuckerState state);
71
71
  error JBSucker_DeprecationTimestampTooSoon(uint256 givenTime, uint256 minimumTime);
72
- error JBSucker_ExpectedMsgValue();
72
+ error JBSucker_ExpectedMsgValue(uint256 msgValue);
73
73
  error JBSucker_IndexOutOfRange(uint256 index);
74
74
  error JBSucker_InsufficientBalance(uint256 amount, uint256 balance);
75
75
  error JBSucker_InsufficientMsgValue(uint256 received, uint256 expected);
@@ -79,16 +79,16 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
79
79
  error JBSucker_LeafAlreadyExecuted(address token, uint256 index);
80
80
  error JBSucker_NoTerminalForToken(uint256 projectId, address token);
81
81
  error JBSucker_NotPeer(bytes32 caller);
82
- error JBSucker_NothingToSend();
82
+ error JBSucker_NothingToSend(address token, uint256 outboxBalance, uint256 treeCount, uint256 numberOfClaimsSent);
83
83
  error JBSucker_NoRetainedToRemoteFee(address account);
84
84
  error JBSucker_NoRetainedTransportPaymentRefund(address account);
85
- error JBSucker_RefundFailed();
85
+ error JBSucker_RefundFailed(address beneficiary, uint256 amount);
86
86
  error JBSucker_TokenAlreadyMapped(address localToken, bytes32 mappedTo);
87
87
  error JBSucker_TokenHasInvalidEmergencyHatchState(address token);
88
88
  error JBSucker_TokenNotMapped(address token);
89
89
  error JBSucker_UnexpectedMsgValue(uint256 value);
90
- error JBSucker_ZeroBeneficiary();
91
- error JBSucker_ZeroERC20Token();
90
+ error JBSucker_ZeroBeneficiary(bytes32 beneficiary);
91
+ error JBSucker_ZeroERC20Token(uint256 projectId);
92
92
 
93
93
  //*********************************************************************//
94
94
  // ------------------------- public constants ------------------------ //
@@ -318,7 +318,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
318
318
  /// @param tokens The terminal tokens to enable the emergency hatch for.
319
319
  function enableEmergencyHatchFor(address[] calldata tokens) external override {
320
320
  // The caller must be the project owner or have the `QUEUE_RULESETS` permission from them.
321
- // slither-disable-next-line calls-loop
322
321
  uint256 _projectId = projectId();
323
322
 
324
323
  _requirePermissionFrom({
@@ -396,7 +395,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
396
395
  // Using _msgSender() would allow a trusted forwarder to spoof the bridge messenger address via the
397
396
  // ERC-2771 calldata suffix.
398
397
  if (!_isRemotePeer(msg.sender)) {
399
- revert JBSucker_NotPeer(_toBytes32(msg.sender));
398
+ revert JBSucker_NotPeer({caller: _toBytes32(msg.sender)});
400
399
  }
401
400
 
402
401
  // Validate the message version to reject incompatible messages.
@@ -479,7 +478,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
479
478
 
480
479
  // Perform each token mapping.
481
480
  for (uint256 i; i < maps.length;) {
482
- // slither-disable-next-line msg-value-loop
483
481
  _mapToken({map: maps[i], transportPaymentValue: numberToDisable > 0 ? msg.value / numberToDisable : 0});
484
482
  unchecked {
485
483
  ++i;
@@ -489,15 +487,13 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
489
487
  // If no tokens were disabled, the full `msg.value` is unused — refund it.
490
488
  if (numberToDisable == 0) {
491
489
  if (msg.value > 0) {
492
- (bool _ok,) = _msgSender().call{value: msg.value}("");
493
- if (!_ok) revert JBSucker_RefundFailed();
490
+ _sendNativeTo({beneficiary: payable(_msgSender()), amount: msg.value});
494
491
  }
495
492
  } else {
496
493
  // Refund any remainder from integer division so dust wei isn't stuck in the contract.
497
494
  uint256 remainder = msg.value % numberToDisable;
498
495
  if (remainder > 0) {
499
496
  // Best-effort refund — don't revert if caller can't accept ETH.
500
- // slither-disable-next-line low-level-calls,unchecked-lowlevel
501
497
  (bool _ok,) = _msgSender().call{value: remainder}("");
502
498
  _ok; // Silence unused-variable warning; failure is intentionally ignored.
503
499
  }
@@ -534,32 +530,27 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
534
530
  {
535
531
  // Make sure the beneficiary is not the zero address, as this would revert when minting on the remote chain.
536
532
  if (beneficiary == bytes32(0)) {
537
- revert JBSucker_ZeroBeneficiary();
533
+ revert JBSucker_ZeroBeneficiary({beneficiary: beneficiary});
538
534
  }
539
535
 
540
536
  // Get the project's token.
541
537
  IERC20 projectToken = IERC20(address(TOKENS.tokenOf(projectId())));
542
538
  if (address(projectToken) == address(0)) {
543
- revert JBSucker_ZeroERC20Token();
539
+ revert JBSucker_ZeroERC20Token({projectId: projectId()});
544
540
  }
545
541
 
546
542
  // Make sure that the token is mapped to a remote token.
547
543
  if (!_remoteTokenFor[token].enabled) {
548
- revert JBSucker_TokenNotMapped(token);
544
+ revert JBSucker_TokenNotMapped({token: token});
549
545
  }
550
546
 
551
547
  // Make sure that the sucker still allows sending new messaged.
552
- JBSuckerState deprecationState = state();
553
- if (deprecationState == JBSuckerState.DEPRECATED || deprecationState == JBSuckerState.SENDING_DISABLED) {
554
- revert JBSucker_Deprecated();
555
- }
548
+ _requireSendingEnabled();
556
549
 
557
550
  // Transfer the tokens to this contract.
558
- // slither-disable-next-line reentrancy-events,reentrancy-benign
559
551
  projectToken.safeTransferFrom({from: _msgSender(), to: address(this), value: projectTokenCount});
560
552
 
561
553
  // Cash out the tokens.
562
- // slither-disable-next-line reentrancy-events,reentrancy-benign
563
554
  uint256 terminalTokenAmount = _pullBackingAssets({
564
555
  projectToken: projectToken, count: projectTokenCount, token: token, minTokensReclaimed: minTokensReclaimed
565
556
  });
@@ -580,12 +571,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
580
571
  function setDeprecation(uint40 timestamp) external override {
581
572
  // As long as the sucker has not started letting users withdrawal, its deprecation time can be
582
573
  // extended/shortened.
583
- JBSuckerState deprecationState = state();
584
- if (deprecationState == JBSuckerState.DEPRECATED || deprecationState == JBSuckerState.SENDING_DISABLED) {
585
- revert JBSucker_Deprecated();
586
- }
574
+ _requireSendingEnabled();
587
575
 
588
- // slither-disable-next-line calls-loop
589
576
  uint256 _projectId = projectId();
590
577
 
591
578
  // The caller must be the project owner or have the `SET_SUCKER_DEPRECATION` permission from them.
@@ -626,13 +613,18 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
626
613
 
627
614
  // Ensure that the token does not have an emergency hatch enabled.
628
615
  if (remoteToken.emergencyHatch) {
629
- revert JBSucker_TokenHasInvalidEmergencyHatchState(token);
616
+ revert JBSucker_TokenHasInvalidEmergencyHatchState({token: token});
630
617
  }
631
618
 
632
619
  // Revert if nothing has changed since the last toRemote() call.
633
620
  JBOutboxTree storage outbox = _outboxOf[token];
634
621
  if (outbox.balance == 0 && outbox.tree.count == outbox.numberOfClaimsSent) {
635
- revert JBSucker_NothingToSend();
622
+ revert JBSucker_NothingToSend({
623
+ token: token,
624
+ outboxBalance: outbox.balance,
625
+ treeCount: outbox.tree.count,
626
+ numberOfClaimsSent: outbox.numberOfClaimsSent
627
+ });
636
628
  }
637
629
 
638
630
  // Read the fee from the registry.
@@ -650,7 +642,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
650
642
  IJBTerminal feeTerminal = _primaryTerminalOf({forProjectId: FEE_PROJECT_ID, token: JBConstants.NATIVE_TOKEN});
651
643
  bool feePaid;
652
644
  if (address(feeTerminal) != address(0) && _toRemoteFee != 0) {
653
- // slither-disable-next-line unused-return,reentrancy-events,reentrancy-benign,arbitrary-send-eth
654
645
  try feeTerminal.pay{value: _toRemoteFee}({
655
646
  projectId: FEE_PROJECT_ID,
656
647
  token: JBConstants.NATIVE_TOKEN,
@@ -827,7 +818,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
827
818
  /// @notice Claim retained failed-fee ETH.
828
819
  /// @param beneficiary The address that should receive the retained ETH.
829
820
  function claimRetainedToRemoteFee(address payable beneficiary) external override {
830
- if (beneficiary == address(0)) revert JBSucker_ZeroBeneficiary();
821
+ if (beneficiary == address(0)) revert JBSucker_ZeroBeneficiary({beneficiary: bytes32(0)});
831
822
 
832
823
  address account = _msgSender();
833
824
  uint256 amount = retainedToRemoteFeeOf[account];
@@ -836,12 +827,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
836
827
  retainedToRemoteFeeOf[account] = 0;
837
828
  retainedToRemoteFeeBalance -= amount;
838
829
 
839
- // slither-disable-next-line arbitrary-send-eth
840
- (bool success,) = beneficiary.call{value: amount}("");
841
- if (!success) revert JBSucker_RefundFailed();
830
+ _sendNativeTo({beneficiary: beneficiary, amount: amount});
842
831
 
843
832
  // State was cleared before sending ETH; the event is emitted after the transfer so failed sends do not log.
844
- // slither-disable-next-line reentrancy-events
845
833
  emit RetainedToRemoteFeeClaimed({
846
834
  account: account, beneficiary: beneficiary, amount: amount, caller: _msgSender()
847
835
  });
@@ -850,7 +838,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
850
838
  /// @notice Claim retained failed transport-payment refund ETH.
851
839
  /// @param beneficiary The address that should receive the retained ETH.
852
840
  function claimRetainedTransportPaymentRefund(address payable beneficiary) external override {
853
- if (beneficiary == address(0)) revert JBSucker_ZeroBeneficiary();
841
+ if (beneficiary == address(0)) revert JBSucker_ZeroBeneficiary({beneficiary: bytes32(0)});
854
842
 
855
843
  address account = _msgSender();
856
844
  uint256 amount = retainedTransportPaymentRefundOf[account];
@@ -859,12 +847,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
859
847
  retainedTransportPaymentRefundOf[account] = 0;
860
848
  retainedTransportPaymentRefundBalance -= amount;
861
849
 
862
- // slither-disable-next-line arbitrary-send-eth
863
- (bool success,) = beneficiary.call{value: amount}("");
864
- if (!success) revert JBSucker_RefundFailed();
850
+ _sendNativeTo({beneficiary: beneficiary, amount: amount});
865
851
 
866
852
  // State was cleared before sending ETH; the event is emitted after the transfer so failed sends do not log.
867
- // slither-disable-next-line reentrancy-events
868
853
  emit RetainedTransportPaymentRefundClaimed({
869
854
  account: account, beneficiary: beneficiary, amount: amount, caller: _msgSender()
870
855
  });
@@ -887,7 +872,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
887
872
  /// @param _projectId The ID of the project (on the local chain) that this sucker is associated with.
888
873
  /// @param remotePeer The remote peer address. Leave zero to use the default deterministic same-address peer.
889
874
  function _initialize(uint256 _projectId, bytes32 remotePeer) internal {
890
- // slither-disable-next-line missing-zero-check
891
875
  _localProjectId = _projectId;
892
876
  _peer = remotePeer;
893
877
  deployer = _msgSender();
@@ -920,7 +904,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
920
904
  IJBTerminal terminal = _primaryTerminalOf({forProjectId: cachedProjectId, token: token});
921
905
 
922
906
  // Revert if no terminal is configured for this token.
923
- // slither-disable-next-line incorrect-equality
924
907
  if (address(terminal) == address(0)) {
925
908
  revert JBSucker_NoTerminalForToken({projectId: cachedProjectId, token: token});
926
909
  }
@@ -928,14 +911,12 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
928
911
  // Perform the `addToBalance` for ERC-20 tokens.
929
912
  if (token != JBConstants.NATIVE_TOKEN) {
930
913
  // Record the balance before the transfer for the sanity check.
931
- // slither-disable-next-line calls-loop
932
914
  uint256 balanceBefore = IERC20(token).balanceOf(address(this));
933
915
 
934
916
  // Approve the terminal to spend the ERC-20 tokens.
935
917
  SafeERC20.forceApprove({token: IERC20(token), spender: address(terminal), value: amount});
936
918
 
937
919
  // Add the tokens to the project's balance.
938
- // slither-disable-next-line calls-loop
939
920
  terminal.addToBalanceOf({
940
921
  projectId: cachedProjectId,
941
922
  token: token,
@@ -946,11 +927,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
946
927
  });
947
928
 
948
929
  // Sanity check: make sure we transferred the full amount.
949
- // slither-disable-next-line calls-loop,incorrect-equality
950
930
  assert(IERC20(token).balanceOf(address(this)) == balanceBefore - amount);
951
931
  } else {
952
932
  // If the token is the native token, send ETH with the call.
953
- // slither-disable-next-line arbitrary-send-eth,calls-loop
954
933
  terminal.addToBalanceOf{value: amount}({
955
934
  projectId: cachedProjectId,
956
935
  token: token,
@@ -988,7 +967,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
988
967
  // before enabling suckers.
989
968
  //
990
969
  // Mint the project tokens for the beneficiary via the project's controller.
991
- // slither-disable-next-line calls-loop,unused-return
992
970
  IJBController(address(DIRECTORY.controllerOf(cachedProjectId)))
993
971
  .mintTokensOf({
994
972
  projectId: cachedProjectId,
@@ -1062,7 +1040,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1062
1040
 
1063
1041
  // Once the emergency hatch for a token is enabled it can't be disabled.
1064
1042
  if (currentMapping.emergencyHatch) {
1065
- revert JBSucker_TokenHasInvalidEmergencyHatchState(token);
1043
+ revert JBSucker_TokenHasInvalidEmergencyHatchState({token: token});
1066
1044
  }
1067
1045
 
1068
1046
  // Validate the token mapping according to the rules of the sucker.
@@ -1072,7 +1050,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1072
1050
  uint256 _projectId = projectId();
1073
1051
 
1074
1052
  // The registry can map during authorized deployment. Otherwise, require the project's mapping permission.
1075
- // slither-disable-next-line calls-loop
1076
1053
  _requirePermissionAllowingOverrideFrom({
1077
1054
  account: PROJECTS.ownerOf(_projectId),
1078
1055
  projectId: _projectId,
@@ -1171,7 +1148,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1171
1148
  });
1172
1149
 
1173
1150
  // Sanity check to make sure we received the expected amount.
1174
- // slither-disable-next-line incorrect-equality
1175
1151
  assert(reclaimedAmount == _balanceOf({token: token, addr: address(this)}) - balanceBefore);
1176
1152
  }
1177
1153
 
@@ -1187,12 +1163,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1187
1163
  if (remoteToken.addr == bytes32(0)) revert JBSucker_TokenNotMapped(token);
1188
1164
 
1189
1165
  // Make sure that the sucker still allows sending new messaged.
1190
- {
1191
- JBSuckerState deprecationState = state();
1192
- if (deprecationState == JBSuckerState.DEPRECATED || deprecationState == JBSuckerState.SENDING_DISABLED) {
1193
- revert JBSucker_Deprecated();
1194
- }
1195
- }
1166
+ _requireSendingEnabled();
1196
1167
 
1197
1168
  // Drain the outbox: read balance/nonce/root, clear balance, advance nonce and numberOfClaimsSent.
1198
1169
  uint256 amount;
@@ -1241,6 +1212,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1241
1212
  });
1242
1213
  }
1243
1214
 
1215
+ /// @notice Send native tokens, reverting if the recipient rejects them.
1216
+ /// @param beneficiary The recipient.
1217
+ /// @param amount The amount to send.
1218
+ function _sendNativeTo(address payable beneficiary, uint256 amount) internal {
1219
+ (bool success,) = beneficiary.call{value: amount}("");
1220
+ if (!success) revert JBSucker_RefundFailed({beneficiary: beneficiary, amount: amount});
1221
+ }
1222
+
1244
1223
  /// @notice Performs the logic to send a message to the peer over the AMB.
1245
1224
  /// @dev This is chain/sucker/bridge specific logic.
1246
1225
  /// @param transportPayment The amount of `msg.value` that is going to get paid for sending this message.
@@ -1386,7 +1365,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1386
1365
  deprecationState != JBSuckerState.DEPRECATED && deprecationState != JBSuckerState.SENDING_DISABLED
1387
1366
  && !_remoteTokenFor[terminalToken].emergencyHatch
1388
1367
  ) {
1389
- revert JBSucker_TokenHasInvalidEmergencyHatchState(terminalToken);
1368
+ revert JBSucker_TokenHasInvalidEmergencyHatchState({token: terminalToken});
1390
1369
  }
1391
1370
 
1392
1371
  // Check that this claim is within the bounds of who can claim.
@@ -1440,7 +1419,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1440
1419
  return addr.balance;
1441
1420
  }
1442
1421
 
1443
- // slither-disable-next-line calls-loop
1444
1422
  return IERC20(token).balanceOf(addr);
1445
1423
  }
1446
1424
 
@@ -1561,10 +1539,17 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1561
1539
  /// @return The primary terminal.
1562
1540
  function _primaryTerminalOf(uint256 forProjectId, address token) internal view returns (IJBTerminal) {
1563
1541
  // Claim processing may call this through a bounded claim list; each lookup must use the live directory state.
1564
- // slither-disable-next-line calls-loop
1565
1542
  return DIRECTORY.primaryTerminalOf({projectId: forProjectId, token: token});
1566
1543
  }
1567
1544
 
1545
+ /// @notice Revert if new outbound sends are disabled or deprecated.
1546
+ function _requireSendingEnabled() internal view {
1547
+ JBSuckerState deprecationState = state();
1548
+ if (deprecationState == JBSuckerState.DEPRECATED || deprecationState == JBSuckerState.SENDING_DISABLED) {
1549
+ revert JBSucker_Deprecated({state: deprecationState});
1550
+ }
1551
+ }
1552
+
1568
1553
  /// @notice Convert a bytes32 remote address to a local EVM address.
1569
1554
  /// @param remote The bytes32 representation of the address.
1570
1555
  /// @return The EVM address (lower 20 bytes).
@@ -1587,7 +1572,7 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1587
1572
  // If the token being mapped is the native token, the `remoteToken` must also be the native token.
1588
1573
  // The native token can also be mapped to the 0 address, which is used to disable native token bridging.
1589
1574
  if (isNative && map.remoteToken != _toBytes32(JBConstants.NATIVE_TOKEN) && map.remoteToken != bytes32(0)) {
1590
- revert JBSucker_InvalidNativeRemoteAddress(map.remoteToken);
1575
+ revert JBSucker_InvalidNativeRemoteAddress({remoteToken: map.remoteToken});
1591
1576
  }
1592
1577
 
1593
1578
  // Enforce a reasonable minimum gas limit for bridging. A minimum which is too low could lead to the loss of
@@ -1641,7 +1626,14 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
1641
1626
  sourceTimestamp: sourceTimestamp
1642
1627
  });
1643
1628
 
1644
- // Send the root over the AMB (positional args slither IR parser crashes on named args here).
1645
- _sendRootOverAMB(transportPayment, index, token, amount, remoteToken, message);
1629
+ // Send the root over the AMB. This overloaded interface call is intentionally positional.
1630
+ _sendRootOverAMB({
1631
+ transportPayment: transportPayment,
1632
+ index: index,
1633
+ token: token,
1634
+ amount: amount,
1635
+ remoteToken: remoteToken,
1636
+ message: message
1637
+ });
1646
1638
  }
1647
1639
  }
@@ -127,7 +127,6 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
127
127
  // Count active suckers.
128
128
  uint256 activeCount;
129
129
  for (uint256 i; i < allSuckers.length;) {
130
- // slither-disable-next-line unused-return
131
130
  (, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
132
131
  if (val == _SUCKER_EXISTS) activeCount++;
133
132
  unchecked {
@@ -139,11 +138,9 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
139
138
  pairs = new JBSuckersPair[](activeCount);
140
139
  uint256 j;
141
140
  for (uint256 i; i < allSuckers.length;) {
142
- // slither-disable-next-line unused-return
143
141
  (, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
144
142
  if (val == _SUCKER_EXISTS) {
145
143
  IJBSucker sucker = IJBSucker(allSuckers[i]);
146
- // slither-disable-next-line calls-loop
147
144
  pairs[j] =
148
145
  JBSuckersPair({local: address(sucker), remote: sucker.peer(), remoteChainId: sucker.peerChainId()});
149
146
  unchecked {
@@ -166,7 +163,6 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
166
163
  // Count active suckers.
167
164
  uint256 activeCount;
168
165
  for (uint256 i; i < allSuckers.length;) {
169
- // slither-disable-next-line unused-return
170
166
  (, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
171
167
  if (val == _SUCKER_EXISTS) activeCount++;
172
168
  unchecked {
@@ -178,7 +174,6 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
178
174
  suckers = new address[](activeCount);
179
175
  uint256 j;
180
176
  for (uint256 i; i < allSuckers.length;) {
181
- // slither-disable-next-line unused-return
182
177
  (, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
183
178
  if (val == _SUCKER_EXISTS) {
184
179
  suckers[j] = allSuckers[i];
@@ -221,15 +216,12 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
221
216
  uint256 chainCount;
222
217
 
223
218
  for (uint256 i; i < len;) {
224
- // slither-disable-next-line unused-return
225
219
  (, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
226
220
  // Include both active and deprecated suckers in aggregate economic views.
227
221
  if (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED) {
228
- // slither-disable-next-line calls-loop
229
222
  try IJBSucker(allSuckers[i]).peerChainBalanceOf(decimals, currency) returns (
230
223
  JBDenominatedAmount memory amt
231
224
  ) {
232
- // slither-disable-next-line calls-loop
233
225
  uint256 chainId = IJBSucker(allSuckers[i]).peerChainId();
234
226
  chainCount = _recordPeerValue({
235
227
  chainIds: chainIds,
@@ -285,15 +277,12 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
285
277
  uint256 chainCount;
286
278
 
287
279
  for (uint256 i; i < len;) {
288
- // slither-disable-next-line unused-return
289
280
  (, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
290
281
  // Include both active and deprecated suckers in aggregate economic views.
291
282
  if (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED) {
292
- // slither-disable-next-line calls-loop
293
283
  try IJBSucker(allSuckers[i]).peerChainSurplusOf(decimals, currency) returns (
294
284
  JBDenominatedAmount memory amt
295
285
  ) {
296
- // slither-disable-next-line calls-loop
297
286
  uint256 chainId = IJBSucker(allSuckers[i]).peerChainId();
298
287
  chainCount = _recordPeerValue({
299
288
  chainIds: chainIds,
@@ -338,13 +327,10 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
338
327
  uint256 chainCount;
339
328
 
340
329
  for (uint256 i; i < len;) {
341
- // slither-disable-next-line unused-return
342
330
  (, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
343
331
  // Include both active and deprecated suckers in aggregate economic views.
344
332
  if (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED) {
345
- // slither-disable-next-line calls-loop
346
333
  try IJBSucker(allSuckers[i]).peerChainTotalSupply() returns (uint256 supply) {
347
- // slither-disable-next-line calls-loop
348
334
  uint256 chainId = IJBSucker(allSuckers[i]).peerChainId();
349
335
  chainCount = _recordPeerValue({
350
336
  chainIds: chainIds,
@@ -518,21 +504,18 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
518
504
 
519
505
  // Make sure the deployer is allowed.
520
506
  if (!suckerDeployerIsAllowed[address(configuration.deployer)]) {
521
- revert JBSuckerRegistry_InvalidDeployer(configuration.deployer);
507
+ revert JBSuckerRegistry_InvalidDeployer({deployer: configuration.deployer});
522
508
  }
523
509
 
524
510
  // Create the sucker.
525
- // slither-disable-next-line reentrancy-event,calls-loop
526
511
  IJBSucker sucker = configuration.deployer
527
512
  .createForSender({localProjectId: projectId, salt: salt, peer: configuration.peer});
528
513
  suckers[i] = address(sucker);
529
514
 
530
515
  // Store the sucker as being deployed for this project.
531
- // slither-disable-next-line unused-return
532
516
  _suckersOf[projectId].set({key: address(sucker), value: _SUCKER_EXISTS});
533
517
 
534
518
  // Map the tokens for the sucker.
535
- // slither-disable-next-line reentrancy-events,calls-loop
536
519
  sucker.mapTokens(configuration.mappings);
537
520
  emit SuckerDeployedFor({
538
521
  projectId: projectId, sucker: address(sucker), configuration: configuration, caller: sender
@@ -552,17 +535,16 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
552
535
  // Sanity check, make sure that the sucker does actually belong to the project.
553
536
  (bool belongsToProject, uint256 val) = _suckersOf[projectId].tryGet(sucker);
554
537
  if (!belongsToProject || val != _SUCKER_EXISTS) {
555
- revert JBSuckerRegistry_SuckerDoesNotBelongToProject(projectId, address(sucker));
538
+ revert JBSuckerRegistry_SuckerDoesNotBelongToProject({projectId: projectId, sucker: address(sucker)});
556
539
  }
557
540
 
558
541
  // Check if the sucker is deprecated.
559
542
  JBSuckerState state = IJBSucker(sucker).state();
560
543
  if (state != JBSuckerState.DEPRECATED) {
561
- revert JBSuckerRegistry_SuckerIsNotDeprecated(address(sucker), state);
544
+ revert JBSuckerRegistry_SuckerIsNotDeprecated({sucker: address(sucker), suckerState: state});
562
545
  }
563
546
 
564
547
  // Mark the sucker as deprecated (retains mint permission, excluded from active listings).
565
- // slither-disable-next-line unused-return
566
548
  _suckersOf[projectId].set(address(sucker), _SUCKER_DEPRECATED);
567
549
  emit SuckerDeprecated({projectId: projectId, sucker: address(sucker), caller: _msgSender()});
568
550
  }
@@ -574,7 +556,7 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
574
556
  if (fee > MAX_TO_REMOTE_FEE) revert JBSuckerRegistry_FeeExceedsMax(fee, MAX_TO_REMOTE_FEE);
575
557
  uint256 oldFee = toRemoteFee;
576
558
  toRemoteFee = fee;
577
- emit ToRemoteFeeChanged(oldFee, fee, _msgSender());
559
+ emit ToRemoteFeeChanged({oldFee: oldFee, newFee: fee, caller: _msgSender()});
578
560
  }
579
561
 
580
562
  /// @notice Removes a sucker deployer from the allowlist.
@@ -88,10 +88,10 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
88
88
 
89
89
  error JBSwapCCIPSucker_BatchNotReceived(uint64 nonce);
90
90
  error JBSwapCCIPSucker_CallerNotPoolManager(address caller);
91
- error JBSwapCCIPSucker_InvalidBridgeToken();
92
- error JBSwapCCIPSucker_NoPendingSwap();
93
- error JBSwapCCIPSucker_OnlySelf();
94
- error JBSwapCCIPSucker_SwapFailed();
91
+ error JBSwapCCIPSucker_InvalidBridgeToken(address bridgeToken, address wrappedNativeToken);
92
+ error JBSwapCCIPSucker_NoPendingSwap(address localToken, uint64 nonce, bool retrySwapLocked);
93
+ error JBSwapCCIPSucker_OnlySelf(address caller, address expected);
94
+ error JBSwapCCIPSucker_SwapFailed(address tokenIn, address tokenOut, uint256 amountIn);
95
95
  error JBSwapCCIPSucker_SwapPending(uint64 nonce);
96
96
 
97
97
  //*********************************************************************//
@@ -226,10 +226,16 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
226
226
  UNIV4_HOOK = swapDeployer.univ4Hook();
227
227
  WRAPPED_NATIVE_TOKEN = IWrappedNativeToken(swapDeployer.weth());
228
228
 
229
- if (address(BRIDGE_TOKEN) == address(0)) revert JBSwapCCIPSucker_InvalidBridgeToken();
229
+ if (address(BRIDGE_TOKEN) == address(0)) {
230
+ revert JBSwapCCIPSucker_InvalidBridgeToken({
231
+ bridgeToken: address(BRIDGE_TOKEN), wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN)
232
+ });
233
+ }
230
234
  // BRIDGE_TOKEN must not be the wrapped native token — wrapping and CCIP ERC-20 bridging conflict.
231
235
  if (address(BRIDGE_TOKEN) == address(WRAPPED_NATIVE_TOKEN) && address(WRAPPED_NATIVE_TOKEN) != address(0)) {
232
- revert JBSwapCCIPSucker_InvalidBridgeToken();
236
+ revert JBSwapCCIPSucker_InvalidBridgeToken({
237
+ bridgeToken: address(BRIDGE_TOKEN), wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN)
238
+ });
233
239
  }
234
240
  // NOTE: V3_FACTORY and POOL_MANAGER can both be address(0) on chains where the local terminal token
235
241
  // IS the bridge token (e.g., USDC on Tempo). No swap is ever needed in that case. If a swap IS attempted
@@ -259,7 +265,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
259
265
  address origin = abi.decode(any2EvmMessage.sender, (address));
260
266
 
261
267
  if (origin != _toAddress(peer()) || any2EvmMessage.sourceChainSelector != REMOTE_CHAIN_SELECTOR) {
262
- revert JBSucker_NotPeer(_toBytes32(origin));
268
+ revert JBSucker_NotPeer({caller: _toBytes32(origin)});
263
269
  }
264
270
 
265
271
  // Decode the typed message: abi.encode(uint8 type, bytes payload).
@@ -286,7 +292,6 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
286
292
  // Wrapped in try-catch so a swap failure doesn't revert the entire CCIP message
287
293
  // (which would leave tokens stuck in the OffRamp). On failure, bridge tokens are
288
294
  // stored for later retry via `retrySwap` (written below, after nonce validation).
289
- // slither-disable-next-line reentrancy-benign,reentrancy-events
290
295
  try this.executeSwapExternal({
291
296
  tokenIn: tokenAmount.token, tokenOut: localToken, amount: tokenAmount.amount
292
297
  }) returns (
@@ -367,7 +372,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
367
372
  }
368
373
  }
369
374
  } else {
370
- revert JBCCIPSucker_UnknownMessageType(messageType);
375
+ revert JBCCIPSucker_UnknownMessageType({messageType: messageType});
371
376
  }
372
377
  }
373
378
 
@@ -403,7 +408,9 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
403
408
  external
404
409
  returns (uint256 amountOut)
405
410
  {
406
- if (msg.sender != address(this)) revert JBSwapCCIPSucker_OnlySelf();
411
+ if (msg.sender != address(this)) {
412
+ revert JBSwapCCIPSucker_OnlySelf({caller: msg.sender, expected: address(this)});
413
+ }
407
414
  return _executeSwap({tokenIn: tokenIn, tokenOut: tokenOut, amount: amount});
408
415
  }
409
416
 
@@ -419,18 +426,22 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
419
426
  // Reentrancy guard: prevents re-entry into retrySwap AND prevents claims from executing
420
427
  // during the swap window (which would see the stale {leafTotal > 0, localTotal: 0} rate
421
428
  // and mint project tokens backed by zero terminal tokens).
422
- if (_retrySwapLocked) revert JBSwapCCIPSucker_NoPendingSwap();
429
+ if (_retrySwapLocked) {
430
+ revert JBSwapCCIPSucker_NoPendingSwap({
431
+ localToken: localToken, nonce: nonce, retrySwapLocked: _retrySwapLocked
432
+ });
433
+ }
423
434
  _retrySwapLocked = true;
424
435
 
425
436
  PendingSwap memory pending = pendingSwapOf[localToken][nonce];
426
- if (pending.bridgeAmount == 0) revert JBSwapCCIPSucker_NoPendingSwap();
437
+ if (pending.bridgeAmount == 0) {
438
+ revert JBSwapCCIPSucker_NoPendingSwap({
439
+ localToken: localToken, nonce: nonce, retrySwapLocked: _retrySwapLocked
440
+ });
441
+ }
427
442
 
428
- // slither-disable-next-line reentrancy-no-eth,reentrancy-benign,reentrancy-events,reentrancy-eth
429
443
  uint256 localAmount =
430
- _executeSwap({tokenIn: pending.bridgeToken, tokenOut: localToken, amount: pending.bridgeAmount});
431
-
432
- // Revert on zero output — matches outbound guard at toRemote.
433
- if (localAmount == 0) revert JBSwapCCIPSucker_SwapFailed();
444
+ _executeSwapOrRevert({tokenIn: pending.bridgeToken, tokenOut: localToken, amount: pending.bridgeAmount});
434
445
 
435
446
  // Update the conversion rate so claims can proceed, then clear the pending swap.
436
447
  _conversionRateOf[localToken][nonce] = ConversionRate({leafTotal: pending.leafTotal, localTotal: localAmount});
@@ -453,9 +464,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
453
464
  function claim(JBClaim calldata claimData) public override {
454
465
  // Block claims during retrySwap to prevent zero-backed minting via reentrancy.
455
466
  if (_retrySwapLocked) revert JBSwapCCIPSucker_SwapPending(0);
456
- // slither-disable-next-line events-maths
457
467
  _currentClaimLeafIndex = claimData.leaf.index + 1;
458
- // slither-disable-next-line reentrancy-eth
459
468
  super.claim(claimData);
460
469
  // Clear stale transient context to prevent leaking into same-tx emergency exits.
461
470
  _currentClaimLeafIndex = 0;
@@ -485,7 +494,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
485
494
  // claims must wait. This check must come BEFORE the leafTotal gate so that
486
495
  // failed swaps (where _conversionRateOf was never written) still block claims.
487
496
  if (pendingSwapOf[token][nonce].bridgeAmount > 0) {
488
- revert JBSwapCCIPSucker_SwapPending(nonce);
497
+ revert JBSwapCCIPSucker_SwapPending({nonce: nonce});
489
498
  }
490
499
  ConversionRate storage rate = _conversionRateOf[token][nonce];
491
500
  if (rate.leafTotal > 0) {
@@ -537,9 +546,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
537
546
  // (where the caller spends their own funds), here the swap output sets the conversion
538
547
  // rate for ALL claimers of the batch. Caller-controlled slippage would allow sandwich
539
548
  // attacks that lock in bad rates for everyone.
540
- // slither-disable-next-line reentrancy-events,reentrancy-benign
541
- bridgeAmount = _executeSwap({tokenIn: token, tokenOut: bridgeTokenAddr, amount: amount});
542
- if (bridgeAmount == 0) revert JBSwapCCIPSucker_SwapFailed();
549
+ bridgeAmount = _executeSwapOrRevert({tokenIn: token, tokenOut: bridgeTokenAddr, amount: amount});
543
550
  }
544
551
 
545
552
  tokenAmounts = new Client.EVMTokenAmount[](1);
@@ -607,6 +614,25 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
607
614
  });
608
615
  }
609
616
 
617
+ /// @notice Execute a swap and revert if it produces no output.
618
+ /// @param tokenIn The input token.
619
+ /// @param tokenOut The output token.
620
+ /// @param amount The input amount.
621
+ /// @return amountOut The output amount.
622
+ function _executeSwapOrRevert(
623
+ address tokenIn,
624
+ address tokenOut,
625
+ uint256 amount
626
+ )
627
+ internal
628
+ returns (uint256 amountOut)
629
+ {
630
+ amountOut = _executeSwap({tokenIn: tokenIn, tokenOut: tokenOut, amount: amount});
631
+ if (amountOut == 0) {
632
+ revert JBSwapCCIPSucker_SwapFailed({tokenIn: tokenIn, tokenOut: tokenOut, amountIn: amount});
633
+ }
634
+ }
635
+
610
636
  //*********************************************************************//
611
637
  // ----------------------- internal views ---------------------------- //
612
638
  //*********************************************************************//
@@ -625,7 +651,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
625
651
  if (_nonceContainsLeaf({token: token, nonce: n, leafIndex: leafIndex})) return n;
626
652
  }
627
653
 
628
- revert JBSwapCCIPSucker_BatchNotReceived(0);
654
+ revert JBSwapCCIPSucker_BatchNotReceived({nonce: 0});
629
655
  }
630
656
 
631
657
  /// @notice Check whether the given nonce's batch range contains the leaf index.