@bananapus/suckers-v6 0.0.35 → 0.0.36
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 +1 -1
- package/src/JBCCIPSucker.sol +5 -5
- package/src/JBCeloSucker.sol +19 -18
- package/src/JBSucker.sol +2 -2
- package/src/JBSuckerRegistry.sol +9 -37
- package/src/JBSwapCCIPSucker.sol +12 -7
- package/src/deployers/JBCeloSuckerDeployer.sol +2 -2
- package/src/deployers/JBSwapCCIPSuckerDeployer.sol +2 -2
- package/src/interfaces/IJBCeloSuckerDeployer.sol +1 -1
- package/src/interfaces/IJBSwapCCIPSuckerDeployer.sol +2 -1
- package/src/libraries/CCIPHelper.sol +3 -3
- package/src/libraries/JBCCIPLib.sol +8 -8
- package/src/libraries/JBSwapPoolLib.sol +23 -23
package/package.json
CHANGED
package/src/JBCCIPSucker.sol
CHANGED
|
@@ -174,7 +174,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
174
174
|
// Decode the root message from the payload.
|
|
175
175
|
JBMessageRoot memory root = abi.decode(payload, (JBMessageRoot));
|
|
176
176
|
|
|
177
|
-
// Only unwrap
|
|
177
|
+
// Only unwrap wrapped native token when the root targets native token (not when claiming it as ERC-20).
|
|
178
178
|
if (root.token == bytes32(uint256(uint160(JBConstants.NATIVE_TOKEN)))) {
|
|
179
179
|
JBCCIPLib.unwrapReceivedTokens({
|
|
180
180
|
ccipRouter: CCIP_ROUTER, destTokenAmounts: any2EvmMessage.destTokenAmounts
|
|
@@ -224,7 +224,7 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
224
224
|
// Add extra gas for the ERC-20 token transfer on the remote chain.
|
|
225
225
|
gasLimit += remoteToken.minGas;
|
|
226
226
|
|
|
227
|
-
// Wrap native
|
|
227
|
+
// Wrap native tokens if needed, build the CCIP token amounts array, and approve the router.
|
|
228
228
|
// slither-disable-next-line unused-return
|
|
229
229
|
(tokenAmounts,) = JBCCIPLib.prepareTokenAmounts({ccipRouter: CCIP_ROUTER, token: token, amount: amount});
|
|
230
230
|
} else {
|
|
@@ -281,15 +281,15 @@ contract JBCCIPSucker is JBSucker, IAny2EVMMessageReceiver {
|
|
|
281
281
|
///
|
|
282
282
|
/// Example: ETH mainnet (native = ETH) <-> Celo (native = CELO, ETH is an ERC-20).
|
|
283
283
|
/// - On mainnet: `mapToken({localToken: NATIVE_TOKEN, remoteToken: celoETH_address})`
|
|
284
|
-
/// - Sending: `_sendRootOverAMB` wraps native
|
|
285
|
-
/// - Receiving: `ccipReceive` checks `root.token == NATIVE_TOKEN` to decide whether to unwrap
|
|
284
|
+
/// - Sending: `_sendRootOverAMB` wraps native tokens, bridges them via CCIP.
|
|
285
|
+
/// - Receiving: `ccipReceive` checks `root.token == NATIVE_TOKEN` to decide whether to unwrap.
|
|
286
286
|
/// If `root.token` is an ERC-20 address (like celoETH), no unwrap occurs — tokens stay as ERC-20.
|
|
287
287
|
///
|
|
288
288
|
/// The base class restriction (`NATIVE_TOKEN` can only map to `NATIVE_TOKEN` or `address(0)`) is intentionally
|
|
289
289
|
/// removed here. The base class retains that restriction for OP/Arbitrum where both chains share ETH as native.
|
|
290
290
|
function _validateTokenMapping(JBTokenMapping calldata map) internal pure virtual override {
|
|
291
291
|
// Enforce a reasonable minimum gas limit for bridging. A minimum which is too low could lead to the loss of
|
|
292
|
-
// funds. CCIP wraps native tokens
|
|
292
|
+
// funds. CCIP wraps native tokens before bridging (see `_sendRootOverAMB`), so ALL tokens —
|
|
293
293
|
// including native — need sufficient gas for an ERC-20 transfer on the remote chain.
|
|
294
294
|
if (map.minGas < MESSENGER_ERC20_MIN_GAS_LIMIT) {
|
|
295
295
|
revert JBSucker_BelowMinGas({minGas: map.minGas, minGasLimit: MESSENGER_ERC20_MIN_GAS_LIMIT});
|
package/src/JBCeloSucker.sol
CHANGED
|
@@ -20,7 +20,7 @@ import {JBRemoteToken} from "./structs/JBRemoteToken.sol";
|
|
|
20
20
|
import {JBTokenMapping} from "./structs/JBTokenMapping.sol";
|
|
21
21
|
|
|
22
22
|
/// @notice A `JBSucker` implementation for Celo — an OP Stack chain with a custom gas token (CELO, not ETH).
|
|
23
|
-
/// @dev ETH exists on Celo only as an ERC-20
|
|
23
|
+
/// @dev ETH exists on Celo only as an ERC-20. This sucker wraps native ETH before bridging
|
|
24
24
|
/// as ERC-20 via the OP standard bridge, and removes the `NATIVE_TOKEN → NATIVE_TOKEN` restriction so that
|
|
25
25
|
/// native ETH can map to a remote ERC-20.
|
|
26
26
|
contract JBCeloSucker is JBOptimismSucker {
|
|
@@ -28,8 +28,8 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
28
28
|
// --------------- public immutable stored properties ---------------- //
|
|
29
29
|
//*********************************************************************//
|
|
30
30
|
|
|
31
|
-
/// @notice The
|
|
32
|
-
IWrappedNativeToken public immutable
|
|
31
|
+
/// @notice The ERC-20 wrapper for the chain's native token on the local chain.
|
|
32
|
+
IWrappedNativeToken public immutable WRAPPED_NATIVE_TOKEN;
|
|
33
33
|
|
|
34
34
|
//*********************************************************************//
|
|
35
35
|
// ---------------------------- constructor -------------------------- //
|
|
@@ -53,7 +53,7 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
53
53
|
JBOptimismSucker(deployer, directory, permissions, prices, tokens, feeProjectId, registry, trustedForwarder)
|
|
54
54
|
{
|
|
55
55
|
// Fetch the wrapped native token by doing a callback to the deployer contract.
|
|
56
|
-
|
|
56
|
+
WRAPPED_NATIVE_TOKEN = JBCeloSuckerDeployer(deployer).wrappedNative();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
//*********************************************************************//
|
|
@@ -73,24 +73,25 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
73
73
|
// --------------------- internal transactions ----------------------- //
|
|
74
74
|
//*********************************************************************//
|
|
75
75
|
|
|
76
|
-
/// @notice Unwraps
|
|
77
|
-
/// @dev When tokens are bridged from Celo → L1 via the OP bridge,
|
|
78
|
-
/// But the L1 project's terminal accepts native ETH (NATIVE_TOKEN), not
|
|
76
|
+
/// @notice Unwraps wrapped native tokens before adding to the project's balance.
|
|
77
|
+
/// @dev When tokens are bridged from Celo → L1 via the OP bridge, wrapped native tokens (ERC-20) are released to
|
|
78
|
+
/// the sucker. But the L1 project's terminal accepts native ETH (NATIVE_TOKEN), not the wrapped form. This override
|
|
79
|
+
/// unwraps
|
|
79
80
|
/// and adds native ETH to the project's balance.
|
|
80
81
|
/// @param token The terminal token to add to the project's balance.
|
|
81
82
|
/// @param amount The amount of terminal tokens to add to the project's balance.
|
|
82
83
|
/// @param cachedProjectId The cached project ID to avoid redundant storage reads.
|
|
83
84
|
function _addToBalance(address token, uint256 amount, uint256 cachedProjectId) internal override {
|
|
84
|
-
if (token == address(
|
|
85
|
-
// Check addable amount against
|
|
85
|
+
if (token == address(WRAPPED_NATIVE_TOKEN)) {
|
|
86
|
+
// Check addable amount against wrapped native token balance before unwrapping.
|
|
86
87
|
uint256 addableAmount = amountToAddToBalanceOf(token);
|
|
87
88
|
if (amount > addableAmount) {
|
|
88
89
|
revert JBSucker_InsufficientBalance({amount: amount, balance: addableAmount});
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
// Unwrap
|
|
92
|
+
// Unwrap wrapped native tokens → native tokens.
|
|
92
93
|
// slither-disable-next-line calls-loop
|
|
93
|
-
|
|
94
|
+
WRAPPED_NATIVE_TOKEN.withdraw(amount);
|
|
94
95
|
|
|
95
96
|
// Get the project's primary terminal for native token.
|
|
96
97
|
// slither-disable-next-line calls-loop
|
|
@@ -118,7 +119,7 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
118
119
|
|
|
119
120
|
/// @notice Use the `OPMESSENGER` to send the outbox tree for the `token` and the corresponding funds to the peer
|
|
120
121
|
/// over the `OPBRIDGE`.
|
|
121
|
-
/// @dev For Celo, native ETH is wrapped
|
|
122
|
+
/// @dev For Celo, native ETH is wrapped and bridged as ERC-20. The messenger message is sent with
|
|
122
123
|
/// `nativeValue = 0` because Celo's native token is CELO (not ETH), so we never attach ETH as msg.value.
|
|
123
124
|
/// @param transportPayment the amount of `msg.value` that is going to get paid for sending this message.
|
|
124
125
|
/// @param token The token to bridge the outbox tree for.
|
|
@@ -146,13 +147,13 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
146
147
|
address peerAddress = _toAddress(peer());
|
|
147
148
|
|
|
148
149
|
if (amount != 0) {
|
|
149
|
-
// Determine the local token to bridge — native ETH is wrapped
|
|
150
|
+
// Determine the local token to bridge — native ETH is wrapped first.
|
|
150
151
|
address bridgeToken = token;
|
|
151
152
|
if (token == JBConstants.NATIVE_TOKEN) {
|
|
152
|
-
// Wrap native
|
|
153
|
+
// Wrap native tokens so they can be bridged as ERC-20.
|
|
153
154
|
// slither-disable-next-line arbitrary-send-eth,calls-loop
|
|
154
|
-
|
|
155
|
-
bridgeToken = address(
|
|
155
|
+
WRAPPED_NATIVE_TOKEN.deposit{value: amount}();
|
|
156
|
+
bridgeToken = address(WRAPPED_NATIVE_TOKEN);
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
// Approve the bridge to spend the token.
|
|
@@ -183,11 +184,11 @@ contract JBCeloSucker is JBOptimismSucker {
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
/// @notice Allow `NATIVE_TOKEN` to map to any remote token (not just `NATIVE_TOKEN`).
|
|
186
|
-
/// @dev Celo uses CELO as native gas token. ETH is an ERC-20 on Celo
|
|
187
|
+
/// @dev Celo uses CELO as native gas token. ETH is an ERC-20 on Celo. So `NATIVE_TOKEN` on L1
|
|
187
188
|
/// maps to an ERC-20 address on Celo, not to `NATIVE_TOKEN`. The base class restriction is removed.
|
|
188
189
|
function _validateTokenMapping(JBTokenMapping calldata map) internal pure virtual override {
|
|
189
190
|
// Enforce a reasonable minimum gas limit for bridging. Since we always bridge as ERC-20
|
|
190
|
-
// (wrapping native
|
|
191
|
+
// (wrapping native tokens), all tokens need sufficient gas for an ERC-20 transfer.
|
|
191
192
|
if (map.minGas < MESSENGER_ERC20_MIN_GAS_LIMIT) {
|
|
192
193
|
revert JBSucker_BelowMinGas({minGas: map.minGas, minGasLimit: MESSENGER_ERC20_MIN_GAS_LIMIT});
|
|
193
194
|
}
|
package/src/JBSucker.sol
CHANGED
|
@@ -255,8 +255,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
255
255
|
/// @notice Accepts incoming native token (ETH) transfers.
|
|
256
256
|
/// @dev This receive function is intentionally unrestricted. It must accept ETH from multiple sources:
|
|
257
257
|
/// - Bridge contracts (e.g., Optimism's StandardBridge, Arbitrum's gateway) delivering bridged native tokens.
|
|
258
|
-
/// -
|
|
259
|
-
/// - Terminals returning native tokens during `cashOutTokensOf` (backing asset pulls).
|
|
258
|
+
/// - Wrapped native token contracts during unwrapping (e.g., CCIP sucker unwraps via `withdraw()` which sends
|
|
259
|
+
/// native tokens here). - Terminals returning native tokens during `cashOutTokensOf` (backing asset pulls).
|
|
260
260
|
/// @dev Restricting this to known senders would risk breaking bridge integrations, as bridge contracts may change
|
|
261
261
|
/// addresses or use proxy patterns. The sucker's accounting (`_outboxOf[token].balance` and
|
|
262
262
|
/// `amountToAddToBalanceOf`) already tracks expected native token amounts, so excess ETH sent here does not
|
package/src/JBSuckerRegistry.sol
CHANGED
|
@@ -20,9 +20,9 @@ import {JBSuckerDeployerConfig} from "./structs/JBSuckerDeployerConfig.sol";
|
|
|
20
20
|
import {JBSuckersPair} from "./structs/JBSuckersPair.sol";
|
|
21
21
|
|
|
22
22
|
/// @notice The canonical registry that deploys, tracks, and governs cross-chain suckers for Juicebox projects. It
|
|
23
|
-
/// maintains an allowlist of approved deployer contracts,
|
|
24
|
-
/// manages the global `toRemoteFee` (paid into the protocol fee project on each bridge send), and provides
|
|
25
|
-
/// views of remote-chain balances, surplus, and token supply across all of a project's suckers.
|
|
23
|
+
/// maintains an allowlist of approved deployer contracts, allows multiple active suckers per peer chain for bridge
|
|
24
|
+
/// resilience, manages the global `toRemoteFee` (paid into the protocol fee project on each bridge send), and provides
|
|
25
|
+
/// aggregate views of remote-chain balances, surplus, and token supply across all of a project's suckers.
|
|
26
26
|
contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerRegistry {
|
|
27
27
|
using EnumerableMap for EnumerableMap.AddressToUintMap;
|
|
28
28
|
|
|
@@ -30,7 +30,6 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
30
30
|
// --------------------------- custom errors ------------------------- //
|
|
31
31
|
//*********************************************************************//
|
|
32
32
|
|
|
33
|
-
error JBSuckerRegistry_DuplicatePeerChain(uint256 projectId, uint256 peerChainId);
|
|
34
33
|
error JBSuckerRegistry_FeeExceedsMax(uint256 fee, uint256 max);
|
|
35
34
|
error JBSuckerRegistry_InvalidDeployer(IJBSuckerDeployer deployer);
|
|
36
35
|
error JBSuckerRegistry_SuckerDoesNotBelongToProject(uint256 projectId, address sucker);
|
|
@@ -420,8 +419,9 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
420
419
|
{
|
|
421
420
|
for (uint256 j; j < chainCount;) {
|
|
422
421
|
if (chainIds[j] == chainId) {
|
|
423
|
-
//
|
|
424
|
-
//
|
|
422
|
+
// Each sucker caches the entire remote chain's state (not a per-sucker share), so multiple
|
|
423
|
+
// suckers targeting the same chain report redundant snapshots. MAX picks the freshest value
|
|
424
|
+
// without double-counting — SUM would inflate bonding curve denominators.
|
|
425
425
|
if (isActive) {
|
|
426
426
|
if (!hasActiveValue[j] || value > values[j]) values[j] = value;
|
|
427
427
|
hasActiveValue[j] = true;
|
|
@@ -444,30 +444,6 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
/// @notice Reverts if any active sucker for the given project already targets the same peer chain as the new
|
|
448
|
-
/// sucker.
|
|
449
|
-
/// @param projectId The ID of the project to check.
|
|
450
|
-
/// @param newSucker The newly created sucker to validate.
|
|
451
|
-
function _revertIfDuplicatePeerChain(uint256 projectId, IJBSucker newSucker) internal view {
|
|
452
|
-
// The new sucker is registry-deployed and trusted for this validation.
|
|
453
|
-
// slither-disable-next-line calls-loop
|
|
454
|
-
uint256 newPeerChainId = newSucker.peerChainId();
|
|
455
|
-
address[] memory existing = _suckersOf[projectId].keys();
|
|
456
|
-
for (uint256 i; i < existing.length;) {
|
|
457
|
-
// slither-disable-next-line unused-return
|
|
458
|
-
(, uint256 val) = _suckersOf[projectId].tryGet(existing[i]);
|
|
459
|
-
if (val == _SUCKER_EXISTS) {
|
|
460
|
-
// slither-disable-next-line calls-loop
|
|
461
|
-
if (IJBSucker(existing[i]).peerChainId() == newPeerChainId) {
|
|
462
|
-
revert JBSuckerRegistry_DuplicatePeerChain(projectId, newPeerChainId);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
unchecked {
|
|
466
|
-
++i;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
447
|
//*********************************************************************//
|
|
472
448
|
// ---------------------- public transactions ----------------------- //
|
|
473
449
|
//*********************************************************************//
|
|
@@ -502,9 +478,9 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
502
478
|
}
|
|
503
479
|
|
|
504
480
|
/// @notice Deploy one or more cross-chain suckers for a project in a single transaction. Each sucker is created via
|
|
505
|
-
/// its deployer, registered in this registry,
|
|
506
|
-
///
|
|
507
|
-
/// `MAP_SUCKER_TOKEN` permission for the project.
|
|
481
|
+
/// its deployer, registered in this registry, and immediately configured with its token mappings. Multiple suckers
|
|
482
|
+
/// targeting the same peer chain are allowed for bridge resilience. The caller must have `DEPLOY_SUCKERS`
|
|
483
|
+
/// permission, and this registry must hold `MAP_SUCKER_TOKEN` permission for the project.
|
|
508
484
|
/// @param projectId The ID of the project to deploy suckers for.
|
|
509
485
|
/// @param salt The salt used to deploy the contract. For the suckers to be peers, this must be the same value on
|
|
510
486
|
/// each chain where suckers are deployed.
|
|
@@ -551,10 +527,6 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
551
527
|
.createForSender({localProjectId: projectId, salt: salt, peer: configuration.peer});
|
|
552
528
|
suckers[i] = address(sucker);
|
|
553
529
|
|
|
554
|
-
// Make sure no active sucker already targets the same peer chain.
|
|
555
|
-
// slither-disable-next-line calls-loop
|
|
556
|
-
_revertIfDuplicatePeerChain({projectId: projectId, newSucker: sucker});
|
|
557
|
-
|
|
558
530
|
// Store the sucker as being deployed for this project.
|
|
559
531
|
// slither-disable-next-line unused-return
|
|
560
532
|
_suckersOf[projectId].set({key: address(sucker), value: _SUCKER_EXISTS});
|
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -121,8 +121,9 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
121
121
|
/// @notice The Uniswap V3 factory for pool discovery and callback verification. Can be address(0).
|
|
122
122
|
IUniswapV3Factory public immutable V3_FACTORY;
|
|
123
123
|
|
|
124
|
-
/// @notice The
|
|
125
|
-
|
|
124
|
+
/// @notice The ERC-20 wrapper for the chain's native token (e.g. WETH on Ethereum, WCELO on Celo). Used for V3
|
|
125
|
+
/// native swaps.
|
|
126
|
+
IWrappedNativeToken public immutable WRAPPED_NATIVE_TOKEN;
|
|
126
127
|
|
|
127
128
|
//*********************************************************************//
|
|
128
129
|
// ------------------- internal stored properties -------------------- //
|
|
@@ -223,11 +224,11 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
223
224
|
POOL_MANAGER = swapDeployer.poolManager();
|
|
224
225
|
V3_FACTORY = swapDeployer.v3Factory();
|
|
225
226
|
UNIV4_HOOK = swapDeployer.univ4Hook();
|
|
226
|
-
|
|
227
|
+
WRAPPED_NATIVE_TOKEN = IWrappedNativeToken(swapDeployer.weth());
|
|
227
228
|
|
|
228
229
|
if (address(BRIDGE_TOKEN) == address(0)) revert JBSwapCCIPSucker_InvalidBridgeToken();
|
|
229
|
-
// BRIDGE_TOKEN must not be
|
|
230
|
-
if (address(BRIDGE_TOKEN) == address(
|
|
230
|
+
// BRIDGE_TOKEN must not be the wrapped native token — wrapping and CCIP ERC-20 bridging conflict.
|
|
231
|
+
if (address(BRIDGE_TOKEN) == address(WRAPPED_NATIVE_TOKEN) && address(WRAPPED_NATIVE_TOKEN) != address(0)) {
|
|
231
232
|
revert JBSwapCCIPSucker_InvalidBridgeToken();
|
|
232
233
|
}
|
|
233
234
|
// NOTE: V3_FACTORY and POOL_MANAGER can both be address(0) on chains where the local terminal token
|
|
@@ -239,7 +240,8 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
239
240
|
// ------------------------- receive / fallback ---------------------- //
|
|
240
241
|
//*********************************************************************//
|
|
241
242
|
|
|
242
|
-
/// @notice Allow this contract to receive
|
|
243
|
+
/// @notice Allow this contract to receive native tokens (from V4 swaps, wrapped-native-token unwrap, and CCIP
|
|
244
|
+
/// refunds).
|
|
243
245
|
receive() external payable override {}
|
|
244
246
|
|
|
245
247
|
//*********************************************************************//
|
|
@@ -593,7 +595,10 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
593
595
|
function _executeSwap(address tokenIn, address tokenOut, uint256 amount) internal returns (uint256 amountOut) {
|
|
594
596
|
return JBSwapPoolLib.executeSwap({
|
|
595
597
|
config: JBSwapPoolLib.SwapConfig({
|
|
596
|
-
v3Factory: V3_FACTORY,
|
|
598
|
+
v3Factory: V3_FACTORY,
|
|
599
|
+
poolManager: POOL_MANAGER,
|
|
600
|
+
univ4Hook: UNIV4_HOOK,
|
|
601
|
+
weth: address(WRAPPED_NATIVE_TOKEN)
|
|
597
602
|
}),
|
|
598
603
|
tokenIn: tokenIn,
|
|
599
604
|
tokenOut: tokenOut,
|
|
@@ -22,7 +22,7 @@ contract JBCeloSuckerDeployer is JBOptimismSuckerDeployer, IJBCeloSuckerDeployer
|
|
|
22
22
|
// ---------------------- public stored properties ------------------- //
|
|
23
23
|
//*********************************************************************//
|
|
24
24
|
|
|
25
|
-
/// @notice The
|
|
25
|
+
/// @notice The ERC-20 wrapper for the chain's native token on the local chain.
|
|
26
26
|
IWrappedNativeToken public override wrappedNative;
|
|
27
27
|
|
|
28
28
|
//*********************************************************************//
|
|
@@ -63,7 +63,7 @@ contract JBCeloSuckerDeployer is JBOptimismSuckerDeployer, IJBCeloSuckerDeployer
|
|
|
63
63
|
/// @notice Handles layer specific configuration including the wrapped native token.
|
|
64
64
|
/// @param messenger The OPMessenger on this layer.
|
|
65
65
|
/// @param bridge The OPStandardBridge on this layer.
|
|
66
|
-
/// @param _wrappedNative The
|
|
66
|
+
/// @param _wrappedNative The ERC-20 wrapper for the chain's native token on this layer.
|
|
67
67
|
function setChainSpecificConstants(
|
|
68
68
|
IOPMessenger messenger,
|
|
69
69
|
IOPStandardBridge bridge,
|
|
@@ -41,7 +41,7 @@ contract JBSwapCCIPSuckerDeployer is JBCCIPSuckerDeployer, IJBSwapCCIPSuckerDepl
|
|
|
41
41
|
/// @notice The Uniswap V4 hook address for pool discovery (optional).
|
|
42
42
|
address public univ4Hook;
|
|
43
43
|
|
|
44
|
-
/// @notice The
|
|
44
|
+
/// @notice The ERC-20 wrapper address for the chain's native token (e.g. WETH on Ethereum).
|
|
45
45
|
address public weth;
|
|
46
46
|
|
|
47
47
|
//*********************************************************************//
|
|
@@ -72,7 +72,7 @@ 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 _weth The
|
|
75
|
+
/// @param _weth 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,
|
|
@@ -10,7 +10,7 @@ import {IOPStandardBridge} from "./IOPStandardBridge.sol";
|
|
|
10
10
|
interface IJBCeloSuckerDeployer is IJBOpSuckerDeployer {
|
|
11
11
|
// View functions
|
|
12
12
|
|
|
13
|
-
/// @notice The
|
|
13
|
+
/// @notice The ERC-20 wrapper for the chain's native token on the local chain.
|
|
14
14
|
function wrappedNative() external view returns (IWrappedNativeToken);
|
|
15
15
|
|
|
16
16
|
// State-changing functions
|
|
@@ -22,6 +22,7 @@ interface IJBSwapCCIPSuckerDeployer is IJBCCIPSuckerDeployer {
|
|
|
22
22
|
/// @notice The Uniswap V4 hook address used during pool discovery (optional).
|
|
23
23
|
function univ4Hook() external view returns (address);
|
|
24
24
|
|
|
25
|
-
/// @notice The
|
|
25
|
+
/// @notice The ERC-20 wrapper address for the chain's native token (e.g. WETH on Ethereum). Used for V3 native
|
|
26
|
+
/// swaps.
|
|
26
27
|
function weth() external view returns (address);
|
|
27
28
|
}
|
|
@@ -61,7 +61,7 @@ library CCIPHelper {
|
|
|
61
61
|
address public constant TEMPO_LINK = 0x15C03488B29e27d62BAf10E30b0c474bf60E0264;
|
|
62
62
|
address public constant TEMPO_MOD_LINK = 0xEAB080c724587fFC9F2EFF82e36EE4Fb27774959;
|
|
63
63
|
|
|
64
|
-
/// @notice The
|
|
64
|
+
/// @notice The wrapped native token address of each chain
|
|
65
65
|
address public constant ETH_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
|
66
66
|
address public constant ETH_SEP_WETH = 0x097D90c9d3E0B50Ca60e1ae45F6A81010f9FB534;
|
|
67
67
|
address public constant OP_WETH = 0x4200000000000000000000000000000000000006;
|
|
@@ -146,9 +146,9 @@ library CCIPHelper {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/// @notice Returns the
|
|
149
|
+
/// @notice Returns the wrapped native token address for a given chain ID.
|
|
150
150
|
/// @param chainId The EVM chain ID to look up.
|
|
151
|
-
/// @return weth The
|
|
151
|
+
/// @return weth The wrapped native token address.
|
|
152
152
|
function wethOfChain(uint256 chainId) public pure returns (address weth) {
|
|
153
153
|
if (chainId == ETH_ID) {
|
|
154
154
|
return ETH_WETH;
|
|
@@ -19,13 +19,13 @@ library JBCCIPLib {
|
|
|
19
19
|
// ---------------------- external transactions ---------------------- //
|
|
20
20
|
//*********************************************************************//
|
|
21
21
|
|
|
22
|
-
/// @notice Prepare token amounts for CCIP: wrap native
|
|
22
|
+
/// @notice Prepare token amounts for CCIP: wrap native tokens, build token amounts array, approve router.
|
|
23
23
|
/// @dev Runs via DELEGATECALL so native ETH wrapping uses the caller's balance.
|
|
24
24
|
/// @param ccipRouter The CCIP router.
|
|
25
25
|
/// @param token The token to bridge (may be NATIVE_TOKEN).
|
|
26
26
|
/// @param amount The amount to bridge.
|
|
27
27
|
/// @return tokenAmounts The CCIP token amounts array (length 0 or 1).
|
|
28
|
-
/// @return bridgeToken The actual ERC-20 token address to bridge (
|
|
28
|
+
/// @return bridgeToken The actual ERC-20 token address to bridge (wrapped native token if native was wrapped).
|
|
29
29
|
function prepareTokenAmounts(
|
|
30
30
|
ICCIPRouter ccipRouter,
|
|
31
31
|
address token,
|
|
@@ -42,13 +42,13 @@ library JBCCIPLib {
|
|
|
42
42
|
// Start with the original token as the bridge token.
|
|
43
43
|
bridgeToken = token;
|
|
44
44
|
|
|
45
|
-
// Wrap native
|
|
45
|
+
// Wrap native tokens for CCIP bridging. CCIP only transports ERC-20s.
|
|
46
46
|
if (token == JBConstants.NATIVE_TOKEN) {
|
|
47
47
|
// Get the wrapped native token address from the CCIP router.
|
|
48
48
|
// slither-disable-next-line calls-loop
|
|
49
49
|
IWrappedNativeToken wrappedNative = ccipRouter.getWrappedNative();
|
|
50
50
|
|
|
51
|
-
// Deposit
|
|
51
|
+
// Deposit native tokens to receive wrapped native tokens.
|
|
52
52
|
// slither-disable-next-line calls-loop,arbitrary-send-eth
|
|
53
53
|
wrappedNative.deposit{value: amount}();
|
|
54
54
|
|
|
@@ -157,8 +157,8 @@ library JBCCIPLib {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
/// @notice Unwrap
|
|
161
|
-
/// @dev Runs via DELEGATECALL so `address(this).balance` refers to the calling contract.
|
|
160
|
+
/// @notice Unwrap wrapped native tokens from received CCIP tokens if the delivered token matches the wrapped
|
|
161
|
+
/// native. @dev Runs via DELEGATECALL so `address(this).balance` refers to the calling contract.
|
|
162
162
|
/// @param ccipRouter The CCIP router (used to look up wrapped native token).
|
|
163
163
|
/// @param destTokenAmounts The token amounts delivered by CCIP (length 0 or 1).
|
|
164
164
|
function unwrapReceivedTokens(ICCIPRouter ccipRouter, Client.EVMTokenAmount[] calldata destTokenAmounts) external {
|
|
@@ -171,12 +171,12 @@ library JBCCIPLib {
|
|
|
171
171
|
// slither-disable-next-line calls-loop
|
|
172
172
|
IWrappedNativeToken wrappedNative = ccipRouter.getWrappedNative();
|
|
173
173
|
|
|
174
|
-
// If the delivered token is
|
|
174
|
+
// If the delivered token is the wrapped native token and the amount is non-zero, unwrap it.
|
|
175
175
|
if (tokenAmount.token == address(wrappedNative) && tokenAmount.amount > 0) {
|
|
176
176
|
// Record the ETH balance before unwrapping.
|
|
177
177
|
uint256 balanceBefore = address(this).balance;
|
|
178
178
|
|
|
179
|
-
// Withdraw
|
|
179
|
+
// Withdraw wrapped native tokens to receive native tokens.
|
|
180
180
|
wrappedNative.withdraw(tokenAmount.amount);
|
|
181
181
|
|
|
182
182
|
// Assert the ETH balance increased by the expected amount.
|
|
@@ -77,7 +77,7 @@ 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 weth The address of the
|
|
80
|
+
/// @custom:member weth The address of the ERC-20 wrapper for the chain's native token (e.g. WETH on Ethereum).
|
|
81
81
|
struct SwapConfig {
|
|
82
82
|
IUniswapV3Factory v3Factory;
|
|
83
83
|
IPoolManager poolManager;
|
|
@@ -91,7 +91,7 @@ library JBSwapPoolLib {
|
|
|
91
91
|
|
|
92
92
|
/// @notice Execute a full swap: discover the best V3/V4 pool, quote via TWAP, execute the swap.
|
|
93
93
|
/// @dev Runs via DELEGATECALL so the calling contract's balance and callbacks are used.
|
|
94
|
-
/// @param config The swap configuration (factory, pool manager, hook,
|
|
94
|
+
/// @param config The swap configuration (factory, pool manager, hook, wrapped native token addresses).
|
|
95
95
|
/// @param tokenIn The input token (raw address, may be NATIVE_TOKEN sentinel).
|
|
96
96
|
/// @param tokenOut The output token (raw address).
|
|
97
97
|
/// @param amount The amount of input tokens to swap.
|
|
@@ -108,11 +108,11 @@ library JBSwapPoolLib {
|
|
|
108
108
|
external
|
|
109
109
|
returns (uint256 amountOut)
|
|
110
110
|
{
|
|
111
|
-
// Normalize NATIVE_TOKEN sentinel to
|
|
111
|
+
// Normalize NATIVE_TOKEN sentinel to the wrapped native token for pool lookups.
|
|
112
112
|
address normalizedIn = _normalize({token: tokenIn, weth: config.weth});
|
|
113
113
|
address normalizedOut = _normalize({token: tokenOut, weth: config.weth});
|
|
114
114
|
|
|
115
|
-
// No swap needed if tokens are the same after normalization (e.g., NATIVE_TOKEN and
|
|
115
|
+
// No swap needed if tokens are the same after normalization (e.g., NATIVE_TOKEN and wrapped native token).
|
|
116
116
|
if (normalizedIn == normalizedOut) return amount;
|
|
117
117
|
|
|
118
118
|
// Discover the most liquid pool across V3 and V4.
|
|
@@ -163,16 +163,16 @@ library JBSwapPoolLib {
|
|
|
163
163
|
originalTokenIn: tokenIn
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
|
-
// V3 outputs
|
|
166
|
+
// V3 outputs wrapped native token for native pairs — unwrap to raw native token.
|
|
167
167
|
if (tokenOut == JBConstants.NATIVE_TOKEN) {
|
|
168
168
|
IWrappedNativeToken(config.weth).withdraw(amountOut);
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
// V4 outputs native
|
|
173
|
-
// wrap the received
|
|
172
|
+
// V4 outputs native tokens for wrapped-native-paired pools. If the caller requested the wrapped form
|
|
173
|
+
// (not NATIVE_TOKEN), wrap the received native tokens so the caller gets the token they expect.
|
|
174
174
|
if (isV4 && tokenOut != JBConstants.NATIVE_TOKEN && normalizedOut == config.weth) {
|
|
175
|
-
// Wrap through the configured
|
|
175
|
+
// Wrap through the configured wrapped native token contract.
|
|
176
176
|
// slither-disable-next-line arbitrary-send-eth
|
|
177
177
|
IWrappedNativeToken(config.weth).deposit{value: amountOut}();
|
|
178
178
|
}
|
|
@@ -236,7 +236,7 @@ library JBSwapPoolLib {
|
|
|
236
236
|
// Settle input (pay what we owe to the PoolManager).
|
|
237
237
|
Currency inputCurrency = zeroForOne ? key.currency0 : key.currency1;
|
|
238
238
|
if (Currency.unwrap(inputCurrency) == address(0)) {
|
|
239
|
-
// Native
|
|
239
|
+
// Native token: unwrap if needed, then settle by sending native value directly.
|
|
240
240
|
if (weth != address(0)) IWrappedNativeToken(weth).withdraw(amountIn);
|
|
241
241
|
// slither-disable-next-line unused-return,arbitrary-send-eth
|
|
242
242
|
poolManager.settle{value: amountIn}();
|
|
@@ -286,8 +286,8 @@ library JBSwapPoolLib {
|
|
|
286
286
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
287
287
|
uint256 amountToSend = amount0Delta < 0 ? uint256(amount1Delta) : uint256(amount0Delta);
|
|
288
288
|
|
|
289
|
-
// If input is native
|
|
290
|
-
// When originalTokenIn == NATIVE_TOKEN, normalizedIn is already the
|
|
289
|
+
// If input is the native token, wrap for V3.
|
|
290
|
+
// When originalTokenIn == NATIVE_TOKEN, normalizedIn is already the wrapped native address.
|
|
291
291
|
if (originalTokenIn == JBConstants.NATIVE_TOKEN) {
|
|
292
292
|
IWrappedNativeToken(normalizedIn).deposit{value: amountToSend}();
|
|
293
293
|
}
|
|
@@ -302,7 +302,7 @@ library JBSwapPoolLib {
|
|
|
302
302
|
|
|
303
303
|
/// @notice Externally accessible pool discovery for testing and off-chain queries.
|
|
304
304
|
/// @param config The swap configuration (factory, pool manager, etc.).
|
|
305
|
-
/// @param normalizedTokenIn The normalized input token address (
|
|
305
|
+
/// @param normalizedTokenIn The normalized input token address (wrapped native token, not NATIVE_TOKEN).
|
|
306
306
|
/// @param normalizedTokenOut The normalized output token address.
|
|
307
307
|
/// @return isV4 Whether the best pool is a V4 pool.
|
|
308
308
|
/// @return v3Pool The best V3 pool (or address(0) if V4 is better).
|
|
@@ -327,8 +327,8 @@ library JBSwapPoolLib {
|
|
|
327
327
|
//*********************************************************************//
|
|
328
328
|
|
|
329
329
|
/// @notice Find the highest liquidity pool across all V3 fee tiers and V4 pool configurations.
|
|
330
|
-
/// @param config The swap configuration (factory, pool manager, hook,
|
|
331
|
-
/// @param normalizedTokenIn The normalized input token address (
|
|
330
|
+
/// @param config The swap configuration (factory, pool manager, hook, wrapped native token addresses).
|
|
331
|
+
/// @param normalizedTokenIn The normalized input token address (wrapped native token, not NATIVE_TOKEN).
|
|
332
332
|
/// @param normalizedTokenOut The normalized output token address.
|
|
333
333
|
/// @return isV4 Whether the best pool is a V4 pool.
|
|
334
334
|
/// @return v3Pool The best V3 pool (or address(0) if V4 is better).
|
|
@@ -419,7 +419,7 @@ library JBSwapPoolLib {
|
|
|
419
419
|
|
|
420
420
|
/// @notice Search V4 pools across 4 fee tiers and 2 hook configs for the best eligible liquidity.
|
|
421
421
|
/// @dev TWAP-capable hooked pools are preferred over hookless spot pools. Broken hooked pools are skipped.
|
|
422
|
-
/// @param config The swap configuration (pool manager, hook,
|
|
422
|
+
/// @param config The swap configuration (pool manager, hook, wrapped native token addresses).
|
|
423
423
|
/// @param normalizedTokenIn The normalized input token address.
|
|
424
424
|
/// @param normalizedTokenOut The normalized output token address.
|
|
425
425
|
/// @return bestKey The selected V4 pool key.
|
|
@@ -439,8 +439,8 @@ library JBSwapPoolLib {
|
|
|
439
439
|
PoolKey memory bestSpotKey;
|
|
440
440
|
uint128 bestSpotLiquidity;
|
|
441
441
|
|
|
442
|
-
// Convert to V4 convention before sorting:
|
|
443
|
-
// keys use address(0) for native
|
|
442
|
+
// Convert to V4 convention before sorting: the wrapped native token represents native tokens in the calling
|
|
443
|
+
// code, while V4 pool keys use address(0) for native tokens.
|
|
444
444
|
address sorted0;
|
|
445
445
|
address sorted1;
|
|
446
446
|
{
|
|
@@ -604,7 +604,7 @@ library JBSwapPoolLib {
|
|
|
604
604
|
}
|
|
605
605
|
|
|
606
606
|
/// @notice Get a V4 quote with dynamic slippage. Hooked pools must serve TWAP; hookless pools use spot fallback.
|
|
607
|
-
/// @param config The swap configuration (pool manager,
|
|
607
|
+
/// @param config The swap configuration (pool manager, wrapped native token addresses).
|
|
608
608
|
/// @param key The V4 pool key to quote against.
|
|
609
609
|
/// @param normalizedTokenIn The normalized input token address.
|
|
610
610
|
/// @param normalizedTokenOut The normalized output token address.
|
|
@@ -867,7 +867,7 @@ library JBSwapPoolLib {
|
|
|
867
867
|
//*********************************************************************//
|
|
868
868
|
|
|
869
869
|
/// @notice Quote via V4 TWAP/spot and execute swap. Separate function for stack isolation.
|
|
870
|
-
/// @param config The swap configuration (pool manager,
|
|
870
|
+
/// @param config The swap configuration (pool manager, wrapped native token addresses).
|
|
871
871
|
/// @param key The V4 pool key to swap through.
|
|
872
872
|
/// @param normalizedTokenIn The normalized input token address.
|
|
873
873
|
/// @param normalizedTokenOut The normalized output token address.
|
|
@@ -976,7 +976,7 @@ library JBSwapPoolLib {
|
|
|
976
976
|
}
|
|
977
977
|
|
|
978
978
|
/// @notice Execute a swap through a V4 pool via `PoolManager.unlock()`.
|
|
979
|
-
/// @param config The swap configuration (pool manager,
|
|
979
|
+
/// @param config The swap configuration (pool manager, wrapped native token addresses).
|
|
980
980
|
/// @param key The V4 pool key to swap through.
|
|
981
981
|
/// @param normalizedTokenIn The normalized input token address.
|
|
982
982
|
/// @param amount The amount of input tokens to swap.
|
|
@@ -992,7 +992,7 @@ library JBSwapPoolLib {
|
|
|
992
992
|
internal
|
|
993
993
|
returns (uint256 amountOut)
|
|
994
994
|
{
|
|
995
|
-
// Convert
|
|
995
|
+
// Convert wrapped native token to address(0) for V4's native token convention.
|
|
996
996
|
address v4In = normalizedTokenIn == config.weth ? address(0) : normalizedTokenIn;
|
|
997
997
|
|
|
998
998
|
// Determine swap direction based on currency ordering in the pool key.
|
|
@@ -1021,9 +1021,9 @@ library JBSwapPoolLib {
|
|
|
1021
1021
|
amountOut = abi.decode(result, (uint256));
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
|
-
/// @notice Normalize a token address, converting the NATIVE_TOKEN sentinel to
|
|
1024
|
+
/// @notice Normalize a token address, converting the NATIVE_TOKEN sentinel to the wrapped native token.
|
|
1025
1025
|
/// @param token The token address to normalize.
|
|
1026
|
-
/// @param weth The
|
|
1026
|
+
/// @param weth The wrapped native token address on this chain.
|
|
1027
1027
|
/// @return The normalized token address.
|
|
1028
1028
|
function _normalize(address token, address weth) internal pure returns (address) {
|
|
1029
1029
|
return token == JBConstants.NATIVE_TOKEN ? weth : token;
|