@bananapus/suckers-v6 0.0.71 → 0.0.73
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 +11 -11
- package/package.json +9 -8
- package/references/operations.md +5 -5
- package/references/runtime.md +5 -5
- package/src/JBArbitrumSucker.sol +2 -0
- package/src/JBCCIPSucker.sol +11 -0
- package/src/JBOptimismSucker.sol +1 -0
- package/src/JBSucker.sol +88 -35
- package/src/JBSuckerRegistry.sol +16 -8
- package/src/deployers/JBSuckerDeployer.sol +15 -1
- package/src/interfaces/IJBSucker.sol +3 -2
- package/src/interfaces/IJBSuckerRegistry.sol +1 -2
- package/src/libraries/CCIPHelper.sol +1 -0
- package/src/libraries/JBSuckerLib.sol +5 -8
- package/src/structs/JBInboxTreeRoot.sol +1 -2
- package/src/utils/MerkleLib.sol +3 -1
- package/src/archive/IJBCeloSuckerDeployer.sol +0 -25
- package/src/archive/IJBSwapCCIPSuckerDeployer.sol +0 -28
- package/src/archive/JBCeloSucker.sol +0 -188
- package/src/archive/JBCeloSuckerDeployer.sol +0 -96
- package/src/archive/JBConversionRate.sol +0 -12
- package/src/archive/JBPendingSwap.sol +0 -12
- package/src/archive/JBSwapCCIPSucker.sol +0 -735
- package/src/archive/JBSwapCCIPSuckerDeployer.sol +0 -115
- package/src/archive/JBSwapLib.sol +0 -148
- package/src/archive/JBSwapPoolLib.sol +0 -1129
|
@@ -1,735 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
// Core JB imports.
|
|
5
|
-
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
6
|
-
import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
|
|
7
|
-
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
8
|
-
// CCIP imports.
|
|
9
|
-
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
|
|
10
|
-
|
|
11
|
-
// OpenZeppelin imports.
|
|
12
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
13
|
-
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
14
|
-
|
|
15
|
-
// Uniswap V3 imports.
|
|
16
|
-
import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
|
|
17
|
-
import {IUniswapV3SwapCallback} from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol";
|
|
18
|
-
|
|
19
|
-
// Uniswap V4 imports.
|
|
20
|
-
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
21
|
-
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
|
|
22
|
-
|
|
23
|
-
// Local: contracts.
|
|
24
|
-
import {JBCCIPSucker} from "./JBCCIPSucker.sol";
|
|
25
|
-
|
|
26
|
-
// Local: deployers.
|
|
27
|
-
import {JBSwapCCIPSuckerDeployer} from "./deployers/JBSwapCCIPSuckerDeployer.sol";
|
|
28
|
-
|
|
29
|
-
// Local: interfaces (alphabetized).
|
|
30
|
-
import {IJBSuckerRegistry} from "./interfaces/IJBSuckerRegistry.sol";
|
|
31
|
-
import {IJBSwapCCIPSuckerDeployer} from "./interfaces/IJBSwapCCIPSuckerDeployer.sol";
|
|
32
|
-
import {IWrappedNativeToken} from "./interfaces/IWrappedNativeToken.sol";
|
|
33
|
-
|
|
34
|
-
// Local: libraries.
|
|
35
|
-
import {CCIPHelper} from "./libraries/CCIPHelper.sol";
|
|
36
|
-
import {JBCCIPLib} from "./libraries/JBCCIPLib.sol";
|
|
37
|
-
import {JBSwapPoolLib} from "./libraries/JBSwapPoolLib.sol";
|
|
38
|
-
|
|
39
|
-
// Local: structs (alphabetized).
|
|
40
|
-
import {JBClaim} from "./structs/JBClaim.sol";
|
|
41
|
-
import {JBConversionRate} from "./structs/JBConversionRate.sol";
|
|
42
|
-
import {JBMessageRoot} from "./structs/JBMessageRoot.sol";
|
|
43
|
-
import {JBPendingSwap} from "./structs/JBPendingSwap.sol";
|
|
44
|
-
import {JBRemoteToken} from "./structs/JBRemoteToken.sol";
|
|
45
|
-
|
|
46
|
-
/// @notice A `JBCCIPSucker` extension that swaps between local and bridge tokens using the best
|
|
47
|
-
/// Uniswap V3 or V4 pool before/after CCIP bridging.
|
|
48
|
-
/// @dev Enables cross-currency bridging: e.g., ETH on Ethereum <-> USDC on Tempo.
|
|
49
|
-
/// Discovers the most liquid pool across V3 fee tiers and V4 pool configurations,
|
|
50
|
-
/// then applies TWAP-based quoting with sigmoid slippage protection.
|
|
51
|
-
///
|
|
52
|
-
/// **Cross-denomination claim scaling:** Because the merkle tree leaf amounts are denominated in the
|
|
53
|
-
/// source chain's terminal token, the receiving chain must scale each claim proportionally. This contract
|
|
54
|
-
/// stores an immutable conversion rate per nonce (one per received root batch). The `claim` override
|
|
55
|
-
/// sets the leaf index context, and `_addToBalance` uses it to look up the correct nonce and rate:
|
|
56
|
-
/// `scaledAmount = leafAmount * batchLocal / batchLeaf`. This is ordering-independent — out-of-order
|
|
57
|
-
/// CCIP delivery cannot cause one batch's rate to be applied to another batch's claims.
|
|
58
|
-
///
|
|
59
|
-
/// Flow (Ethereum -> Tempo, ETH -> USDC):
|
|
60
|
-
/// prepare(ETH) -> burn project tokens, cash out ETH from terminal
|
|
61
|
-
/// toRemote(ETH) -> swap ETH->USDC on best V3/V4 pool -> CCIP bridge USDC -> Tempo receives USDC
|
|
62
|
-
///
|
|
63
|
-
/// Flow (Tempo -> Ethereum, USDC -> ETH):
|
|
64
|
-
/// prepare(USDC) -> burn project tokens, cash out USDC from terminal
|
|
65
|
-
/// toRemote(USDC) -> CCIP bridge USDC -> Ethereum receives USDC -> swap USDC->ETH on best V3/V4 pool
|
|
66
|
-
///
|
|
67
|
-
/// **Gas limit configuration**: When mapping tokens for swap suckers via `mapToken`, set
|
|
68
|
-
/// `JBTokenMapping.minGas` to at least 600,000. Combined with the base `MESSENGER_BASE_GAS_LIMIT`
|
|
69
|
-
/// of 300,000, this provides ~900,000 gas for `ccipReceive` — sufficient for V3/V4 swap execution,
|
|
70
|
-
/// TWAP oracle consultation, and slippage computation. Insufficient gas causes the CCIP message to
|
|
71
|
-
/// fail on delivery, requiring manual re-execution via CCIP's ManualExecution mechanism.
|
|
72
|
-
///
|
|
73
|
-
/// **Inbound swap resilience**: If a swap reverts during `ccipReceive` (due to insufficient
|
|
74
|
-
/// liquidity, stale TWAP observations, or extreme price impact), the CCIP message still succeeds.
|
|
75
|
-
/// The unswapped bridge tokens are stored in a `JBPendingSwap` and the merkle root is recorded
|
|
76
|
-
/// normally. Claims for the affected batch are gated until `retrySwap` is called successfully.
|
|
77
|
-
/// Anyone can call `retrySwap` once swap conditions improve.
|
|
78
|
-
///
|
|
79
|
-
/// **No caller-controlled slippage**: Unlike the router terminal (where the caller spends their own
|
|
80
|
-
/// funds and can accept any slippage), here the swap output determines the conversion rate for ALL
|
|
81
|
-
/// claimers of the batch. Caller-controlled `minAmountOut` would allow sandwich attacks that lock
|
|
82
|
-
/// in bad rates for everyone. All swaps (outbound and retry) use TWAP quoting exclusively.
|
|
83
|
-
contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallback {
|
|
84
|
-
using SafeERC20 for IERC20;
|
|
85
|
-
|
|
86
|
-
//*********************************************************************//
|
|
87
|
-
// --------------------------- custom errors ------------------------- //
|
|
88
|
-
//*********************************************************************//
|
|
89
|
-
|
|
90
|
-
error JBSwapCCIPSucker_BatchNotReceived(uint64 nonce);
|
|
91
|
-
error JBSwapCCIPSucker_CallerNotPoolManager(address caller);
|
|
92
|
-
error JBSwapCCIPSucker_DuplicateBatch(uint64 nonce);
|
|
93
|
-
error JBSwapCCIPSucker_InvalidBridgeToken(address bridgeToken, address wrappedNativeToken);
|
|
94
|
-
error JBSwapCCIPSucker_NoPendingSwap(address localToken, uint64 nonce, bool retrySwapLocked);
|
|
95
|
-
error JBSwapCCIPSucker_OnlySelf(address caller, address expected);
|
|
96
|
-
error JBSwapCCIPSucker_PositiveRootWithoutDelivery(uint256 rootAmount);
|
|
97
|
-
error JBSwapCCIPSucker_SwapFailed(address tokenIn, address tokenOut, uint256 amountIn);
|
|
98
|
-
error JBSwapCCIPSucker_SwapPending(uint64 nonce);
|
|
99
|
-
error JBSwapCCIPSucker_UnexpectedDeliveredTokens(uint256 count);
|
|
100
|
-
error JBSwapCCIPSucker_WrongDeliveredToken(address delivered, address expected);
|
|
101
|
-
|
|
102
|
-
//*********************************************************************//
|
|
103
|
-
// ------------------------------ events ----------------------------- //
|
|
104
|
-
//*********************************************************************//
|
|
105
|
-
|
|
106
|
-
/// @notice Emitted when a previously failed inbound swap is successfully retried.
|
|
107
|
-
/// @param localToken The local token that the bridge tokens were swapped into.
|
|
108
|
-
/// @param nonce The nonce of the batch whose swap was retried.
|
|
109
|
-
/// @param bridgeAmount The amount of bridge tokens that were swapped.
|
|
110
|
-
/// @param localAmount The amount of local tokens received from the retry swap.
|
|
111
|
-
/// @param caller The address that retried the swap.
|
|
112
|
-
event SwapRetried(
|
|
113
|
-
address indexed localToken, uint64 indexed nonce, uint256 bridgeAmount, uint256 localAmount, address caller
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
//*********************************************************************//
|
|
117
|
-
// --------------- public immutable stored properties ---------------- //
|
|
118
|
-
//*********************************************************************//
|
|
119
|
-
|
|
120
|
-
/// @notice The ERC-20 token used for CCIP bridging (e.g., USDC). Must exist on both chains.
|
|
121
|
-
IERC20 public immutable BRIDGE_TOKEN;
|
|
122
|
-
|
|
123
|
-
/// @notice The Uniswap V4 PoolManager. Can be address(0) if V4 is unavailable on this chain.
|
|
124
|
-
IPoolManager public immutable POOL_MANAGER;
|
|
125
|
-
|
|
126
|
-
/// @notice The Uniswap V4 hook address for pool discovery (optional).
|
|
127
|
-
address public immutable UNIV4_HOOK;
|
|
128
|
-
|
|
129
|
-
/// @notice The Uniswap V3 factory for pool discovery and callback verification. Can be address(0).
|
|
130
|
-
IUniswapV3Factory public immutable V3_FACTORY;
|
|
131
|
-
|
|
132
|
-
/// @notice The ERC-20 wrapper for the chain's native token (e.g. WETH on Ethereum, WCELO on Celo). Used for V3
|
|
133
|
-
/// native swaps.
|
|
134
|
-
IWrappedNativeToken public immutable WRAPPED_NATIVE_TOKEN;
|
|
135
|
-
|
|
136
|
-
//*********************************************************************//
|
|
137
|
-
// ------------------- internal stored properties -------------------- //
|
|
138
|
-
//*********************************************************************//
|
|
139
|
-
|
|
140
|
-
/// @notice Pending (failed) inbound swaps, keyed by local token and batch nonce.
|
|
141
|
-
/// @dev Populated when `ccipReceive` swap fails; cleared when `retrySwap` succeeds.
|
|
142
|
-
/// @custom:param localToken The local token the swap targets.
|
|
143
|
-
/// @custom:param nonce The CCIP nonce identifying the batch.
|
|
144
|
-
mapping(address localToken => mapping(uint64 nonce => JBPendingSwap)) public pendingSwapOf;
|
|
145
|
-
|
|
146
|
-
/// @notice End leaf index (exclusive) for each received root batch, keyed by token and nonce.
|
|
147
|
-
/// @custom:param token The local token address.
|
|
148
|
-
/// @custom:param nonce The CCIP nonce identifying the batch.
|
|
149
|
-
mapping(address token => mapping(uint64 nonce => uint256)) internal _batchEndOf;
|
|
150
|
-
|
|
151
|
-
/// @notice Start leaf index for each received root batch, keyed by token and nonce.
|
|
152
|
-
/// @dev Together with `_batchEndOf`, defines the half-open range [start, end) of leaf indices in each batch.
|
|
153
|
-
/// Self-describing per nonce — no sequential dependency for out-of-order CCIP delivery.
|
|
154
|
-
/// @custom:param token The local token address.
|
|
155
|
-
/// @custom:param nonce The CCIP nonce identifying the batch.
|
|
156
|
-
mapping(address token => mapping(uint64 nonce => uint256)) internal _batchStartOf;
|
|
157
|
-
|
|
158
|
-
/// @notice Conversion rate for each received root batch, keyed by token and nonce.
|
|
159
|
-
/// @custom:param token The local token address.
|
|
160
|
-
/// @custom:param nonce The CCIP nonce identifying the batch.
|
|
161
|
-
mapping(address token => mapping(uint64 nonce => JBConversionRate)) internal _conversionRateOf;
|
|
162
|
-
|
|
163
|
-
/// @notice Count of populated batch nonces per token. Appended exactly once per batch in
|
|
164
|
-
/// `ccipReceive`, so it equals the number of received batches independent of CCIP ordering.
|
|
165
|
-
/// @custom:param token The local token address.
|
|
166
|
-
mapping(address token => uint64) internal _populatedNonceCount;
|
|
167
|
-
|
|
168
|
-
/// @notice Populated batch nonces per token, indexed by insertion order.
|
|
169
|
-
/// @dev `_findNonceForLeafIndex` walks this list directly. That bounds lookup by the number of
|
|
170
|
-
/// received batches, not by the highest nonce, so sparse or out-of-order CCIP delivery cannot
|
|
171
|
-
/// force the claim path to scan empty nonce slots.
|
|
172
|
-
/// @custom:param token The local token address.
|
|
173
|
-
/// @custom:param index The insertion index in [0, _populatedNonceCount[token]).
|
|
174
|
-
mapping(address token => mapping(uint64 index => uint64 nonce)) internal _populatedNonceByIndex;
|
|
175
|
-
|
|
176
|
-
/// @notice Cumulative leaf count at the last `_sendRootOverAMB` call, per token.
|
|
177
|
-
/// @dev Used on the sender side to derive the batch start index for the next send.
|
|
178
|
-
/// @custom:param token The local token address.
|
|
179
|
-
mapping(address token => uint256) internal _lastSentCount;
|
|
180
|
-
|
|
181
|
-
//*********************************************************************//
|
|
182
|
-
// ------------------- transient stored properties ------------------- //
|
|
183
|
-
//*********************************************************************//
|
|
184
|
-
|
|
185
|
-
/// @dev Reentrancy guard for the initial `ccipReceive` swap. Prevents claims from consuming newly received
|
|
186
|
-
/// swap output before the batch's conversion rate has been recorded.
|
|
187
|
-
bool transient _ccipReceiveSwapLocked;
|
|
188
|
-
|
|
189
|
-
/// @notice Leaf index + 1 of the claim currently in progress (set by the `claim` override).
|
|
190
|
-
/// @dev Transient storage — auto-resets to 0 each transaction, saving ~9,800 gas per claim vs SSTORE.
|
|
191
|
-
/// Value 0 means no active claim (bypass scaling); non-zero means leafIndex = value - 1.
|
|
192
|
-
uint256 transient _currentClaimLeafIndex;
|
|
193
|
-
|
|
194
|
-
/// @dev Reentrancy guard for `retrySwap`. Prevents claims from executing during the swap window
|
|
195
|
-
/// (between delete pendingSwapOf and writing the conversion rate), which would allow zero-backed
|
|
196
|
-
/// minting. Also prevents re-entry into `retrySwap` itself. Transient — auto-resets each tx.
|
|
197
|
-
bool transient _retrySwapLocked;
|
|
198
|
-
|
|
199
|
-
//*********************************************************************//
|
|
200
|
-
// ---------------------------- constructor -------------------------- //
|
|
201
|
-
//*********************************************************************//
|
|
202
|
-
|
|
203
|
-
/// @param deployer The deployer that stores chain-specific configuration.
|
|
204
|
-
/// @param directory The directory of terminals and controllers for projects.
|
|
205
|
-
/// @param permissions The permissions contract.
|
|
206
|
-
/// @param tokens The contract that manages token minting and burning.
|
|
207
|
-
/// @param feeProjectId The project ID that receives bridge fees.
|
|
208
|
-
/// @param registry The sucker registry.
|
|
209
|
-
/// @param trustedForwarder The trusted forwarder for ERC-2771 meta-transactions.
|
|
210
|
-
constructor(
|
|
211
|
-
JBSwapCCIPSuckerDeployer deployer,
|
|
212
|
-
IJBDirectory directory,
|
|
213
|
-
IJBPermissions permissions,
|
|
214
|
-
IJBTokens tokens,
|
|
215
|
-
uint256 feeProjectId,
|
|
216
|
-
IJBSuckerRegistry registry,
|
|
217
|
-
address trustedForwarder
|
|
218
|
-
)
|
|
219
|
-
JBCCIPSucker(deployer, directory, permissions, tokens, feeProjectId, registry, trustedForwarder)
|
|
220
|
-
{
|
|
221
|
-
IJBSwapCCIPSuckerDeployer swapDeployer = IJBSwapCCIPSuckerDeployer(address(deployer));
|
|
222
|
-
BRIDGE_TOKEN = swapDeployer.bridgeToken();
|
|
223
|
-
POOL_MANAGER = swapDeployer.poolManager();
|
|
224
|
-
V3_FACTORY = swapDeployer.v3Factory();
|
|
225
|
-
UNIV4_HOOK = swapDeployer.univ4Hook();
|
|
226
|
-
WRAPPED_NATIVE_TOKEN = IWrappedNativeToken(swapDeployer.wrappedNativeToken());
|
|
227
|
-
|
|
228
|
-
if (address(BRIDGE_TOKEN) == address(0)) {
|
|
229
|
-
revert JBSwapCCIPSucker_InvalidBridgeToken({
|
|
230
|
-
bridgeToken: address(BRIDGE_TOKEN), wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN)
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
// BRIDGE_TOKEN must not be the wrapped native token — wrapping and CCIP ERC-20 bridging conflict.
|
|
234
|
-
if (address(BRIDGE_TOKEN) == address(WRAPPED_NATIVE_TOKEN) && address(WRAPPED_NATIVE_TOKEN) != address(0)) {
|
|
235
|
-
revert JBSwapCCIPSucker_InvalidBridgeToken({
|
|
236
|
-
bridgeToken: address(BRIDGE_TOKEN), wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN)
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
// NOTE: V3_FACTORY and POOL_MANAGER can both be address(0) on chains where the local terminal token
|
|
240
|
-
// IS the bridge token (e.g., USDC on Tempo). No swap is ever needed in that case. If a swap IS attempted
|
|
241
|
-
// without swap infra, _discoverPool / _executeSwap will revert at runtime with JBSwapCCIPSucker_NoPool.
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
//*********************************************************************//
|
|
245
|
-
// ------------------------- receive / fallback ---------------------- //
|
|
246
|
-
//*********************************************************************//
|
|
247
|
-
|
|
248
|
-
/// @notice Allow this contract to receive native tokens (from V4 swaps, wrapped-native-token unwrap, and CCIP
|
|
249
|
-
/// refunds).
|
|
250
|
-
receive() external payable override {}
|
|
251
|
-
|
|
252
|
-
//*********************************************************************//
|
|
253
|
-
// --------------------- external transactions ----------------------- //
|
|
254
|
-
//*********************************************************************//
|
|
255
|
-
|
|
256
|
-
/// @notice Override CCIP receive to swap bridge tokens into local tokens and track denomination conversion.
|
|
257
|
-
/// @dev Preserves the parent's typed message discrimination and adds swap logic for ROOT messages.
|
|
258
|
-
/// For ROOT messages: swaps received bridge tokens to local tokens and tracks the leaf-to-local conversion ratio.
|
|
259
|
-
/// @param any2EvmMessage The CCIP message received from the remote chain.
|
|
260
|
-
function ccipReceive(Client.Any2EVMMessage calldata any2EvmMessage) external override {
|
|
261
|
-
// Use msg.sender (not _msgSender()) to prevent ERC2771 spoofing.
|
|
262
|
-
if (msg.sender != address(CCIP_ROUTER)) revert JBSucker_NotPeer(_toBytes32(msg.sender));
|
|
263
|
-
|
|
264
|
-
address origin = abi.decode(any2EvmMessage.sender, (address));
|
|
265
|
-
|
|
266
|
-
if (origin != _toAddress(peer()) || any2EvmMessage.sourceChainSelector != REMOTE_CHAIN_SELECTOR) {
|
|
267
|
-
revert JBSucker_NotPeer({caller: _toBytes32(origin)});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Decode the typed message: abi.encode(uint8 type, bytes payload).
|
|
271
|
-
(uint8 messageType, bytes memory payload) = JBCCIPLib.decodeTypedMessage(any2EvmMessage.data);
|
|
272
|
-
|
|
273
|
-
if (messageType == _CCIP_MSG_TYPE_ROOT) {
|
|
274
|
-
// ROOT message — swap bridge tokens to local tokens before storing the merkle root.
|
|
275
|
-
// Decode the root and batch range [batchStart, batchEnd).
|
|
276
|
-
(JBMessageRoot memory root, uint256 batchStart, uint256 batchEnd) =
|
|
277
|
-
abi.decode(payload, (JBMessageRoot, uint256, uint256));
|
|
278
|
-
|
|
279
|
-
address localToken = _toAddress(root.token);
|
|
280
|
-
uint64 nonce = root.remoteRoot.nonce;
|
|
281
|
-
uint256 leafTotal = root.amount;
|
|
282
|
-
uint256 localAmount;
|
|
283
|
-
bool swapFailed;
|
|
284
|
-
// Cache the single delivered entry once so subsequent branches reuse it without re-indexing
|
|
285
|
-
// calldata. `deliveredAmount > 0` later implies a delivery was present.
|
|
286
|
-
address deliveredToken;
|
|
287
|
-
uint256 deliveredAmount;
|
|
288
|
-
{
|
|
289
|
-
// Send-side guarantees: at most one entry in `destTokenAmounts` (length 0 for zero-value
|
|
290
|
-
// batches, length 1 for value-bearing batches), and when present the delivered token is
|
|
291
|
-
// `BRIDGE_TOKEN`. Refuse anything that deviates so a peer compromise or a malformed CCIP
|
|
292
|
-
// delivery cannot register positive root accounting against zero or wrong-token backing.
|
|
293
|
-
uint256 deliveryCount = any2EvmMessage.destTokenAmounts.length;
|
|
294
|
-
if (deliveryCount > 1) {
|
|
295
|
-
revert JBSwapCCIPSucker_UnexpectedDeliveredTokens(deliveryCount);
|
|
296
|
-
}
|
|
297
|
-
if (deliveryCount == 0) {
|
|
298
|
-
if (leafTotal > 0) revert JBSwapCCIPSucker_PositiveRootWithoutDelivery(leafTotal);
|
|
299
|
-
} else {
|
|
300
|
-
Client.EVMTokenAmount calldata delivered = any2EvmMessage.destTokenAmounts[0];
|
|
301
|
-
deliveredToken = delivered.token;
|
|
302
|
-
deliveredAmount = delivered.amount;
|
|
303
|
-
if (deliveredToken != address(BRIDGE_TOKEN)) {
|
|
304
|
-
revert JBSwapCCIPSucker_WrongDeliveredToken({
|
|
305
|
-
delivered: deliveredToken, expected: address(BRIDGE_TOKEN)
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
// Zero delivery alongside a positive root is structurally indistinguishable from
|
|
309
|
-
// "no delivery + positive root" — both leave the local sucker with nothing to back
|
|
310
|
-
// the leaves the root advertises. Reject so a peer cannot mint a claimable rate
|
|
311
|
-
// that records `leafTotal=N, localTotal=0` and lets later claims withdraw against
|
|
312
|
-
// unrelated balance.
|
|
313
|
-
if (leafTotal > 0 && deliveredAmount == 0) {
|
|
314
|
-
revert JBSwapCCIPSucker_PositiveRootWithoutDelivery(leafTotal);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Detect an already-processed batch before the swap path. The inbox nonce alone cannot be used here:
|
|
320
|
-
// CCIP can deliver nonce 2 before nonce 1, and nonce 1 still needs its self-described batch metadata.
|
|
321
|
-
if (
|
|
322
|
-
_batchEndOf[localToken][nonce] != 0 || _conversionRateOf[localToken][nonce].leafTotal != 0
|
|
323
|
-
|| pendingSwapOf[localToken][nonce].bridgeAmount != 0
|
|
324
|
-
) {
|
|
325
|
-
if (deliveredAmount != 0) {
|
|
326
|
-
revert JBSwapCCIPSucker_DuplicateBatch({nonce: nonce});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// After the validation block above, `deliveredToken != address(0)` iff a delivery was present,
|
|
333
|
-
// because the invariants ensure it equals `BRIDGE_TOKEN` (a non-zero ERC-20) whenever there is one.
|
|
334
|
-
if (deliveredToken != address(0)) {
|
|
335
|
-
if (localToken == address(BRIDGE_TOKEN) || localToken == deliveredToken) {
|
|
336
|
-
// No swap needed — bridge token IS the local token.
|
|
337
|
-
localAmount = deliveredAmount;
|
|
338
|
-
} else {
|
|
339
|
-
// Swap bridge token -> local token via best V3/V4 pool.
|
|
340
|
-
// Wrapped in try-catch so a swap failure doesn't revert the entire CCIP message
|
|
341
|
-
// (which would leave tokens stuck in the OffRamp). On failure, bridge tokens are
|
|
342
|
-
// stored for later retry via `retrySwap` (written below, after nonce validation).
|
|
343
|
-
_ccipReceiveSwapLocked = true;
|
|
344
|
-
try this.executeSwapExternal({
|
|
345
|
-
tokenIn: deliveredToken, tokenOut: localToken, amount: deliveredAmount
|
|
346
|
-
}) returns (
|
|
347
|
-
uint256 swapped
|
|
348
|
-
) {
|
|
349
|
-
_ccipReceiveSwapLocked = false;
|
|
350
|
-
localAmount = swapped;
|
|
351
|
-
} catch {
|
|
352
|
-
_ccipReceiveSwapLocked = false;
|
|
353
|
-
swapFailed = true;
|
|
354
|
-
// localAmount stays 0 — pendingSwapOf and conversion rate are written
|
|
355
|
-
// below, after fromRemote validates the nonce.
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Store the inbox merkle root for later claims.
|
|
361
|
-
// Must be called BEFORE writing batch metadata and conversion rates so that stale
|
|
362
|
-
// (duplicate/replayed) roots that fromRemote silently rejects do not overwrite
|
|
363
|
-
// metadata from the original accepted delivery.
|
|
364
|
-
this.fromRemote(root);
|
|
365
|
-
|
|
366
|
-
// Write batch metadata if this nonce hasn't been seen before.
|
|
367
|
-
// Decoupled from nonce advancement to support out-of-order CCIP delivery:
|
|
368
|
-
// if nonce 2 arrives before nonce 1, fromRemote only advances the inbox for nonce 2,
|
|
369
|
-
// but we still need to record nonce 1's batch metadata when it arrives later.
|
|
370
|
-
// The Merkle tree is append-only, so nonce 1's leaves are provable against nonce 2's root.
|
|
371
|
-
//
|
|
372
|
-
// Detect "already seen" without extra storage: a nonce has been processed if it has
|
|
373
|
-
// either a batch range (batchEnd > 0) or a conversion rate / pending swap recorded.
|
|
374
|
-
// Record the batch range so _findNonceForLeafIndex can resolve leaf ownership
|
|
375
|
-
// independently of nonce ordering. Each nonce is self-describing: [start, end).
|
|
376
|
-
//
|
|
377
|
-
// Only record metadata for batches that carry value (`leafTotal > 0`). The base-sucker
|
|
378
|
-
// `prepare` revert on zero `projectTokenCount` blocks the legitimate spam entry point,
|
|
379
|
-
// but a compromised peer or a `projectTokenCount = 1`-style bypass could still ship a
|
|
380
|
-
// zero-leaf root over the bridge. Skipping the metadata write here ensures such roots
|
|
381
|
-
// cannot inflate `_populatedNonceByIndex` and tax future `_findNonceForLeafIndex`
|
|
382
|
-
// walks. Roots with `leafTotal == 0` carry no claimable leaves, so there is nothing
|
|
383
|
-
// for `_findNonceForLeafIndex` to resolve against them.
|
|
384
|
-
if (batchEnd > 0 && leafTotal > 0) {
|
|
385
|
-
// Record this batch's half-open leaf range `[batchStart, batchEnd)`. Self-
|
|
386
|
-
// describing per-nonce — no implicit chain across nonces — so out-of-order
|
|
387
|
-
// delivery can still resolve a leaf to its batch.
|
|
388
|
-
_batchStartOf[localToken][nonce] = batchStart;
|
|
389
|
-
_batchEndOf[localToken][nonce] = batchEnd;
|
|
390
|
-
|
|
391
|
-
// Append `nonce` to the populated-nonce list for this token. The duplicate guard
|
|
392
|
-
// above fires at most once per (token, nonce), so each populated nonce is appended
|
|
393
|
-
// exactly once — the array stays duplicate-free without extra checks.
|
|
394
|
-
//
|
|
395
|
-
// Reading `_populatedNonceCount[localToken]` first into a local lets us write
|
|
396
|
-
// the new slot and the new count in a single read-modify-write pair (one
|
|
397
|
-
// SLOAD, two SSTOREs to distinct slots). The `unchecked` increment is safe:
|
|
398
|
-
// `priorCount` is bounded by the total number of populated nonces, which is
|
|
399
|
-
// upper-bounded by the CCIP nonce space (`uint64`) — overflow requires more
|
|
400
|
-
// batches than `uint64.max`, which the inbox can never produce.
|
|
401
|
-
uint64 priorCount = _populatedNonceCount[localToken];
|
|
402
|
-
_populatedNonceByIndex[localToken][priorCount] = nonce;
|
|
403
|
-
unchecked {
|
|
404
|
-
_populatedNonceCount[localToken] = priorCount + 1;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Store pendingSwapOf for failed swaps now that nonce is validated.
|
|
409
|
-
if (swapFailed) {
|
|
410
|
-
pendingSwapOf[localToken][nonce] =
|
|
411
|
-
JBPendingSwap({bridgeToken: deliveredToken, bridgeAmount: deliveredAmount, leafTotal: leafTotal});
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Zero-output swap guard: When a swap succeeds but returns zero local tokens, the
|
|
415
|
-
// batch must NOT be marked claimable. Without this guard, `_addToBalance` would see
|
|
416
|
-
// `pendingSwapOf.bridgeAmount == 0` (no pending swap stored) and allow claims to
|
|
417
|
-
// proceed — minting the full bridged project-token amount while adding zero terminal
|
|
418
|
-
// backing, breaking cross-chain solvency.
|
|
419
|
-
//
|
|
420
|
-
// Route zero-output swaps into `pendingSwapOf` so the swap can be retried via
|
|
421
|
-
// `retrySwap` once pool conditions improve. Only store the conversion rate when
|
|
422
|
-
// the swap produced a positive local amount.
|
|
423
|
-
if (leafTotal > 0 && !swapFailed) {
|
|
424
|
-
if (localAmount == 0 && deliveredAmount > 0) {
|
|
425
|
-
pendingSwapOf[localToken][nonce] = JBPendingSwap({
|
|
426
|
-
bridgeToken: deliveredToken, bridgeAmount: deliveredAmount, leafTotal: leafTotal
|
|
427
|
-
});
|
|
428
|
-
} else {
|
|
429
|
-
_conversionRateOf[localToken][nonce] =
|
|
430
|
-
JBConversionRate({leafTotal: leafTotal, localTotal: localAmount});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
} else {
|
|
434
|
-
revert JBCCIPSucker_UnknownMessageType({messageType: messageType});
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/// @notice Uniswap V3 swap callback — delegates to JBSwapPoolLib (via DELEGATECALL) to reduce bytecode.
|
|
439
|
-
/// @param amount0Delta The amount of token0 used for the swap.
|
|
440
|
-
/// @param amount1Delta The amount of token1 used for the swap.
|
|
441
|
-
/// @param data Encoded (originalTokenIn, normalizedTokenIn, normalizedTokenOut).
|
|
442
|
-
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external override {
|
|
443
|
-
JBSwapPoolLib.executeV3SwapCallback({
|
|
444
|
-
v3Factory: V3_FACTORY, amount0Delta: amount0Delta, amount1Delta: amount1Delta, data: data
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/// @notice Uniswap V4 unlock callback — delegates to JBSwapPoolLib (via DELEGATECALL) to reduce bytecode.
|
|
449
|
-
/// @param data Encoded swap parameters from PoolManager.unlock().
|
|
450
|
-
/// @return Encoded output amount.
|
|
451
|
-
function unlockCallback(bytes calldata data) external override returns (bytes memory) {
|
|
452
|
-
if (msg.sender != address(POOL_MANAGER)) revert JBSwapCCIPSucker_CallerNotPoolManager(msg.sender);
|
|
453
|
-
return JBSwapPoolLib.executeV4UnlockCallback({poolManager: POOL_MANAGER, data: data});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/// @notice External swap entry point callable ONLY by this contract. Exists so `ccipReceive` can
|
|
457
|
-
/// wrap the swap in a try-catch (Solidity requires an external call target for try-catch).
|
|
458
|
-
/// @param tokenIn The input token address.
|
|
459
|
-
/// @param tokenOut The output token address.
|
|
460
|
-
/// @param amount The amount of input tokens to swap.
|
|
461
|
-
/// @return amountOut The amount of output tokens received.
|
|
462
|
-
function executeSwapExternal(
|
|
463
|
-
address tokenIn,
|
|
464
|
-
address tokenOut,
|
|
465
|
-
uint256 amount
|
|
466
|
-
)
|
|
467
|
-
external
|
|
468
|
-
returns (uint256 amountOut)
|
|
469
|
-
{
|
|
470
|
-
if (msg.sender != address(this)) {
|
|
471
|
-
revert JBSwapCCIPSucker_OnlySelf({caller: msg.sender, expected: address(this)});
|
|
472
|
-
}
|
|
473
|
-
return _executeSwap({tokenIn: tokenIn, tokenOut: tokenOut, amount: amount});
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/// @notice Retry a previously failed inbound swap. Anyone can call this once swap conditions improve.
|
|
477
|
-
/// @dev On success, updates the conversion rate so claims for this nonce's batch can proceed.
|
|
478
|
-
/// Always uses TWAP quoting — no caller-provided `minAmountOut`. Unlike the router terminal (where the
|
|
479
|
-
/// caller is spending their own funds and can accept any slippage they choose), here the swap output
|
|
480
|
-
/// determines the conversion rate for ALL claimers of this batch. Allowing a caller-controlled minimum
|
|
481
|
-
/// would let an attacker sandwich the swap with `minAmountOut = 1` and lock in a bad rate for everyone.
|
|
482
|
-
/// @param localToken The local token that the bridge tokens should be swapped into.
|
|
483
|
-
/// @param nonce The nonce of the batch whose swap failed.
|
|
484
|
-
function retrySwap(address localToken, uint64 nonce) external {
|
|
485
|
-
// Reentrancy guard: prevents re-entry into retrySwap AND prevents claims from executing
|
|
486
|
-
// during the swap window (which would see the stale {leafTotal > 0, localTotal: 0} rate
|
|
487
|
-
// and mint project tokens backed by zero terminal tokens).
|
|
488
|
-
if (_retrySwapLocked) {
|
|
489
|
-
revert JBSwapCCIPSucker_NoPendingSwap({
|
|
490
|
-
localToken: localToken, nonce: nonce, retrySwapLocked: _retrySwapLocked
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
_retrySwapLocked = true;
|
|
494
|
-
|
|
495
|
-
JBPendingSwap memory pending = pendingSwapOf[localToken][nonce];
|
|
496
|
-
if (pending.bridgeAmount == 0) {
|
|
497
|
-
revert JBSwapCCIPSucker_NoPendingSwap({
|
|
498
|
-
localToken: localToken, nonce: nonce, retrySwapLocked: _retrySwapLocked
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
uint256 localAmount =
|
|
503
|
-
_executeSwapOrRevert({tokenIn: pending.bridgeToken, tokenOut: localToken, amount: pending.bridgeAmount});
|
|
504
|
-
|
|
505
|
-
// Update the conversion rate so claims can proceed, then clear the pending swap.
|
|
506
|
-
_conversionRateOf[localToken][nonce] = JBConversionRate({leafTotal: pending.leafTotal, localTotal: localAmount});
|
|
507
|
-
delete pendingSwapOf[localToken][nonce];
|
|
508
|
-
|
|
509
|
-
_retrySwapLocked = false;
|
|
510
|
-
emit SwapRetried({
|
|
511
|
-
localToken: localToken,
|
|
512
|
-
nonce: nonce,
|
|
513
|
-
bridgeAmount: pending.bridgeAmount,
|
|
514
|
-
localAmount: localAmount,
|
|
515
|
-
caller: _msgSender()
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
//*********************************************************************//
|
|
520
|
-
// ----------------------- public transactions ----------------------- //
|
|
521
|
-
//*********************************************************************//
|
|
522
|
-
|
|
523
|
-
/// @notice Override single claim to set the leaf index context for `_addToBalance` scaling.
|
|
524
|
-
/// @dev The batch `claim(JBClaim[])` calls this in a loop, so it works automatically.
|
|
525
|
-
/// Stores leafIndex + 1 (0 = no active claim sentinel). No reset needed — transient storage auto-clears.
|
|
526
|
-
/// @param claimData The claim data containing the leaf and proof.
|
|
527
|
-
function claim(JBClaim calldata claimData) public override {
|
|
528
|
-
// Block claims during retrySwap to prevent zero-backed minting via reentrancy.
|
|
529
|
-
if (_retrySwapLocked || _ccipReceiveSwapLocked) revert JBSwapCCIPSucker_SwapPending(0);
|
|
530
|
-
_currentClaimLeafIndex = claimData.leaf.index + 1;
|
|
531
|
-
super.claim(claimData);
|
|
532
|
-
// Clear stale transient context to prevent leaking into same-tx emergency exits.
|
|
533
|
-
_currentClaimLeafIndex = 0;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
//*********************************************************************//
|
|
537
|
-
// ---------------------- internal transactions ---------------------- //
|
|
538
|
-
//*********************************************************************//
|
|
539
|
-
|
|
540
|
-
/// @notice Override to scale claim amounts from source-chain denomination to local-chain denomination.
|
|
541
|
-
/// @dev When cross-currency bridging, merkle tree leaf amounts are in the source chain's terminal token
|
|
542
|
-
/// denomination. This override converts them proportionally using the nonce-indexed conversion rate
|
|
543
|
-
/// populated during `ccipReceive`.
|
|
544
|
-
///
|
|
545
|
-
/// **Per-batch isolation**: Each received root stores an immutable conversion rate keyed by nonce.
|
|
546
|
-
/// The `claim` override sets `_currentClaimLeafIndex`, which this function uses to look up the
|
|
547
|
-
/// correct nonce (and thus the correct rate) for the claim. This prevents out-of-order CCIP
|
|
548
|
-
/// delivery from applying the wrong batch's rate to a claim.
|
|
549
|
-
/// @param token The local token address.
|
|
550
|
-
/// @param amount The claim amount in source-chain denomination.
|
|
551
|
-
/// @param cachedProjectId The project ID (cached for gas efficiency).
|
|
552
|
-
function _addToBalance(address token, uint256 amount, uint256 cachedProjectId) internal override {
|
|
553
|
-
if (_currentClaimLeafIndex != 0) {
|
|
554
|
-
uint64 nonce = _findNonceForLeafIndex({token: token, leafIndex: _currentClaimLeafIndex - 1});
|
|
555
|
-
if (nonce != 0) {
|
|
556
|
-
// Gate on pending swaps — if a swap failed and hasn't been retried yet,
|
|
557
|
-
// claims must wait. This check must come BEFORE the leafTotal gate so that
|
|
558
|
-
// failed swaps (where _conversionRateOf was never written) still block claims.
|
|
559
|
-
if (pendingSwapOf[token][nonce].bridgeAmount > 0) {
|
|
560
|
-
revert JBSwapCCIPSucker_SwapPending({nonce: nonce});
|
|
561
|
-
}
|
|
562
|
-
JBConversionRate storage rate = _conversionRateOf[token][nonce];
|
|
563
|
-
if (rate.leafTotal > 0) {
|
|
564
|
-
amount = amount * rate.localTotal / rate.leafTotal;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
super._addToBalance({token: token, amount: amount, cachedProjectId: cachedProjectId});
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/// @notice Override to swap local tokens into bridge tokens before CCIP bridging.
|
|
573
|
-
/// @dev Does NOT modify `suckerMessage.amount` — keeps the original leaf-denomination total so the
|
|
574
|
-
/// receiving chain can use it (along with the actual delivered amount) to compute the proportional
|
|
575
|
-
/// scaling factor for individual claims.
|
|
576
|
-
/// Delegates CCIP message construction to JBCCIPLib (via DELEGATECALL) to reduce bytecode.
|
|
577
|
-
/// @param transportPayment The ETH sent for CCIP fees.
|
|
578
|
-
/// @param index The last leaf index in the current batch.
|
|
579
|
-
/// @param token The local token to bridge.
|
|
580
|
-
/// @param amount The amount of local tokens to bridge.
|
|
581
|
-
/// @param remoteToken The remote token configuration (including minGas).
|
|
582
|
-
/// @param suckerMessage The merkle root message to send to the remote chain.
|
|
583
|
-
// forge-lint: disable-next-line(mixed-case-function)
|
|
584
|
-
function _sendRootOverAMB(
|
|
585
|
-
uint256 transportPayment,
|
|
586
|
-
uint256 index,
|
|
587
|
-
address token,
|
|
588
|
-
uint256 amount,
|
|
589
|
-
JBRemoteToken memory remoteToken,
|
|
590
|
-
JBMessageRoot memory suckerMessage
|
|
591
|
-
)
|
|
592
|
-
internal
|
|
593
|
-
override
|
|
594
|
-
{
|
|
595
|
-
Client.EVMTokenAmount[] memory tokenAmounts;
|
|
596
|
-
bytes memory encodedPayload;
|
|
597
|
-
|
|
598
|
-
{
|
|
599
|
-
uint256 bridgeAmount;
|
|
600
|
-
if (amount == 0) {
|
|
601
|
-
tokenAmounts = new Client.EVMTokenAmount[](0);
|
|
602
|
-
} else {
|
|
603
|
-
address bridgeTokenAddr = address(BRIDGE_TOKEN);
|
|
604
|
-
|
|
605
|
-
if (token == bridgeTokenAddr) {
|
|
606
|
-
bridgeAmount = amount;
|
|
607
|
-
} else {
|
|
608
|
-
// Always use TWAP quoting — no caller-provided minAmountOut. Unlike the router terminal
|
|
609
|
-
// (where the caller spends their own funds), here the swap output sets the conversion
|
|
610
|
-
// rate for ALL claimers of the batch. Caller-controlled slippage would allow sandwich
|
|
611
|
-
// attacks that lock in bad rates for everyone.
|
|
612
|
-
bridgeAmount = _executeSwapOrRevert({tokenIn: token, tokenOut: bridgeTokenAddr, amount: amount});
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
tokenAmounts = new Client.EVMTokenAmount[](1);
|
|
616
|
-
tokenAmounts[0] = Client.EVMTokenAmount({token: bridgeTokenAddr, amount: bridgeAmount});
|
|
617
|
-
BRIDGE_TOKEN.forceApprove({spender: address(CCIP_ROUTER), value: bridgeAmount});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// NOTE: suckerMessage.amount stays as the original leaf-denomination total.
|
|
621
|
-
// Encode batch range [batchStart, batchEnd) so the receiver can resolve leaf ownership
|
|
622
|
-
// per-nonce without requiring contiguous nonce delivery.
|
|
623
|
-
uint256 batchStart = _lastSentCount[token];
|
|
624
|
-
uint256 batchEnd = index + 1;
|
|
625
|
-
_lastSentCount[token] = batchEnd;
|
|
626
|
-
encodedPayload = abi.encode(_CCIP_MSG_TYPE_ROOT, abi.encode(suckerMessage, batchStart, batchEnd));
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
{
|
|
630
|
-
// Determine fee payment mode: native ETH or LINK token.
|
|
631
|
-
address feeToken = transportPayment == 0 ? CCIPHelper.linkOfChain(block.chainid) : address(0);
|
|
632
|
-
|
|
633
|
-
(bool refundFailed, uint256 refundAmount) = JBCCIPLib.sendCCIPMessage({
|
|
634
|
-
ccipRouter: CCIP_ROUTER,
|
|
635
|
-
remoteChainSelector: REMOTE_CHAIN_SELECTOR,
|
|
636
|
-
peerAddress: _toAddress(peer()),
|
|
637
|
-
transportPayment: transportPayment,
|
|
638
|
-
feeToken: feeToken,
|
|
639
|
-
feeTokenPayer: feeToken != address(0) ? _msgSender() : address(0),
|
|
640
|
-
gasLimit: MESSENGER_BASE_GAS_LIMIT + remoteToken.minGas,
|
|
641
|
-
encodedPayload: encodedPayload,
|
|
642
|
-
tokenAmounts: tokenAmounts,
|
|
643
|
-
refundRecipient: _msgSender()
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
if (refundFailed) {
|
|
647
|
-
_retainTransportPaymentRefund({account: _msgSender(), amount: refundAmount});
|
|
648
|
-
emit TransportPaymentRefundFailed({recipient: _msgSender(), amount: refundAmount, caller: _msgSender()});
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
//*********************************************************************//
|
|
654
|
-
// ----------------------- internal helpers -------------------------- //
|
|
655
|
-
//*********************************************************************//
|
|
656
|
-
|
|
657
|
-
/// @notice Execute a swap between two tokens using the best available V3 or V4 pool.
|
|
658
|
-
/// @dev Delegates pool discovery, TWAP quoting, and swap execution to JBSwapPoolLib (via DELEGATECALL).
|
|
659
|
-
/// Swap callbacks (`uniswapV3SwapCallback`, `unlockCallback`) remain on this contract.
|
|
660
|
-
/// Always uses TWAP quoting (minAmountOut = 0) — see contract NatSpec for rationale.
|
|
661
|
-
/// @param tokenIn The input token (raw address, e.g., NATIVE_TOKEN sentinel for ETH).
|
|
662
|
-
/// @param tokenOut The output token (raw address).
|
|
663
|
-
/// @param amount The amount of input tokens to swap.
|
|
664
|
-
/// @return amountOut The amount of output tokens received.
|
|
665
|
-
function _executeSwap(address tokenIn, address tokenOut, uint256 amount) internal returns (uint256 amountOut) {
|
|
666
|
-
return JBSwapPoolLib.executeSwap({
|
|
667
|
-
config: JBSwapPoolLib.SwapConfig({
|
|
668
|
-
v3Factory: V3_FACTORY,
|
|
669
|
-
poolManager: POOL_MANAGER,
|
|
670
|
-
univ4Hook: UNIV4_HOOK,
|
|
671
|
-
wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN)
|
|
672
|
-
}),
|
|
673
|
-
tokenIn: tokenIn,
|
|
674
|
-
tokenOut: tokenOut,
|
|
675
|
-
amount: amount,
|
|
676
|
-
minAmountOut: 0
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/// @notice Execute a swap and revert if it produces no output.
|
|
681
|
-
/// @param tokenIn The input token.
|
|
682
|
-
/// @param tokenOut The output token.
|
|
683
|
-
/// @param amount The input amount.
|
|
684
|
-
/// @return amountOut The output amount.
|
|
685
|
-
function _executeSwapOrRevert(
|
|
686
|
-
address tokenIn,
|
|
687
|
-
address tokenOut,
|
|
688
|
-
uint256 amount
|
|
689
|
-
)
|
|
690
|
-
internal
|
|
691
|
-
returns (uint256 amountOut)
|
|
692
|
-
{
|
|
693
|
-
amountOut = _executeSwap({tokenIn: tokenIn, tokenOut: tokenOut, amount: amount});
|
|
694
|
-
if (amountOut == 0) {
|
|
695
|
-
revert JBSwapCCIPSucker_SwapFailed({tokenIn: tokenIn, tokenOut: tokenOut, amountIn: amount});
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
//*********************************************************************//
|
|
700
|
-
// ----------------------- internal views ---------------------------- //
|
|
701
|
-
//*********************************************************************//
|
|
702
|
-
|
|
703
|
-
/// @notice Find the received nonce whose batch contains the given leaf index.
|
|
704
|
-
/// @dev Walks `_populatedNonceByIndex` instead of `[1, highestNonce]`. The populated list is the
|
|
705
|
-
/// only set that can contain a claimable batch, and it stays compact even when CCIP delivers
|
|
706
|
-
/// nonce 10 before nonce 2. This keeps lookup O(K) where K is received batches, avoids sparse
|
|
707
|
-
/// empty-slot scans, and keeps the deployable bytecode below the EIP-170 size limit.
|
|
708
|
-
/// @param token The local token address.
|
|
709
|
-
/// @param leafIndex The leaf index from the claim.
|
|
710
|
-
/// @return The nonce of the batch containing this leaf, or 0 if no batches have been recorded.
|
|
711
|
-
function _findNonceForLeafIndex(address token, uint256 leafIndex) internal view returns (uint64) {
|
|
712
|
-
// No populated batches for this token means there is no conversion rate to apply. Preserve
|
|
713
|
-
// nonce 0 as the "unbatched" sentinel used by `_addToBalance`'s non-claim path.
|
|
714
|
-
uint64 count = _populatedNonceCount[token];
|
|
715
|
-
if (count == 0) return 0;
|
|
716
|
-
|
|
717
|
-
// Walk only nonces that actually received a batch. The array is insertion-ordered, not
|
|
718
|
-
// sorted, because CCIP can deliver batches out of nonce order; each entry still points to a
|
|
719
|
-
// self-contained `[batchStart, batchEnd)` range written before the append.
|
|
720
|
-
unchecked {
|
|
721
|
-
for (uint64 i; i < count; i++) {
|
|
722
|
-
uint64 nonce = _populatedNonceByIndex[token][i];
|
|
723
|
-
uint256 end = _batchEndOf[token][nonce];
|
|
724
|
-
|
|
725
|
-
// Ranges are non-overlapping across populated nonces. The first hit is therefore
|
|
726
|
-
// the unique conversion-rate batch for this claim leaf.
|
|
727
|
-
if (leafIndex >= _batchStartOf[token][nonce] && leafIndex < end) return nonce;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// Batches exist for the token, but none cover this leaf index; surface the same error used
|
|
732
|
-
// before the compact populated-nonce index was introduced.
|
|
733
|
-
revert JBSwapCCIPSucker_BatchNotReceived({nonce: 0});
|
|
734
|
-
}
|
|
735
|
-
}
|