@bananapus/suckers-v6 0.0.40 → 0.0.41
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/package.json +2 -2
- package/src/JBSwapCCIPSucker.sol +52 -37
- package/src/libraries/JBSwapPoolLib.sol +21 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/suckers-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@arbitrum/nitro-contracts": "3.2.0",
|
|
33
|
-
"@bananapus/core-v6": "^0.0.
|
|
33
|
+
"@bananapus/core-v6": "^0.0.48",
|
|
34
34
|
"@bananapus/permission-ids-v6": "^0.0.25",
|
|
35
35
|
"@chainlink/contracts-ccip": "1.6.4",
|
|
36
36
|
"@chainlink/local": "0.2.7",
|
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -91,8 +91,11 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
91
91
|
error JBSwapCCIPSucker_InvalidBridgeToken(address bridgeToken, address wrappedNativeToken);
|
|
92
92
|
error JBSwapCCIPSucker_NoPendingSwap(address localToken, uint64 nonce, bool retrySwapLocked);
|
|
93
93
|
error JBSwapCCIPSucker_OnlySelf(address caller, address expected);
|
|
94
|
+
error JBSwapCCIPSucker_PositiveRootWithoutDelivery(uint256 rootAmount);
|
|
94
95
|
error JBSwapCCIPSucker_SwapFailed(address tokenIn, address tokenOut, uint256 amountIn);
|
|
95
96
|
error JBSwapCCIPSucker_SwapPending(uint64 nonce);
|
|
97
|
+
error JBSwapCCIPSucker_UnexpectedDeliveredTokens(uint256 count);
|
|
98
|
+
error JBSwapCCIPSucker_WrongDeliveredToken(address delivered, address expected);
|
|
96
99
|
|
|
97
100
|
//*********************************************************************//
|
|
98
101
|
// ------------------------------ events ----------------------------- //
|
|
@@ -278,22 +281,50 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
278
281
|
abi.decode(payload, (JBMessageRoot, uint256, uint256));
|
|
279
282
|
|
|
280
283
|
address localToken = _toAddress(root.token);
|
|
284
|
+
uint64 nonce = root.remoteRoot.nonce;
|
|
285
|
+
uint256 leafTotal = root.amount;
|
|
281
286
|
uint256 localAmount;
|
|
282
287
|
bool swapFailed;
|
|
288
|
+
// Cache the single delivered entry once so subsequent branches reuse it without re-indexing
|
|
289
|
+
// calldata. `deliveredAmount > 0` later implies a delivery was present.
|
|
290
|
+
address deliveredToken;
|
|
291
|
+
uint256 deliveredAmount;
|
|
292
|
+
{
|
|
293
|
+
// Send-side guarantees: at most one entry in `destTokenAmounts` (length 0 for zero-value
|
|
294
|
+
// batches, length 1 for value-bearing batches), and when present the delivered token is
|
|
295
|
+
// `BRIDGE_TOKEN`. Refuse anything that deviates so a peer compromise or a malformed CCIP
|
|
296
|
+
// delivery cannot register positive root accounting against zero or wrong-token backing.
|
|
297
|
+
uint256 deliveryCount = any2EvmMessage.destTokenAmounts.length;
|
|
298
|
+
if (deliveryCount > 1) {
|
|
299
|
+
revert JBSwapCCIPSucker_UnexpectedDeliveredTokens(deliveryCount);
|
|
300
|
+
}
|
|
301
|
+
if (deliveryCount == 0) {
|
|
302
|
+
if (leafTotal > 0) revert JBSwapCCIPSucker_PositiveRootWithoutDelivery(leafTotal);
|
|
303
|
+
} else {
|
|
304
|
+
Client.EVMTokenAmount calldata delivered = any2EvmMessage.destTokenAmounts[0];
|
|
305
|
+
deliveredToken = delivered.token;
|
|
306
|
+
deliveredAmount = delivered.amount;
|
|
307
|
+
if (deliveredToken != address(BRIDGE_TOKEN)) {
|
|
308
|
+
revert JBSwapCCIPSucker_WrongDeliveredToken({
|
|
309
|
+
delivered: deliveredToken, expected: address(BRIDGE_TOKEN)
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
283
314
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (localToken == address(BRIDGE_TOKEN) || localToken ==
|
|
315
|
+
// After the validation block above, `deliveredToken != address(0)` iff a delivery was present,
|
|
316
|
+
// because the invariants ensure it equals `BRIDGE_TOKEN` (a non-zero ERC-20) whenever there is one.
|
|
317
|
+
if (deliveredToken != address(0)) {
|
|
318
|
+
if (localToken == address(BRIDGE_TOKEN) || localToken == deliveredToken) {
|
|
288
319
|
// No swap needed — bridge token IS the local token.
|
|
289
|
-
localAmount =
|
|
320
|
+
localAmount = deliveredAmount;
|
|
290
321
|
} else {
|
|
291
322
|
// Swap bridge token -> local token via best V3/V4 pool.
|
|
292
323
|
// Wrapped in try-catch so a swap failure doesn't revert the entire CCIP message
|
|
293
324
|
// (which would leave tokens stuck in the OffRamp). On failure, bridge tokens are
|
|
294
325
|
// stored for later retry via `retrySwap` (written below, after nonce validation).
|
|
295
326
|
try this.executeSwapExternal({
|
|
296
|
-
tokenIn:
|
|
327
|
+
tokenIn: deliveredToken, tokenOut: localToken, amount: deliveredAmount
|
|
297
328
|
}) returns (
|
|
298
329
|
uint256 swapped
|
|
299
330
|
) {
|
|
@@ -321,28 +352,23 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
321
352
|
// Detect "already seen" without extra storage: a nonce has been processed if it has
|
|
322
353
|
// either a batch range (batchEnd > 0) or a conversion rate / pending swap recorded.
|
|
323
354
|
if (
|
|
324
|
-
_batchEndOf[localToken][
|
|
325
|
-
&&
|
|
326
|
-
&& pendingSwapOf[localToken][root.remoteRoot.nonce].leafTotal == 0
|
|
355
|
+
_batchEndOf[localToken][nonce] == 0 && _conversionRateOf[localToken][nonce].leafTotal == 0
|
|
356
|
+
&& pendingSwapOf[localToken][nonce].leafTotal == 0
|
|
327
357
|
) {
|
|
328
358
|
// Record the batch range so _findNonceForLeafIndex can resolve leaf ownership
|
|
329
359
|
// independently of nonce ordering. Each nonce is self-describing: [start, end).
|
|
330
360
|
if (batchEnd > 0) {
|
|
331
|
-
_batchStartOf[localToken][
|
|
332
|
-
_batchEndOf[localToken][
|
|
333
|
-
if (
|
|
334
|
-
_highestReceivedNonce[localToken] =
|
|
361
|
+
_batchStartOf[localToken][nonce] = batchStart;
|
|
362
|
+
_batchEndOf[localToken][nonce] = batchEnd;
|
|
363
|
+
if (nonce > _highestReceivedNonce[localToken]) {
|
|
364
|
+
_highestReceivedNonce[localToken] = nonce;
|
|
335
365
|
}
|
|
336
366
|
}
|
|
337
367
|
|
|
338
368
|
// Store pendingSwapOf for failed swaps now that nonce is validated.
|
|
339
369
|
if (swapFailed) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
bridgeToken: failedTokenAmount.token,
|
|
343
|
-
bridgeAmount: failedTokenAmount.amount,
|
|
344
|
-
leafTotal: root.amount
|
|
345
|
-
});
|
|
370
|
+
pendingSwapOf[localToken][nonce] =
|
|
371
|
+
PendingSwap({bridgeToken: deliveredToken, bridgeAmount: deliveredAmount, leafTotal: leafTotal});
|
|
346
372
|
}
|
|
347
373
|
|
|
348
374
|
// Zero-output swap guard: When a swap succeeds but returns zero local tokens, the
|
|
@@ -354,25 +380,14 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
354
380
|
// Route zero-output swaps into `pendingSwapOf` so the swap can be retried via
|
|
355
381
|
// `retrySwap` once pool conditions improve. Only store the conversion rate when
|
|
356
382
|
// the swap produced a positive local amount.
|
|
357
|
-
if (
|
|
358
|
-
if (localAmount == 0 &&
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
// normally — there is nothing to retry.
|
|
363
|
-
if (zeroSwapTokenAmount.amount > 0) {
|
|
364
|
-
pendingSwapOf[localToken][root.remoteRoot.nonce] = PendingSwap({
|
|
365
|
-
bridgeToken: zeroSwapTokenAmount.token,
|
|
366
|
-
bridgeAmount: zeroSwapTokenAmount.amount,
|
|
367
|
-
leafTotal: root.amount
|
|
368
|
-
});
|
|
369
|
-
} else {
|
|
370
|
-
_conversionRateOf[localToken][root.remoteRoot.nonce] =
|
|
371
|
-
ConversionRate({leafTotal: root.amount, localTotal: 0});
|
|
372
|
-
}
|
|
383
|
+
if (leafTotal > 0 && !swapFailed) {
|
|
384
|
+
if (localAmount == 0 && deliveredAmount > 0) {
|
|
385
|
+
pendingSwapOf[localToken][nonce] = PendingSwap({
|
|
386
|
+
bridgeToken: deliveredToken, bridgeAmount: deliveredAmount, leafTotal: leafTotal
|
|
387
|
+
});
|
|
373
388
|
} else {
|
|
374
|
-
_conversionRateOf[localToken][
|
|
375
|
-
ConversionRate({leafTotal:
|
|
389
|
+
_conversionRateOf[localToken][nonce] =
|
|
390
|
+
ConversionRate({leafTotal: leafTotal, localTotal: localAmount});
|
|
376
391
|
}
|
|
377
392
|
}
|
|
378
393
|
}
|
|
@@ -132,6 +132,7 @@ library JBSwapPoolLib {
|
|
|
132
132
|
config: config,
|
|
133
133
|
key: v4Key,
|
|
134
134
|
normalizedTokenIn: normalizedIn,
|
|
135
|
+
originalTokenIn: tokenIn,
|
|
135
136
|
amount: amount,
|
|
136
137
|
minAmountOut: minAmountOut
|
|
137
138
|
});
|
|
@@ -142,6 +143,7 @@ library JBSwapPoolLib {
|
|
|
142
143
|
key: v4Key,
|
|
143
144
|
normalizedTokenIn: normalizedIn,
|
|
144
145
|
normalizedTokenOut: normalizedOut,
|
|
146
|
+
originalTokenIn: tokenIn,
|
|
145
147
|
amount: amount
|
|
146
148
|
});
|
|
147
149
|
}
|
|
@@ -893,6 +895,7 @@ library JBSwapPoolLib {
|
|
|
893
895
|
PoolKey memory key,
|
|
894
896
|
address normalizedTokenIn,
|
|
895
897
|
address normalizedTokenOut,
|
|
898
|
+
address originalTokenIn,
|
|
896
899
|
uint256 amount
|
|
897
900
|
)
|
|
898
901
|
internal
|
|
@@ -909,7 +912,12 @@ library JBSwapPoolLib {
|
|
|
909
912
|
|
|
910
913
|
// Execute the swap through the V4 PoolManager.
|
|
911
914
|
amountOut = _executeV4Swap({
|
|
912
|
-
config: config,
|
|
915
|
+
config: config,
|
|
916
|
+
key: key,
|
|
917
|
+
normalizedTokenIn: normalizedTokenIn,
|
|
918
|
+
originalTokenIn: originalTokenIn,
|
|
919
|
+
amount: amount,
|
|
920
|
+
minAmountOut: minOut
|
|
913
921
|
});
|
|
914
922
|
}
|
|
915
923
|
|
|
@@ -1001,6 +1009,7 @@ library JBSwapPoolLib {
|
|
|
1001
1009
|
SwapConfig memory config,
|
|
1002
1010
|
PoolKey memory key,
|
|
1003
1011
|
address normalizedTokenIn,
|
|
1012
|
+
address originalTokenIn,
|
|
1004
1013
|
uint256 amount,
|
|
1005
1014
|
uint256 minAmountOut
|
|
1006
1015
|
)
|
|
@@ -1013,6 +1022,16 @@ library JBSwapPoolLib {
|
|
|
1013
1022
|
// Determine swap direction based on currency ordering in the pool key.
|
|
1014
1023
|
bool zeroForOne = Currency.unwrap(key.currency0) == v4In;
|
|
1015
1024
|
|
|
1025
|
+
// Tell the unlock callback whether to consume any wrapped-native-token balance the caller may hold.
|
|
1026
|
+
// The pool's input side is native iff the swap input came from the wrapped-native ERC-20 (not the
|
|
1027
|
+
// NATIVE_TOKEN sentinel). If the caller's input was already native (NATIVE_TOKEN sentinel), the caller
|
|
1028
|
+
// holds raw ETH for THIS swap; any wrapped balance it holds is for unrelated reasons (e.g., backing other
|
|
1029
|
+
// claims) and must not be consumed here.
|
|
1030
|
+
address callbackWrappedNativeToken;
|
|
1031
|
+
if (v4In == address(0) && originalTokenIn != JBConstants.NATIVE_TOKEN) {
|
|
1032
|
+
callbackWrappedNativeToken = config.wrappedNativeToken;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1016
1035
|
// Build the encoded unlock data in a scoped block to avoid stack-too-deep.
|
|
1017
1036
|
bytes memory unlockData;
|
|
1018
1037
|
{
|
|
@@ -1027,7 +1046,7 @@ library JBSwapPoolLib {
|
|
|
1027
1046
|
int256 exactInputAmount = -int256(amount);
|
|
1028
1047
|
|
|
1029
1048
|
unlockData = abi.encode(
|
|
1030
|
-
key, zeroForOne, exactInputAmount, sqrtPriceLimitX96, minAmountOut,
|
|
1049
|
+
key, zeroForOne, exactInputAmount, sqrtPriceLimitX96, minAmountOut, callbackWrappedNativeToken
|
|
1031
1050
|
);
|
|
1032
1051
|
}
|
|
1033
1052
|
|