@bananapus/suckers-v6 0.0.38 → 0.0.40
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/JBSuckerRegistry.sol +19 -0
- package/src/JBSwapCCIPSucker.sol +15 -10
- package/src/deployers/JBSwapCCIPSuckerDeployer.sol +4 -4
- package/src/interfaces/IJBSwapCCIPSuckerDeployer.sol +1 -1
- package/src/libraries/JBSuckerLib.sol +2 -2
- package/src/libraries/JBSwapPoolLib.sol +28 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/suckers-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@arbitrum/nitro-contracts": "3.2.0",
|
|
33
33
|
"@bananapus/core-v6": "^0.0.44",
|
|
34
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
34
|
+
"@bananapus/permission-ids-v6": "^0.0.25",
|
|
35
35
|
"@chainlink/contracts-ccip": "1.6.4",
|
|
36
36
|
"@chainlink/local": "0.2.7",
|
|
37
37
|
"@openzeppelin/contracts": "5.6.1",
|
package/src/JBSuckerRegistry.sol
CHANGED
|
@@ -497,6 +497,13 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
497
497
|
// default peer symmetry assumption will not hold.
|
|
498
498
|
salt = keccak256(abi.encode(sender, salt));
|
|
499
499
|
|
|
500
|
+
// Cache the project owner so the explicit-peer gate can check against the original authority, not a
|
|
501
|
+
// delegated operator. The default same-address peering invariant (peer == 0 or peer == address(this))
|
|
502
|
+
// does not need this stronger gate, but a non-symmetric explicit peer authorizes that arbitrary address
|
|
503
|
+
// to deliver outbox roots and mint project tokens — so it must require a permission strictly broader
|
|
504
|
+
// than ops automation's `DEPLOY_SUCKERS`.
|
|
505
|
+
address projectOwner = PROJECTS.ownerOf(projectId);
|
|
506
|
+
|
|
500
507
|
// Iterate through the configurations and deploy the suckers.
|
|
501
508
|
for (uint256 i; i < configurations.length;) {
|
|
502
509
|
// Get the configuration being iterated over.
|
|
@@ -507,6 +514,18 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
507
514
|
revert JBSuckerRegistry_InvalidDeployer({deployer: configuration.deployer});
|
|
508
515
|
}
|
|
509
516
|
|
|
517
|
+
// If the configuration specifies a non-symmetric explicit peer, require the additional
|
|
518
|
+
// `SET_SUCKER_PEER` permission. Default peering (peer == 0 or peer == bytes32 of address(this)) is
|
|
519
|
+
// unaffected. Without this gate, a delegated operator with only `DEPLOY_SUCKERS` could register an
|
|
520
|
+
// attacker peer and use the resulting sucker's mint authority to deliver fabricated outbox roots.
|
|
521
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
522
|
+
bytes32 selfPeer = bytes32(uint256(uint160(address(this))));
|
|
523
|
+
if (configuration.peer != bytes32(0) && configuration.peer != selfPeer) {
|
|
524
|
+
_requirePermissionFrom({
|
|
525
|
+
account: projectOwner, projectId: projectId, permissionId: JBPermissionIds.SET_SUCKER_PEER
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
510
529
|
// Create the sucker.
|
|
511
530
|
IJBSucker sucker = configuration.deployer
|
|
512
531
|
.createForSender({localProjectId: projectId, salt: salt, peer: configuration.peer});
|
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -224,7 +224,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
224
224
|
POOL_MANAGER = swapDeployer.poolManager();
|
|
225
225
|
V3_FACTORY = swapDeployer.v3Factory();
|
|
226
226
|
UNIV4_HOOK = swapDeployer.univ4Hook();
|
|
227
|
-
WRAPPED_NATIVE_TOKEN = IWrappedNativeToken(swapDeployer.
|
|
227
|
+
WRAPPED_NATIVE_TOKEN = IWrappedNativeToken(swapDeployer.wrappedNativeToken());
|
|
228
228
|
|
|
229
229
|
if (address(BRIDGE_TOKEN) == address(0)) {
|
|
230
230
|
revert JBSwapCCIPSucker_InvalidBridgeToken({
|
|
@@ -306,20 +306,25 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
// Capture the inbox nonce before fromRemote to detect whether the root was accepted.
|
|
310
|
-
uint64 inboxNonceBefore = _inboxOf[localToken].nonce;
|
|
311
|
-
|
|
312
309
|
// Store the inbox merkle root for later claims.
|
|
313
310
|
// Must be called BEFORE writing batch metadata and conversion rates so that stale
|
|
314
311
|
// (duplicate/replayed) roots that fromRemote silently rejects do not overwrite
|
|
315
312
|
// metadata from the original accepted delivery.
|
|
316
313
|
this.fromRemote(root);
|
|
317
314
|
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
|
|
315
|
+
// Write batch metadata if this nonce hasn't been seen before.
|
|
316
|
+
// Decoupled from nonce advancement to support out-of-order CCIP delivery:
|
|
317
|
+
// if nonce 2 arrives before nonce 1, fromRemote only advances the inbox for nonce 2,
|
|
318
|
+
// but we still need to record nonce 1's batch metadata when it arrives later.
|
|
319
|
+
// The Merkle tree is append-only, so nonce 1's leaves are provable against nonce 2's root.
|
|
320
|
+
//
|
|
321
|
+
// Detect "already seen" without extra storage: a nonce has been processed if it has
|
|
322
|
+
// either a batch range (batchEnd > 0) or a conversion rate / pending swap recorded.
|
|
323
|
+
if (
|
|
324
|
+
_batchEndOf[localToken][root.remoteRoot.nonce] == 0
|
|
325
|
+
&& _conversionRateOf[localToken][root.remoteRoot.nonce].leafTotal == 0
|
|
326
|
+
&& pendingSwapOf[localToken][root.remoteRoot.nonce].leafTotal == 0
|
|
327
|
+
) {
|
|
323
328
|
// Record the batch range so _findNonceForLeafIndex can resolve leaf ownership
|
|
324
329
|
// independently of nonce ordering. Each nonce is self-describing: [start, end).
|
|
325
330
|
if (batchEnd > 0) {
|
|
@@ -605,7 +610,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
605
610
|
v3Factory: V3_FACTORY,
|
|
606
611
|
poolManager: POOL_MANAGER,
|
|
607
612
|
univ4Hook: UNIV4_HOOK,
|
|
608
|
-
|
|
613
|
+
wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN)
|
|
609
614
|
}),
|
|
610
615
|
tokenIn: tokenIn,
|
|
611
616
|
tokenOut: tokenOut,
|
|
@@ -42,7 +42,7 @@ contract JBSwapCCIPSuckerDeployer is JBCCIPSuckerDeployer, IJBSwapCCIPSuckerDepl
|
|
|
42
42
|
address public univ4Hook;
|
|
43
43
|
|
|
44
44
|
/// @notice The ERC-20 wrapper address for the chain's native token (e.g. WETH on Ethereum).
|
|
45
|
-
address public
|
|
45
|
+
address public wrappedNativeToken;
|
|
46
46
|
|
|
47
47
|
//*********************************************************************//
|
|
48
48
|
// ---------------------------- constructor -------------------------- //
|
|
@@ -72,13 +72,13 @@ contract JBSwapCCIPSuckerDeployer is JBCCIPSuckerDeployer, IJBSwapCCIPSuckerDepl
|
|
|
72
72
|
/// @param _poolManager The Uniswap V4 PoolManager (can be address(0) if V4 unavailable).
|
|
73
73
|
/// @param _v3Factory The Uniswap V3 factory (can be address(0) if V3 unavailable).
|
|
74
74
|
/// @param _univ4Hook The V4 hook for pool discovery (optional, address(0) if none).
|
|
75
|
-
/// @param
|
|
75
|
+
/// @param _wrappedNativeToken The ERC-20 wrapper address for the chain's native token (e.g. WETH on Ethereum).
|
|
76
76
|
function setSwapConstants(
|
|
77
77
|
IERC20 _bridgeToken,
|
|
78
78
|
IPoolManager _poolManager,
|
|
79
79
|
IUniswapV3Factory _v3Factory,
|
|
80
80
|
address _univ4Hook,
|
|
81
|
-
address
|
|
81
|
+
address _wrappedNativeToken
|
|
82
82
|
)
|
|
83
83
|
external
|
|
84
84
|
{
|
|
@@ -110,6 +110,6 @@ contract JBSwapCCIPSuckerDeployer is JBCCIPSuckerDeployer, IJBSwapCCIPSuckerDepl
|
|
|
110
110
|
univ4Hook = _univ4Hook;
|
|
111
111
|
|
|
112
112
|
// Store the wrapped native token address.
|
|
113
|
-
|
|
113
|
+
wrappedNativeToken = _wrappedNativeToken;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -24,5 +24,5 @@ interface IJBSwapCCIPSuckerDeployer is IJBCCIPSuckerDeployer {
|
|
|
24
24
|
|
|
25
25
|
/// @notice The ERC-20 wrapper address for the chain's native token (e.g. WETH on Ethereum). Used for V3 native
|
|
26
26
|
/// swaps.
|
|
27
|
-
function
|
|
27
|
+
function wrappedNativeToken() external view returns (address);
|
|
28
28
|
}
|
|
@@ -130,11 +130,11 @@ library JBSuckerLib {
|
|
|
130
130
|
projectId: projectId,
|
|
131
131
|
pricingCurrency: tokenCurrency,
|
|
132
132
|
unitCurrency: JBCurrencyIds.ETH,
|
|
133
|
-
decimals:
|
|
133
|
+
decimals: dec
|
|
134
134
|
}) returns (
|
|
135
135
|
uint256 price
|
|
136
136
|
) {
|
|
137
|
-
ethBalance += mulDiv({x: bal, y:
|
|
137
|
+
ethBalance += mulDiv({x: bal, y: 10 ** _ETH_DECIMALS, denominator: price});
|
|
138
138
|
} catch {}
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -77,12 +77,13 @@ library JBSwapPoolLib {
|
|
|
77
77
|
/// @custom:member v3Factory The Uniswap V3 factory used for pool discovery.
|
|
78
78
|
/// @custom:member poolManager The Uniswap V4 pool manager used for V4 pool queries and swaps.
|
|
79
79
|
/// @custom:member univ4Hook The address of the Uniswap V4 hook contract to search for hooked pools.
|
|
80
|
-
/// @custom:member
|
|
80
|
+
/// @custom:member wrappedNativeToken The address of the ERC-20 wrapper for the chain's native token (e.g. WETH on
|
|
81
|
+
/// Ethereum).
|
|
81
82
|
struct SwapConfig {
|
|
82
83
|
IUniswapV3Factory v3Factory;
|
|
83
84
|
IPoolManager poolManager;
|
|
84
85
|
address univ4Hook;
|
|
85
|
-
address
|
|
86
|
+
address wrappedNativeToken;
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
//*********************************************************************//
|
|
@@ -109,8 +110,8 @@ library JBSwapPoolLib {
|
|
|
109
110
|
returns (uint256 amountOut)
|
|
110
111
|
{
|
|
111
112
|
// Normalize NATIVE_TOKEN sentinel to the wrapped native token for pool lookups.
|
|
112
|
-
address normalizedIn = _normalize({token: tokenIn,
|
|
113
|
-
address normalizedOut = _normalize({token: tokenOut,
|
|
113
|
+
address normalizedIn = _normalize({token: tokenIn, wrappedNativeToken: config.wrappedNativeToken});
|
|
114
|
+
address normalizedOut = _normalize({token: tokenOut, wrappedNativeToken: config.wrappedNativeToken});
|
|
114
115
|
|
|
115
116
|
// No swap needed if tokens are the same after normalization (e.g., NATIVE_TOKEN and wrapped native token).
|
|
116
117
|
if (normalizedIn == normalizedOut) return amount;
|
|
@@ -167,15 +168,15 @@ library JBSwapPoolLib {
|
|
|
167
168
|
}
|
|
168
169
|
// V3 outputs wrapped native token for native pairs — unwrap to raw native token.
|
|
169
170
|
if (tokenOut == JBConstants.NATIVE_TOKEN) {
|
|
170
|
-
IWrappedNativeToken(config.
|
|
171
|
+
IWrappedNativeToken(config.wrappedNativeToken).withdraw(amountOut);
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
// V4 outputs native tokens for wrapped-native-paired pools. If the caller requested the wrapped form
|
|
175
176
|
// (not NATIVE_TOKEN), wrap the received native tokens so the caller gets the token they expect.
|
|
176
|
-
if (isV4 && tokenOut != JBConstants.NATIVE_TOKEN && normalizedOut == config.
|
|
177
|
+
if (isV4 && tokenOut != JBConstants.NATIVE_TOKEN && normalizedOut == config.wrappedNativeToken) {
|
|
177
178
|
// Wrap through the configured wrapped native token contract.
|
|
178
|
-
IWrappedNativeToken(config.
|
|
179
|
+
IWrappedNativeToken(config.wrappedNativeToken).deposit{value: amountOut}();
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -193,7 +194,7 @@ library JBSwapPoolLib {
|
|
|
193
194
|
int256 amountSpecified,
|
|
194
195
|
uint160 sqrtPriceLimitX96,
|
|
195
196
|
uint256 minAmountOut,
|
|
196
|
-
address
|
|
197
|
+
address wrappedNativeToken
|
|
197
198
|
) = abi.decode(data, (PoolKey, bool, int256, uint160, uint256, address));
|
|
198
199
|
|
|
199
200
|
uint256 amountIn;
|
|
@@ -237,8 +238,14 @@ library JBSwapPoolLib {
|
|
|
237
238
|
// Settle input (pay what we owe to the PoolManager).
|
|
238
239
|
Currency inputCurrency = zeroForOne ? key.currency0 : key.currency1;
|
|
239
240
|
if (Currency.unwrap(inputCurrency) == address(0)) {
|
|
240
|
-
// Native token: unwrap if
|
|
241
|
-
|
|
241
|
+
// Native token: unwrap wrapped native token if the caller holds it, then settle directly.
|
|
242
|
+
// The buyback hook holds wrapped native tokens (needs unwrap), while suckers hold raw ETH (skip unwrap).
|
|
243
|
+
if (wrappedNativeToken != address(0)) {
|
|
244
|
+
uint256 wrappedBalance = IERC20(wrappedNativeToken).balanceOf(address(this));
|
|
245
|
+
if (wrappedBalance >= amountIn) {
|
|
246
|
+
IWrappedNativeToken(wrappedNativeToken).withdraw(amountIn);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
242
249
|
poolManager.settle{value: amountIn}();
|
|
243
250
|
} else {
|
|
244
251
|
// ERC-20: sync the currency balance, transfer tokens, then settle.
|
|
@@ -440,8 +447,8 @@ library JBSwapPoolLib {
|
|
|
440
447
|
address sorted0;
|
|
441
448
|
address sorted1;
|
|
442
449
|
{
|
|
443
|
-
address v4In = normalizedTokenIn == config.
|
|
444
|
-
address v4Out = normalizedTokenOut == config.
|
|
450
|
+
address v4In = normalizedTokenIn == config.wrappedNativeToken ? address(0) : normalizedTokenIn;
|
|
451
|
+
address v4Out = normalizedTokenOut == config.wrappedNativeToken ? address(0) : normalizedTokenOut;
|
|
445
452
|
|
|
446
453
|
// Sort tokens to match the V4 currency ordering convention.
|
|
447
454
|
(sorted0, sorted1) = v4In < v4Out ? (v4In, v4Out) : (v4Out, v4In);
|
|
@@ -668,8 +675,8 @@ library JBSwapPoolLib {
|
|
|
668
675
|
minAmountOut = _quoteWithSlippage({
|
|
669
676
|
amount: amount,
|
|
670
677
|
liquidity: liquidity,
|
|
671
|
-
tokenIn: normalizedTokenIn == config.
|
|
672
|
-
tokenOut: normalizedTokenOut == config.
|
|
678
|
+
tokenIn: normalizedTokenIn == config.wrappedNativeToken ? address(0) : normalizedTokenIn,
|
|
679
|
+
tokenOut: normalizedTokenOut == config.wrappedNativeToken ? address(0) : normalizedTokenOut,
|
|
673
680
|
tick: tick,
|
|
674
681
|
poolFeeBps: feeBps
|
|
675
682
|
});
|
|
@@ -1001,7 +1008,7 @@ library JBSwapPoolLib {
|
|
|
1001
1008
|
returns (uint256 amountOut)
|
|
1002
1009
|
{
|
|
1003
1010
|
// Convert wrapped native token to address(0) for V4's native token convention.
|
|
1004
|
-
address v4In = normalizedTokenIn == config.
|
|
1011
|
+
address v4In = normalizedTokenIn == config.wrappedNativeToken ? address(0) : normalizedTokenIn;
|
|
1005
1012
|
|
|
1006
1013
|
// Determine swap direction based on currency ordering in the pool key.
|
|
1007
1014
|
bool zeroForOne = Currency.unwrap(key.currency0) == v4In;
|
|
@@ -1019,7 +1026,9 @@ library JBSwapPoolLib {
|
|
|
1019
1026
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1020
1027
|
int256 exactInputAmount = -int256(amount);
|
|
1021
1028
|
|
|
1022
|
-
unlockData = abi.encode(
|
|
1029
|
+
unlockData = abi.encode(
|
|
1030
|
+
key, zeroForOne, exactInputAmount, sqrtPriceLimitX96, minAmountOut, config.wrappedNativeToken
|
|
1031
|
+
);
|
|
1023
1032
|
}
|
|
1024
1033
|
|
|
1025
1034
|
// Unlock the PoolManager and encode the swap parameters for the callback.
|
|
@@ -1031,10 +1040,10 @@ library JBSwapPoolLib {
|
|
|
1031
1040
|
|
|
1032
1041
|
/// @notice Normalize a token address, converting the NATIVE_TOKEN sentinel to the wrapped native token.
|
|
1033
1042
|
/// @param token The token address to normalize.
|
|
1034
|
-
/// @param
|
|
1043
|
+
/// @param wrappedNativeToken The wrapped native token address on this chain.
|
|
1035
1044
|
/// @return The normalized token address.
|
|
1036
|
-
function _normalize(address token, address
|
|
1037
|
-
return token == JBConstants.NATIVE_TOKEN ?
|
|
1045
|
+
function _normalize(address token, address wrappedNativeToken) internal pure returns (address) {
|
|
1046
|
+
return token == JBConstants.NATIVE_TOKEN ? wrappedNativeToken : token;
|
|
1038
1047
|
}
|
|
1039
1048
|
|
|
1040
1049
|
/// @notice Get the Uniswap V3 fee tier for a given index.
|