@bananapus/suckers-v6 0.0.47 → 0.0.49
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 +3 -3
- package/script/Deploy.s.sol +125 -139
- package/script/helpers/SuckerDeploymentLib.sol +10 -12
- package/src/JBArbitrumSucker.sol +5 -2
- package/src/JBCCIPSucker.sol +3 -3
- package/src/JBCeloSucker.sol +2 -0
- package/src/JBOptimismSucker.sol +2 -0
- package/src/JBSucker.sol +42 -8
- package/src/JBSuckerRegistry.sol +79 -60
- package/src/JBSwapCCIPSucker.sol +38 -101
- package/src/deployers/JBSwapCCIPSuckerDeployer.sol +17 -17
- package/src/interfaces/IJBSucker.sol +11 -6
- package/src/interfaces/IJBSuckerRegistry.sol +6 -3
- package/src/interfaces/IL1ArbitrumGateway.sol +10 -10
- package/src/libraries/CCIPHelper.sol +64 -64
- package/src/libraries/JBCCIPLib.sol +18 -0
- package/src/libraries/JBRelayBeneficiary.sol +1 -1
- package/src/libraries/JBSuckerLib.sol +157 -161
- package/src/libraries/JBSwapPoolLib.sol +268 -268
- package/src/structs/PeerValueScratch.sol +18 -0
- package/src/utils/MerkleLib.sol +108 -108
package/src/JBSwapCCIPSucker.sol
CHANGED
|
@@ -175,19 +175,15 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
175
175
|
/// @custom:param nonce The CCIP nonce identifying the batch.
|
|
176
176
|
mapping(address token => mapping(uint64 nonce => ConversionRate)) internal _conversionRateOf;
|
|
177
177
|
|
|
178
|
-
/// @notice Highest nonce received so far per token. Used as upper bound for nonce iteration.
|
|
179
|
-
/// @custom:param token The local token address.
|
|
180
|
-
mapping(address token => uint64) internal _highestReceivedNonce;
|
|
181
|
-
|
|
182
178
|
/// @notice Count of populated batch nonces per token. Appended exactly once per batch in
|
|
183
179
|
/// `ccipReceive`, so it equals the number of received batches independent of CCIP ordering.
|
|
184
180
|
/// @custom:param token The local token address.
|
|
185
181
|
mapping(address token => uint64) internal _populatedNonceCount;
|
|
186
182
|
|
|
187
|
-
/// @notice Populated batch nonces per token, indexed by insertion order.
|
|
188
|
-
///
|
|
189
|
-
///
|
|
190
|
-
///
|
|
183
|
+
/// @notice Populated batch nonces per token, indexed by insertion order.
|
|
184
|
+
/// @dev `_findNonceForLeafIndex` walks this list directly. That bounds lookup by the number of
|
|
185
|
+
/// received batches, not by the highest nonce, so sparse or out-of-order CCIP delivery cannot
|
|
186
|
+
/// force the claim path to scan empty nonce slots.
|
|
191
187
|
/// @custom:param token The local token address.
|
|
192
188
|
/// @custom:param index The insertion index in [0, _populatedNonceCount[token]).
|
|
193
189
|
mapping(address token => mapping(uint64 index => uint64 nonce)) internal _populatedNonceByIndex;
|
|
@@ -201,6 +197,10 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
201
197
|
// ------------------- transient stored properties ------------------- //
|
|
202
198
|
//*********************************************************************//
|
|
203
199
|
|
|
200
|
+
/// @dev Reentrancy guard for the initial `ccipReceive` swap. Prevents claims from consuming newly received
|
|
201
|
+
/// swap output before the batch's conversion rate has been recorded.
|
|
202
|
+
bool transient _ccipReceiveSwapLocked;
|
|
203
|
+
|
|
204
204
|
/// @notice Leaf index + 1 of the claim currently in progress (set by the `claim` override).
|
|
205
205
|
/// @dev Transient storage — auto-resets to 0 each transaction, saving ~9,800 gas per claim vs SSTORE.
|
|
206
206
|
/// Value 0 means no active claim (bypass scaling); non-zero means leafIndex = value - 1.
|
|
@@ -344,13 +344,16 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
344
344
|
// Wrapped in try-catch so a swap failure doesn't revert the entire CCIP message
|
|
345
345
|
// (which would leave tokens stuck in the OffRamp). On failure, bridge tokens are
|
|
346
346
|
// stored for later retry via `retrySwap` (written below, after nonce validation).
|
|
347
|
+
_ccipReceiveSwapLocked = true;
|
|
347
348
|
try this.executeSwapExternal({
|
|
348
349
|
tokenIn: deliveredToken, tokenOut: localToken, amount: deliveredAmount
|
|
349
350
|
}) returns (
|
|
350
351
|
uint256 swapped
|
|
351
352
|
) {
|
|
353
|
+
_ccipReceiveSwapLocked = false;
|
|
352
354
|
localAmount = swapped;
|
|
353
355
|
} catch {
|
|
356
|
+
_ccipReceiveSwapLocked = false;
|
|
354
357
|
swapFailed = true;
|
|
355
358
|
// localAmount stays 0 — pendingSwapOf and conversion rate are written
|
|
356
359
|
// below, after fromRemote validates the nonce.
|
|
@@ -401,14 +404,6 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
401
404
|
unchecked {
|
|
402
405
|
_populatedNonceCount[localToken] = priorCount + 1;
|
|
403
406
|
}
|
|
404
|
-
|
|
405
|
-
// Track the highest nonce ever observed for this token. Read by
|
|
406
|
-
// `_findNonceForLeafIndex` as the binary-search upper bound. Out-of-order
|
|
407
|
-
// delivery keeps this monotonic — we only advance it when the new nonce is
|
|
408
|
-
// strictly higher than the prior maximum.
|
|
409
|
-
if (nonce > _highestReceivedNonce[localToken]) {
|
|
410
|
-
_highestReceivedNonce[localToken] = nonce;
|
|
411
|
-
}
|
|
412
407
|
}
|
|
413
408
|
|
|
414
409
|
// Store pendingSwapOf for failed swaps now that nonce is validated.
|
|
@@ -529,7 +524,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
529
524
|
/// @param claimData The claim data containing the leaf and proof.
|
|
530
525
|
function claim(JBClaim calldata claimData) public override {
|
|
531
526
|
// Block claims during retrySwap to prevent zero-backed minting via reentrancy.
|
|
532
|
-
if (_retrySwapLocked) revert JBSwapCCIPSucker_SwapPending(0);
|
|
527
|
+
if (_retrySwapLocked || _ccipReceiveSwapLocked) revert JBSwapCCIPSucker_SwapPending(0);
|
|
533
528
|
_currentClaimLeafIndex = claimData.leaf.index + 1;
|
|
534
529
|
super.claim(claimData);
|
|
535
530
|
// Clear stale transient context to prevent leaking into same-tx emergency exits.
|
|
@@ -573,7 +568,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
573
568
|
}
|
|
574
569
|
|
|
575
570
|
/// @notice Override to swap local tokens into bridge tokens before CCIP bridging.
|
|
576
|
-
/// @dev Does NOT modify `
|
|
571
|
+
/// @dev Does NOT modify `suckerMessage.amount` — keeps the original leaf-denomination total so the
|
|
577
572
|
/// receiving chain can use it (along with the actual delivered amount) to compute the proportional
|
|
578
573
|
/// scaling factor for individual claims.
|
|
579
574
|
/// Delegates CCIP message construction to JBCCIPLib (via DELEGATECALL) to reduce bytecode.
|
|
@@ -582,7 +577,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
582
577
|
/// @param token The local token to bridge.
|
|
583
578
|
/// @param amount The amount of local tokens to bridge.
|
|
584
579
|
/// @param remoteToken The remote token configuration (including minGas).
|
|
585
|
-
/// @param
|
|
580
|
+
/// @param suckerMessage The merkle root message to send to the remote chain.
|
|
586
581
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
587
582
|
function _sendRootOverAMB(
|
|
588
583
|
uint256 transportPayment,
|
|
@@ -590,7 +585,7 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
590
585
|
address token,
|
|
591
586
|
uint256 amount,
|
|
592
587
|
JBRemoteToken memory remoteToken,
|
|
593
|
-
JBMessageRoot memory
|
|
588
|
+
JBMessageRoot memory suckerMessage
|
|
594
589
|
)
|
|
595
590
|
internal
|
|
596
591
|
override
|
|
@@ -620,13 +615,13 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
620
615
|
BRIDGE_TOKEN.forceApprove({spender: address(CCIP_ROUTER), value: bridgeAmount});
|
|
621
616
|
}
|
|
622
617
|
|
|
623
|
-
// NOTE:
|
|
618
|
+
// NOTE: suckerMessage.amount stays as the original leaf-denomination total.
|
|
624
619
|
// Encode batch range [batchStart, batchEnd) so the receiver can resolve leaf ownership
|
|
625
620
|
// per-nonce without requiring contiguous nonce delivery.
|
|
626
621
|
uint256 batchStart = _lastSentCount[token];
|
|
627
622
|
uint256 batchEnd = index + 1;
|
|
628
623
|
_lastSentCount[token] = batchEnd;
|
|
629
|
-
encodedPayload = abi.encode(_CCIP_MSG_TYPE_ROOT, abi.encode(
|
|
624
|
+
encodedPayload = abi.encode(_CCIP_MSG_TYPE_ROOT, abi.encode(suckerMessage, batchStart, batchEnd));
|
|
630
625
|
}
|
|
631
626
|
|
|
632
627
|
{
|
|
@@ -703,94 +698,36 @@ contract JBSwapCCIPSucker is JBCCIPSucker, IUnlockCallback, IUniswapV3SwapCallba
|
|
|
703
698
|
// ----------------------- internal views ---------------------------- //
|
|
704
699
|
//*********************************************************************//
|
|
705
700
|
|
|
706
|
-
/// @notice Find the nonce whose batch contains the given leaf index.
|
|
707
|
-
/// @dev
|
|
708
|
-
///
|
|
709
|
-
///
|
|
710
|
-
///
|
|
711
|
-
/// Binary search exploits that monotonicity for O(log N) lookup.
|
|
712
|
-
/// @dev Gap handling: when the midpoint slot is empty (CCIP out-of-order delivery or sparse
|
|
713
|
-
/// attacker writes), the fallback walks `_populatedNonceByIndex` — the list of every nonce
|
|
714
|
-
/// actually populated. That bounds the worst case at `O(K)` SLOADs, where `K` is the number
|
|
715
|
-
/// of received batches, regardless of how sparse the populated set is inside `[1, maxNonce]`.
|
|
716
|
-
/// The empty-slot scans that drove `O(N)` under the prior linear fallback are eliminated.
|
|
701
|
+
/// @notice Find the received nonce whose batch contains the given leaf index.
|
|
702
|
+
/// @dev Walks `_populatedNonceByIndex` instead of `[1, highestNonce]`. The populated list is the
|
|
703
|
+
/// only set that can contain a claimable batch, and it stays compact even when CCIP delivers
|
|
704
|
+
/// nonce 10 before nonce 2. This keeps lookup O(K) where K is received batches, avoids sparse
|
|
705
|
+
/// empty-slot scans, and keeps the deployable bytecode below the EIP-170 size limit.
|
|
717
706
|
/// @param token The local token address.
|
|
718
707
|
/// @param leafIndex The leaf index from the claim.
|
|
719
708
|
/// @return The nonce of the batch containing this leaf, or 0 if no batches have been recorded.
|
|
720
709
|
function _findNonceForLeafIndex(address token, uint256 leafIndex) internal view returns (uint64) {
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
uint64
|
|
724
|
-
if (
|
|
725
|
-
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
// Wrap arithmetic in `unchecked` — `lo` and `hi` are always in `[1, maxNonce]` and the
|
|
731
|
-
// edge-guards (`mid == lo` / `mid == hi` breaks) prevent the only ways `mid - 1` or
|
|
732
|
-
// `mid + 1` could over/underflow. Skipping the compiler checks shaves enough bytecode to
|
|
733
|
-
// stay under EIP-170 without changing semantics.
|
|
710
|
+
// No populated batches for this token means there is no conversion rate to apply. Preserve
|
|
711
|
+
// nonce 0 as the "unbatched" sentinel used by `_addToBalance`'s non-claim path.
|
|
712
|
+
uint64 count = _populatedNonceCount[token];
|
|
713
|
+
if (count == 0) return 0;
|
|
714
|
+
|
|
715
|
+
// Walk only nonces that actually received a batch. The array is insertion-ordered, not
|
|
716
|
+
// sorted, because CCIP can deliver batches out of nonce order; each entry still points to a
|
|
717
|
+
// self-contained `[batchStart, batchEnd)` range written before the append.
|
|
734
718
|
unchecked {
|
|
735
|
-
|
|
736
|
-
uint64
|
|
737
|
-
|
|
738
|
-
// write guard in `ccipReceive`); a real batch always has `batchEnd > batchStart`.
|
|
739
|
-
uint256 end = _batchEndOf[token][mid];
|
|
740
|
-
|
|
741
|
-
// Empty midpoint from out-of-order CCIP delivery (the sender minted a higher nonce
|
|
742
|
-
// than the inbox has yet received, leaving holes in `[1, maxNonce]`) or a sparse
|
|
743
|
-
// pattern an attacker assembled by inflating `_highestReceivedNonce` without
|
|
744
|
-
// populating intermediate slots. The earlier linear `[lo, hi]` scan walked every
|
|
745
|
-
// empty slot, so worst-case cost was `O(maxNonce)` SLOADs — that's the residual
|
|
746
|
-
// the audit flagged. Walk `_populatedNonceByIndex` instead: it's `K` entries (one
|
|
747
|
-
// per received batch) and contains exactly the slots a real batch can cover.
|
|
748
|
-
if (end == 0) {
|
|
749
|
-
// Number of populated batch slots for this token — equal to the array length.
|
|
750
|
-
// Maintained by `ccipReceive`'s append (one SSTORE per first-time receive).
|
|
751
|
-
uint64 count = _populatedNonceCount[token];
|
|
752
|
-
|
|
753
|
-
// Linear walk over the populated set. Bounded by `count`, not `maxNonce`, so
|
|
754
|
-
// sparse adversarial patterns can't inflate this loop with empty slots.
|
|
755
|
-
for (uint64 i; i < count; i++) {
|
|
756
|
-
// Insertion-ordered nonce at index `i`. May be any value in `[1, maxNonce]`
|
|
757
|
-
// because CCIP delivery is out-of-order; the array is not sorted by nonce.
|
|
758
|
-
uint64 n = _populatedNonceByIndex[token][i];
|
|
759
|
-
|
|
760
|
-
// Read end of this batch's leaf range. Always `> 0` here — we only push to
|
|
761
|
-
// `_populatedNonceByIndex` after the corresponding `_batchEndOf` write.
|
|
762
|
-
uint256 nEnd = _batchEndOf[token][n];
|
|
763
|
-
|
|
764
|
-
// Coverage test: each batch's `[batchStart, batchEnd)` is non-overlapping
|
|
765
|
-
// across populated nonces, so a hit is unique. The `[lo, hi]` window from
|
|
766
|
-
// the binary search isn't applied — the binary-search invariant guarantees
|
|
767
|
-
// the leaf can only live in a populated nonce, so an out-of-window match
|
|
768
|
-
// would mean the binary search was already wrong; falling through to the
|
|
769
|
-
// next iteration costs less than the extra two compares per iteration.
|
|
770
|
-
if (leafIndex >= _batchStartOf[token][n] && leafIndex < nEnd) return n;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// No populated nonce contains `leafIndex` for this token. Break out of the
|
|
774
|
-
// outer binary-search loop and fall through to the shared revert below.
|
|
775
|
-
break;
|
|
776
|
-
}
|
|
719
|
+
for (uint64 i; i < count; i++) {
|
|
720
|
+
uint64 nonce = _populatedNonceByIndex[token][i];
|
|
721
|
+
uint256 end = _batchEndOf[token][nonce];
|
|
777
722
|
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
|
|
781
|
-
if (leafIndex < start) {
|
|
782
|
-
if (mid == lo) break; // Guard against `mid - 1` underflow at the lower edge.
|
|
783
|
-
hi = mid - 1;
|
|
784
|
-
} else if (leafIndex >= end) {
|
|
785
|
-
if (mid == hi) break; // Mirror guard at the upper edge.
|
|
786
|
-
lo = mid + 1;
|
|
787
|
-
} else {
|
|
788
|
-
return mid; // `start <= leafIndex < end`: leaf is inside this batch.
|
|
789
|
-
}
|
|
723
|
+
// Ranges are non-overlapping across populated nonces. The first hit is therefore
|
|
724
|
+
// the unique conversion-rate batch for this claim leaf.
|
|
725
|
+
if (leafIndex >= _batchStartOf[token][nonce] && leafIndex < end) return nonce;
|
|
790
726
|
}
|
|
791
727
|
}
|
|
792
728
|
|
|
793
|
-
//
|
|
729
|
+
// Batches exist for the token, but none cover this leaf index; surface the same error used
|
|
730
|
+
// before the compact populated-nonce index was introduced.
|
|
794
731
|
revert JBSwapCCIPSucker_BatchNotReceived({nonce: 0});
|
|
795
732
|
}
|
|
796
733
|
}
|
|
@@ -68,17 +68,17 @@ contract JBSwapCCIPSuckerDeployer is JBCCIPSuckerDeployer, IJBSwapCCIPSuckerDepl
|
|
|
68
68
|
//*********************************************************************//
|
|
69
69
|
|
|
70
70
|
/// @notice Configure the swap-specific constants. Can only be called once by the configurator.
|
|
71
|
-
/// @param
|
|
72
|
-
/// @param
|
|
73
|
-
/// @param
|
|
74
|
-
/// @param
|
|
75
|
-
/// @param
|
|
71
|
+
/// @param newBridgeToken The ERC-20 token used for CCIP bridging.
|
|
72
|
+
/// @param newPoolManager The Uniswap V4 PoolManager (can be address(0) if V4 unavailable).
|
|
73
|
+
/// @param newV3Factory The Uniswap V3 factory (can be address(0) if V3 unavailable).
|
|
74
|
+
/// @param newUniv4Hook The V4 hook for pool discovery (optional, address(0) if none).
|
|
75
|
+
/// @param newWrappedNativeToken The ERC-20 wrapper address for the chain's native token (e.g. WETH on Ethereum).
|
|
76
76
|
function setSwapConstants(
|
|
77
|
-
IERC20
|
|
78
|
-
IPoolManager
|
|
79
|
-
IUniswapV3Factory
|
|
80
|
-
address
|
|
81
|
-
address
|
|
77
|
+
IERC20 newBridgeToken,
|
|
78
|
+
IPoolManager newPoolManager,
|
|
79
|
+
IUniswapV3Factory newV3Factory,
|
|
80
|
+
address newUniv4Hook,
|
|
81
|
+
address newWrappedNativeToken
|
|
82
82
|
)
|
|
83
83
|
external
|
|
84
84
|
{
|
|
@@ -93,23 +93,23 @@ contract JBSwapCCIPSuckerDeployer is JBCCIPSuckerDeployer, IJBSwapCCIPSuckerDepl
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// Make sure the bridge token is not the zero address.
|
|
96
|
-
if (address(
|
|
97
|
-
revert JBSwapCCIPSuckerDeployer_InvalidSwapConfig({bridgeToken: address(
|
|
96
|
+
if (address(newBridgeToken) == address(0)) {
|
|
97
|
+
revert JBSwapCCIPSuckerDeployer_InvalidSwapConfig({bridgeToken: address(newBridgeToken)});
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Store the bridge token.
|
|
101
|
-
bridgeToken =
|
|
101
|
+
bridgeToken = newBridgeToken;
|
|
102
102
|
|
|
103
103
|
// Store the Uniswap V4 pool manager.
|
|
104
|
-
poolManager =
|
|
104
|
+
poolManager = newPoolManager;
|
|
105
105
|
|
|
106
106
|
// Store the Uniswap V3 factory.
|
|
107
|
-
v3Factory =
|
|
107
|
+
v3Factory = newV3Factory;
|
|
108
108
|
|
|
109
109
|
// Store the Uniswap V4 hook address.
|
|
110
|
-
univ4Hook =
|
|
110
|
+
univ4Hook = newUniv4Hook;
|
|
111
111
|
|
|
112
112
|
// Store the wrapped native token address.
|
|
113
|
-
wrappedNativeToken =
|
|
113
|
+
wrappedNativeToken = newWrappedNativeToken;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -128,12 +128,6 @@ interface IJBSucker is IERC165 {
|
|
|
128
128
|
/// @return chainId The remote chain ID.
|
|
129
129
|
function peerChainId() external view returns (uint256 chainId);
|
|
130
130
|
|
|
131
|
-
/// @notice The last known total token supply on the peer chain, updated each time a bridge message is received.
|
|
132
|
-
/// @dev Used by data hooks to compute `effectiveTotalSupply = localSupply + sum(peerChainTotalSupply)` across all
|
|
133
|
-
/// suckers, preventing cash out tax bypass on chains where a holder dominates the local supply.
|
|
134
|
-
/// @return The peer chain's total supply.
|
|
135
|
-
function peerChainTotalSupply() external view returns (uint256);
|
|
136
|
-
|
|
137
131
|
/// @notice The aggregate peer chain balance, normalized to a desired currency and decimal precision using JBPrices.
|
|
138
132
|
/// @dev The balance is stored as ETH-denominated (18 decimals) and converted to the requested currency/decimals
|
|
139
133
|
/// using the local JBPrices oracle.
|
|
@@ -150,6 +144,12 @@ interface IJBSucker is IERC165 {
|
|
|
150
144
|
/// @return A `JBDenominatedAmount` with the converted value.
|
|
151
145
|
function peerChainSurplusOf(uint256 decimals, uint256 currency) external view returns (JBDenominatedAmount memory);
|
|
152
146
|
|
|
147
|
+
/// @notice The last known total token supply on the peer chain, updated each time a bridge message is received.
|
|
148
|
+
/// @dev Used by data hooks to compute `effectiveTotalSupply = localSupply + sum(peerChainTotalSupply)` across all
|
|
149
|
+
/// suckers, preventing cash out tax bypass on chains where a holder dominates the local supply.
|
|
150
|
+
/// @return The peer chain's total supply.
|
|
151
|
+
function peerChainTotalSupply() external view returns (uint256);
|
|
152
|
+
|
|
153
153
|
/// @notice The ID of the project on the local chain that this sucker is associated with.
|
|
154
154
|
/// @return The project ID.
|
|
155
155
|
function projectId() external view returns (uint256);
|
|
@@ -159,6 +159,11 @@ interface IJBSucker is IERC165 {
|
|
|
159
159
|
/// @return The remote token info.
|
|
160
160
|
function remoteTokenFor(address token) external view returns (JBRemoteToken memory);
|
|
161
161
|
|
|
162
|
+
/// @notice The freshness key of the latest accepted peer-chain economic snapshot.
|
|
163
|
+
/// @dev Higher values are fresher. The key is source-chain monotonic, not a value magnitude.
|
|
164
|
+
/// @return The latest peer-chain snapshot freshness key.
|
|
165
|
+
function snapshotTimestamp() external view returns (uint256);
|
|
166
|
+
|
|
162
167
|
/// @notice The current deprecation state of this sucker.
|
|
163
168
|
/// @return The sucker state.
|
|
164
169
|
function state() external view returns (JBSuckerState);
|
|
@@ -77,13 +77,15 @@ interface IJBSuckerRegistry {
|
|
|
77
77
|
function suckersOf(uint256 projectId) external view returns (address[] memory);
|
|
78
78
|
|
|
79
79
|
/// @notice The cumulative total supply across all remote peer chains for a project.
|
|
80
|
-
/// @dev
|
|
80
|
+
/// @dev Dedupes same-peer active suckers by freshest snapshot, then sums peer-chain values. Silently skips suckers
|
|
81
|
+
/// that revert.
|
|
81
82
|
/// @param projectId The ID of the project.
|
|
82
83
|
/// @return totalSupply The combined peer chain total supply.
|
|
83
84
|
function remoteTotalSupplyOf(uint256 projectId) external view returns (uint256 totalSupply);
|
|
84
85
|
|
|
85
86
|
/// @notice The cumulative balance across all remote peer chains for a project, denominated in a given currency.
|
|
86
|
-
/// @dev
|
|
87
|
+
/// @dev Dedupes same-peer active suckers by freshest snapshot, then sums peer-chain values. Silently skips suckers
|
|
88
|
+
/// that revert.
|
|
87
89
|
/// @param projectId The ID of the project.
|
|
88
90
|
/// @param decimals The decimal precision for the returned value.
|
|
89
91
|
/// @param currency The currency to normalize to.
|
|
@@ -98,7 +100,8 @@ interface IJBSuckerRegistry {
|
|
|
98
100
|
returns (uint256 balance);
|
|
99
101
|
|
|
100
102
|
/// @notice The cumulative surplus across all remote peer chains for a project, denominated in a given currency.
|
|
101
|
-
/// @dev
|
|
103
|
+
/// @dev Dedupes same-peer active suckers by freshest snapshot, then sums peer-chain values. Silently skips suckers
|
|
104
|
+
/// that revert.
|
|
102
105
|
/// @param projectId The ID of the project.
|
|
103
106
|
/// @param decimals The decimal precision for the returned value.
|
|
104
107
|
/// @param currency The currency to normalize to.
|
|
@@ -4,18 +4,18 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
/// @notice Interface for Arbitrum L1 gateways to query the exact calldata they construct for retryable tickets.
|
|
5
5
|
interface IL1ArbitrumGateway {
|
|
6
6
|
/// @notice Returns the calldata that the gateway will submit as a retryable ticket to the Inbox.
|
|
7
|
-
/// @param
|
|
8
|
-
/// @param
|
|
9
|
-
/// @param
|
|
10
|
-
/// @param
|
|
11
|
-
/// @param
|
|
7
|
+
/// @param token The L1 token address.
|
|
8
|
+
/// @param from The sender on L1.
|
|
9
|
+
/// @param to The recipient on L2.
|
|
10
|
+
/// @param amount The amount of tokens to bridge.
|
|
11
|
+
/// @param data Additional data (forwarded to the L2 gateway).
|
|
12
12
|
/// @return The ABI-encoded calldata for `finalizeInboundTransfer` on the L2 gateway.
|
|
13
13
|
function getOutboundCalldata(
|
|
14
|
-
address
|
|
15
|
-
address
|
|
16
|
-
address
|
|
17
|
-
uint256
|
|
18
|
-
bytes calldata
|
|
14
|
+
address token,
|
|
15
|
+
address from,
|
|
16
|
+
address to,
|
|
17
|
+
uint256 amount,
|
|
18
|
+
bytes calldata data
|
|
19
19
|
)
|
|
20
20
|
external
|
|
21
21
|
view
|
|
@@ -133,6 +133,70 @@ library CCIPHelper {
|
|
|
133
133
|
/// @notice The wrapped native token address for Tempo mod chain.
|
|
134
134
|
address public constant TEMPO_MOD_WETH = 0xbb2D3310E4232085d432A2e04b2Ac09c46F634E4;
|
|
135
135
|
|
|
136
|
+
/// @notice Returns the LINK token address for a given chain ID.
|
|
137
|
+
/// @param chainId The EVM chain ID to look up.
|
|
138
|
+
/// @return link The LINK token address.
|
|
139
|
+
function linkOfChain(uint256 chainId) public pure returns (address link) {
|
|
140
|
+
if (chainId == ETH_ID) {
|
|
141
|
+
return ETH_LINK;
|
|
142
|
+
} else if (chainId == OP_ID) {
|
|
143
|
+
return OP_LINK;
|
|
144
|
+
} else if (chainId == ARB_ID) {
|
|
145
|
+
return ARB_LINK;
|
|
146
|
+
} else if (chainId == BASE_ID) {
|
|
147
|
+
return BASE_LINK;
|
|
148
|
+
} else if (chainId == ETH_SEP_ID) {
|
|
149
|
+
return ETH_SEP_LINK;
|
|
150
|
+
} else if (chainId == OP_SEP_ID) {
|
|
151
|
+
return OP_SEP_LINK;
|
|
152
|
+
} else if (chainId == ARB_SEP_ID) {
|
|
153
|
+
return ARB_SEP_LINK;
|
|
154
|
+
} else if (chainId == BASE_SEP_ID) {
|
|
155
|
+
return BASE_SEP_LINK;
|
|
156
|
+
} else if (chainId == TEMPO_ID) {
|
|
157
|
+
return TEMPO_LINK;
|
|
158
|
+
} else if (chainId == TEMPO_MOD_ID) {
|
|
159
|
+
return TEMPO_MOD_LINK;
|
|
160
|
+
} else {
|
|
161
|
+
revert CCIPHelper_UnsupportedChain({chainId: chainId});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// @notice Returns the wrapped native token address for a given chain ID.
|
|
166
|
+
/// @param chainId The EVM chain ID to look up.
|
|
167
|
+
/// @return weth The wrapped native token address.
|
|
168
|
+
function wethOfChain(uint256 chainId) public pure returns (address weth) {
|
|
169
|
+
if (chainId == ETH_ID) {
|
|
170
|
+
return ETH_WETH;
|
|
171
|
+
} else if (chainId == OP_ID) {
|
|
172
|
+
return OP_WETH;
|
|
173
|
+
} else if (chainId == ARB_ID) {
|
|
174
|
+
return ARB_WETH;
|
|
175
|
+
} else if (chainId == POLY_ID) {
|
|
176
|
+
return POLY_WETH;
|
|
177
|
+
} else if (chainId == AVA_ID) {
|
|
178
|
+
return AVA_WETH;
|
|
179
|
+
} else if (chainId == BNB_ID) {
|
|
180
|
+
return BNB_WETH;
|
|
181
|
+
} else if (chainId == BASE_ID) {
|
|
182
|
+
return BASE_WETH;
|
|
183
|
+
} else if (chainId == ETH_SEP_ID) {
|
|
184
|
+
return ETH_SEP_WETH;
|
|
185
|
+
} else if (chainId == ARB_SEP_ID) {
|
|
186
|
+
return ARB_SEP_WETH;
|
|
187
|
+
} else if (chainId == OP_SEP_ID) {
|
|
188
|
+
return OP_SEP_WETH;
|
|
189
|
+
} else if (chainId == BASE_SEP_ID) {
|
|
190
|
+
return BASE_SEP_WETH;
|
|
191
|
+
} else if (chainId == TEMPO_ID) {
|
|
192
|
+
return TEMPO_WETH;
|
|
193
|
+
} else if (chainId == TEMPO_MOD_ID) {
|
|
194
|
+
return TEMPO_MOD_WETH;
|
|
195
|
+
} else {
|
|
196
|
+
revert CCIPHelper_UnsupportedChain({chainId: chainId});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
136
200
|
/// @notice Returns the CCIP router address for a given chain ID.
|
|
137
201
|
/// @param chainId The EVM chain ID to look up.
|
|
138
202
|
/// @return router The CCIP router address.
|
|
@@ -202,68 +266,4 @@ library CCIPHelper {
|
|
|
202
266
|
revert CCIPHelper_UnsupportedChain({chainId: chainId});
|
|
203
267
|
}
|
|
204
268
|
}
|
|
205
|
-
|
|
206
|
-
/// @notice Returns the wrapped native token address for a given chain ID.
|
|
207
|
-
/// @param chainId The EVM chain ID to look up.
|
|
208
|
-
/// @return weth The wrapped native token address.
|
|
209
|
-
function wethOfChain(uint256 chainId) public pure returns (address weth) {
|
|
210
|
-
if (chainId == ETH_ID) {
|
|
211
|
-
return ETH_WETH;
|
|
212
|
-
} else if (chainId == OP_ID) {
|
|
213
|
-
return OP_WETH;
|
|
214
|
-
} else if (chainId == ARB_ID) {
|
|
215
|
-
return ARB_WETH;
|
|
216
|
-
} else if (chainId == POLY_ID) {
|
|
217
|
-
return POLY_WETH;
|
|
218
|
-
} else if (chainId == AVA_ID) {
|
|
219
|
-
return AVA_WETH;
|
|
220
|
-
} else if (chainId == BNB_ID) {
|
|
221
|
-
return BNB_WETH;
|
|
222
|
-
} else if (chainId == BASE_ID) {
|
|
223
|
-
return BASE_WETH;
|
|
224
|
-
} else if (chainId == ETH_SEP_ID) {
|
|
225
|
-
return ETH_SEP_WETH;
|
|
226
|
-
} else if (chainId == ARB_SEP_ID) {
|
|
227
|
-
return ARB_SEP_WETH;
|
|
228
|
-
} else if (chainId == OP_SEP_ID) {
|
|
229
|
-
return OP_SEP_WETH;
|
|
230
|
-
} else if (chainId == BASE_SEP_ID) {
|
|
231
|
-
return BASE_SEP_WETH;
|
|
232
|
-
} else if (chainId == TEMPO_ID) {
|
|
233
|
-
return TEMPO_WETH;
|
|
234
|
-
} else if (chainId == TEMPO_MOD_ID) {
|
|
235
|
-
return TEMPO_MOD_WETH;
|
|
236
|
-
} else {
|
|
237
|
-
revert CCIPHelper_UnsupportedChain({chainId: chainId});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/// @notice Returns the LINK token address for a given chain ID.
|
|
242
|
-
/// @param chainId The EVM chain ID to look up.
|
|
243
|
-
/// @return link The LINK token address.
|
|
244
|
-
function linkOfChain(uint256 chainId) public pure returns (address link) {
|
|
245
|
-
if (chainId == ETH_ID) {
|
|
246
|
-
return ETH_LINK;
|
|
247
|
-
} else if (chainId == OP_ID) {
|
|
248
|
-
return OP_LINK;
|
|
249
|
-
} else if (chainId == ARB_ID) {
|
|
250
|
-
return ARB_LINK;
|
|
251
|
-
} else if (chainId == BASE_ID) {
|
|
252
|
-
return BASE_LINK;
|
|
253
|
-
} else if (chainId == ETH_SEP_ID) {
|
|
254
|
-
return ETH_SEP_LINK;
|
|
255
|
-
} else if (chainId == OP_SEP_ID) {
|
|
256
|
-
return OP_SEP_LINK;
|
|
257
|
-
} else if (chainId == ARB_SEP_ID) {
|
|
258
|
-
return ARB_SEP_LINK;
|
|
259
|
-
} else if (chainId == BASE_SEP_ID) {
|
|
260
|
-
return BASE_SEP_LINK;
|
|
261
|
-
} else if (chainId == TEMPO_ID) {
|
|
262
|
-
return TEMPO_LINK;
|
|
263
|
-
} else if (chainId == TEMPO_MOD_ID) {
|
|
264
|
-
return TEMPO_MOD_LINK;
|
|
265
|
-
} else {
|
|
266
|
-
revert CCIPHelper_UnsupportedChain({chainId: chainId});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
269
|
}
|
|
@@ -133,10 +133,15 @@ library JBCCIPLib {
|
|
|
133
133
|
SafeERC20.forceApprove({token: IERC20(feeToken), spender: router, value: totalApproval});
|
|
134
134
|
|
|
135
135
|
ICCIPRouter(router).ccipSend({destinationChainSelector: chainSel, message: message});
|
|
136
|
+
|
|
137
|
+
_clearTokenAmountApprovals({router: router, tokenAmounts: tokenAmounts});
|
|
138
|
+
SafeERC20.forceApprove({token: IERC20(feeToken), spender: router, value: 0});
|
|
136
139
|
} else {
|
|
137
140
|
// Native ETH fee path.
|
|
138
141
|
ICCIPRouter(router).ccipSend{value: fees}({destinationChainSelector: chainSel, message: message});
|
|
139
142
|
|
|
143
|
+
_clearTokenAmountApprovals({router: router, tokenAmounts: tokenAmounts});
|
|
144
|
+
|
|
140
145
|
// Calculate the excess transport payment to refund.
|
|
141
146
|
refundAmount = transportPayment - fees;
|
|
142
147
|
|
|
@@ -189,4 +194,17 @@ library JBCCIPLib {
|
|
|
189
194
|
// ABI-decode the type and payload from the raw data.
|
|
190
195
|
(messageType, payload) = abi.decode(data, (uint8, bytes));
|
|
191
196
|
}
|
|
197
|
+
|
|
198
|
+
//*********************************************************************//
|
|
199
|
+
// ----------------------- private helpers --------------------------- //
|
|
200
|
+
//*********************************************************************//
|
|
201
|
+
|
|
202
|
+
/// @notice Clear token approvals granted to the CCIP router for the current message.
|
|
203
|
+
/// @param router The CCIP router whose allowances should be revoked.
|
|
204
|
+
/// @param tokenAmounts The bridged token amounts approved for this message.
|
|
205
|
+
function _clearTokenAmountApprovals(address router, Client.EVMTokenAmount[] memory tokenAmounts) private {
|
|
206
|
+
for (uint256 i; i < tokenAmounts.length; i++) {
|
|
207
|
+
SafeERC20.forceApprove({token: IERC20(tokenAmounts[i].token), spender: router, value: 0});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
192
210
|
}
|
|
@@ -35,7 +35,7 @@ library JBRelayBeneficiary {
|
|
|
35
35
|
returns (address effectiveBeneficiary)
|
|
36
36
|
{
|
|
37
37
|
// Only trust relay metadata when the payer is a registered sucker for this project.
|
|
38
|
-
if (!registry.isSuckerOf(projectId, payer)) {
|
|
38
|
+
if (!registry.isSuckerOf({projectId: projectId, addr: payer})) {
|
|
39
39
|
return beneficiary;
|
|
40
40
|
}
|
|
41
41
|
|