@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/README.md +3 -3
- package/package.json +2 -2
- package/references/operations.md +2 -2
- package/references/runtime.md +1 -1
- package/src/JBArbitrumSucker.sol +5 -17
- package/src/JBCCIPSucker.sol +2 -5
- package/src/JBCeloSucker.sol +1 -8
- package/src/JBOptimismSucker.sol +1 -4
- package/src/JBSucker.sol +53 -61
- package/src/JBSuckerRegistry.sol +4 -22
- package/src/JBSwapCCIPSucker.sol +50 -24
- package/src/deployers/JBArbitrumSuckerDeployer.sol +5 -3
- package/src/deployers/JBCCIPSuckerDeployer.sol +5 -3
- package/src/deployers/JBCeloSuckerDeployer.sol +5 -3
- package/src/deployers/JBOptimismSuckerDeployer.sol +5 -3
- package/src/deployers/JBSuckerDeployer.sol +14 -10
- package/src/deployers/JBSwapCCIPSuckerDeployer.sol +5 -7
- package/src/libraries/ARBAddresses.sol +6 -3
- package/src/libraries/ARBChains.sol +4 -1
- package/src/libraries/CCIPHelper.sol +66 -9
- package/src/libraries/JBCCIPLib.sol +0 -9
- package/src/libraries/JBSuckerLib.sol +2 -13
- package/src/libraries/JBSwapPoolLib.sol +34 -26
- package/src/utils/MerkleLib.sol +3 -5
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
|
-
(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1645
|
-
_sendRootOverAMB(
|
|
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
|
}
|
package/src/JBSuckerRegistry.sol
CHANGED
|
@@ -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.
|
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -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))
|
|
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))
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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.
|