@bananapus/suckers-v6 0.0.62 → 0.0.64
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/foundry.toml +3 -0
- package/package.json +1 -1
- package/src/JBSucker.sol +111 -6
- package/src/{libraries → archive}/JBSwapPoolLib.sol +0 -3
- package/src/interfaces/IJBSucker.sol +9 -0
- /package/src/{interfaces → archive}/IJBSwapCCIPSuckerDeployer.sol +0 -0
- /package/src/{structs → archive}/JBConversionRate.sol +0 -0
- /package/src/{structs → archive}/JBPendingSwap.sol +0 -0
- /package/src/{JBSwapCCIPSucker.sol → archive/JBSwapCCIPSucker.sol} +0 -0
- /package/src/{deployers → archive}/JBSwapCCIPSuckerDeployer.sol +0 -0
- /package/src/{libraries → archive}/JBSwapLib.sol +0 -0
package/foundry.toml
CHANGED
|
@@ -5,6 +5,9 @@ evm_version = 'cancun'
|
|
|
5
5
|
optimizer_runs = 200
|
|
6
6
|
libs = ["node_modules", "lib"]
|
|
7
7
|
fs_permissions = [{ access = "read-write", path = "./"}]
|
|
8
|
+
# Archived, unused contracts (e.g. JBSwapCCIPSucker) and their tests live under `archive/` folders and are
|
|
9
|
+
# excluded from compilation, sizing, and test runs. Kept for reference only.
|
|
10
|
+
skip = ["src/archive/**", "test/archive/**"]
|
|
8
11
|
|
|
9
12
|
[fuzz]
|
|
10
13
|
runs = 4096
|
package/package.json
CHANGED
package/src/JBSucker.sol
CHANGED
|
@@ -109,6 +109,15 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
109
109
|
// ------------------------- internal constants ----------------------- //
|
|
110
110
|
//*********************************************************************//
|
|
111
111
|
|
|
112
|
+
/// @notice The number of recently-accepted inbox roots retained per token so that a proof generated against a
|
|
113
|
+
/// slightly older root still validates after a later `toRemote`/`fromRemote` advances the inbox.
|
|
114
|
+
/// @dev The inbox is append-only and a leaf's `(hash, index)` is stable across roots, so honoring a small window
|
|
115
|
+
/// of recent roots is safe: the `_executedFor` double-spend guard is keyed by `(token, leafIndex)` — independent
|
|
116
|
+
/// of which retained root validated the proof — so an executed leaf stays blocked no matter which retained root a
|
|
117
|
+
/// later proof matches. The window only widens which still-valid proofs are accepted; it never relaxes the
|
|
118
|
+
/// double-spend guard.
|
|
119
|
+
uint256 internal constant _INBOX_ROOT_RING_SIZE = 4;
|
|
120
|
+
|
|
112
121
|
/// @notice The depth of the merkle tree used to store the outbox and inbox.
|
|
113
122
|
uint32 internal constant _TREE_DEPTH = 32;
|
|
114
123
|
|
|
@@ -184,6 +193,22 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
184
193
|
/// @custom:param token The local terminal token to get the inbox for.
|
|
185
194
|
mapping(address token => JBInboxTreeRoot root) internal _inboxOf;
|
|
186
195
|
|
|
196
|
+
/// @notice The index of the most recently-written slot in `_inboxRootRingOf[token]`.
|
|
197
|
+
/// @dev Advances modulo `_INBOX_ROOT_RING_SIZE` each time `fromRemote` accepts a newer-nonce root, overwriting the
|
|
198
|
+
/// oldest retained root. Defaults to `0`; the first accepted root is written to slot `1` after the pre-increment.
|
|
199
|
+
/// @custom:param token The local terminal token to get the ring cursor for.
|
|
200
|
+
mapping(address token => uint256 cursor) internal _inboxRootRingCursorOf;
|
|
201
|
+
|
|
202
|
+
/// @notice A small ring buffer of the most recently-accepted inbox roots for a given token.
|
|
203
|
+
/// @dev Holds the last `_INBOX_ROOT_RING_SIZE` distinct roots accepted by `fromRemote` (the newest is also mirrored
|
|
204
|
+
/// in `_inboxOf[token].root`). `_validate` accepts a proof matching ANY retained, not-yet-executed leaf's root, so
|
|
205
|
+
/// proofs generated against a recent-but-superseded root keep validating without regenerated branches. The window
|
|
206
|
+
/// is intentionally small: it bounds storage/gas and keeps the set of accepted roots tightly recent. Unused slots
|
|
207
|
+
/// are `bytes32(0)`, which `_validate` skips (a real root is never `bytes32(0)` — the empty-tree root is
|
|
208
|
+
/// `MerkleLib.Z_32`, and roots only enter the ring once a non-empty tree has been bridged).
|
|
209
|
+
/// @custom:param token The local terminal token to get the retained inbox roots for.
|
|
210
|
+
mapping(address token => bytes32[_INBOX_ROOT_RING_SIZE] roots) internal _inboxRootRingOf;
|
|
211
|
+
|
|
187
212
|
/// @notice The local token that has reserved each remote token address in this sucker.
|
|
188
213
|
/// @dev Inbound roots are keyed by `root.token` on the destination chain. Within a single sucker, allowing two
|
|
189
214
|
/// local tokens to send roots to the same remote token would give them independent source nonces but one shared
|
|
@@ -286,12 +311,27 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
286
311
|
|
|
287
312
|
/// @notice Claim multiple bridged entries in a single transaction. Each claim mints project tokens for the
|
|
288
313
|
/// beneficiary and deposits the corresponding terminal tokens into the project's local balance.
|
|
314
|
+
/// @dev Per-leaf resilience: each leaf is claimed through an external `this.claim(JBClaim)` sub-call wrapped in
|
|
315
|
+
/// try/catch, so a single failing or stale leaf (e.g. its inbox root has not arrived yet, it was already executed,
|
|
316
|
+
/// or a transient mint/add-to-balance dependency reverts) only skips that one leaf — the rest of the batch still
|
|
317
|
+
/// settles. Because the sub-call is a separate message frame, a caught revert rolls back every state change that
|
|
318
|
+
/// leaf attempted (its `_executedFor` bit, its `executedLeafHashOf` entry, and any `_addToBalance`/`mintTokensOf`
|
|
319
|
+
/// effects), so the skipped leaf stays fully claimable later. Routing through `this.claim` is safe because the
|
|
320
|
+
/// single-leaf `claim` mints to `claimData.leaf.beneficiary` and adds funds to the project balance — it never
|
|
321
|
+
/// depends on `msg.sender` being the original batch caller, so the self-call does not change who is credited.
|
|
289
322
|
/// @param claims A list of claims to perform (including the terminal token, merkle tree leaf, and proof for each
|
|
290
323
|
/// claim).
|
|
291
324
|
function claim(JBClaim[] calldata claims) external override {
|
|
292
|
-
// Claim each.
|
|
325
|
+
// Claim each. Isolate each leaf in its own external sub-call so one bad/stale leaf cannot revert the batch.
|
|
293
326
|
for (uint256 i; i < claims.length;) {
|
|
294
|
-
claim(claims[i])
|
|
327
|
+
try this.claim(claims[i]) {
|
|
328
|
+
// Leaf settled successfully.
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// The leaf failed: its sub-call reverted atomically, leaving no persisted state for it. Surface the
|
|
332
|
+
// skip for off-chain monitoring; the leaf remains claimable in a future call.
|
|
333
|
+
emit ClaimFailed({token: claims[i].token, index: claims[i].leaf.index, caller: _msgSender()});
|
|
334
|
+
}
|
|
295
335
|
unchecked {
|
|
296
336
|
++i;
|
|
297
337
|
}
|
|
@@ -448,6 +488,15 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
448
488
|
inbox.nonce = root.remoteRoot.nonce;
|
|
449
489
|
inbox.root = root.remoteRoot.root;
|
|
450
490
|
|
|
491
|
+
// Retain the newly-accepted root in the per-token ring so proofs generated against a recent-but-superseded
|
|
492
|
+
// root still validate. Advance the cursor and overwrite the oldest slot. Skipping the empty-tree root keeps
|
|
493
|
+
// the ring populated only with roots that can actually back a claim.
|
|
494
|
+
if (root.remoteRoot.root != MerkleLib.Z_32) {
|
|
495
|
+
uint256 nextCursor = (_inboxRootRingCursorOf[localToken] + 1) % _INBOX_ROOT_RING_SIZE;
|
|
496
|
+
_inboxRootRingCursorOf[localToken] = nextCursor;
|
|
497
|
+
_inboxRootRingOf[localToken][nextCursor] = root.remoteRoot.root;
|
|
498
|
+
}
|
|
499
|
+
|
|
451
500
|
emit NewInboxTreeRoot({
|
|
452
501
|
token: localToken, nonce: root.remoteRoot.nonce, root: root.remoteRoot.root, caller: _msgSender()
|
|
453
502
|
});
|
|
@@ -1345,10 +1394,21 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1345
1394
|
});
|
|
1346
1395
|
executedLeafHashOf[terminalToken][index] = leafHash;
|
|
1347
1396
|
|
|
1348
|
-
//
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1397
|
+
// Select which retained inbox root this proof should be validated against. A proof generated against any of
|
|
1398
|
+
// the last `_INBOX_ROOT_RING_SIZE` accepted roots is honored, not only the latest, so a proof does not become
|
|
1399
|
+
// unusable the instant a newer root arrives. If the proof matches no retained root, the latest root is used as
|
|
1400
|
+
// the fallback so the failure path reverts with the canonical `JBSucker_InvalidProof` against the live root.
|
|
1401
|
+
//
|
|
1402
|
+
// This widening is double-spend-safe: the `_executedFor[terminalToken]` guard above is keyed by leaf `index`,
|
|
1403
|
+
// not by root. The merkle branch binds `(leafHash, index)` to whichever retained root it matches, so the same
|
|
1404
|
+
// leaf carries the same `index` regardless of which retained root proves it — once executed, every later
|
|
1405
|
+
// proof
|
|
1406
|
+
// for that leaf (against any retained root) is rejected by the bitmap before reaching this point.
|
|
1407
|
+
bytes32 expectedRoot =
|
|
1408
|
+
_selectRetainedInboxRoot({terminalToken: terminalToken, leafHash: leafHash, index: index, leaves: leaves});
|
|
1409
|
+
|
|
1410
|
+
// Calculate the root and compare it to the selected retained inbox root.
|
|
1411
|
+
_validateBranchRoot({expectedRoot: expectedRoot, leafHash: leafHash, index: index, leaves: leaves});
|
|
1352
1412
|
}
|
|
1353
1413
|
|
|
1354
1414
|
/// @notice Validates a branch root against the expected root.
|
|
@@ -1628,6 +1688,51 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1628
1688
|
}
|
|
1629
1689
|
}
|
|
1630
1690
|
|
|
1691
|
+
/// @notice Selects which retained inbox root a proof should be validated against, honoring a small window of
|
|
1692
|
+
/// recently-accepted roots rather than only the latest.
|
|
1693
|
+
/// @dev Computes the branch root implied by the proof once, then returns the first retained ring root it matches.
|
|
1694
|
+
/// Falls back to the latest inbox root (`_inboxOf[terminalToken].root`) when the proof matches no retained root, so
|
|
1695
|
+
/// the caller's subsequent `_validateBranchRoot` reverts against the live root exactly as it did before the ring
|
|
1696
|
+
/// existed. This is `view` and side-effect free; the double-spend guard lives entirely in `_validate`'s bitmap.
|
|
1697
|
+
/// @param terminalToken The terminal token whose retained inbox roots are searched.
|
|
1698
|
+
/// @param leafHash The precomputed leaf hash for the leaf being validated.
|
|
1699
|
+
/// @param index The index of the leaf in the inbox tree.
|
|
1700
|
+
/// @param leaves The merkle branch proving the leaf's inclusion.
|
|
1701
|
+
/// @return expectedRoot The retained root the proof matches, or the latest inbox root if none match.
|
|
1702
|
+
function _selectRetainedInboxRoot(
|
|
1703
|
+
address terminalToken,
|
|
1704
|
+
bytes32 leafHash,
|
|
1705
|
+
uint256 index,
|
|
1706
|
+
bytes32[_TREE_DEPTH] calldata leaves
|
|
1707
|
+
)
|
|
1708
|
+
internal
|
|
1709
|
+
view
|
|
1710
|
+
virtual
|
|
1711
|
+
returns (bytes32 expectedRoot)
|
|
1712
|
+
{
|
|
1713
|
+
// The latest accepted root. Used as the fallback so the failure path is unchanged.
|
|
1714
|
+
bytes32 latestRoot = _inboxOf[terminalToken].root;
|
|
1715
|
+
|
|
1716
|
+
// Compute the root implied by this proof once.
|
|
1717
|
+
bytes32 computedRoot = JBSuckerLib.computeBranchRoot({item: leafHash, branch: leaves, index: index});
|
|
1718
|
+
|
|
1719
|
+
// Honor the latest root first (the common case), then any other retained root in the ring.
|
|
1720
|
+
if (computedRoot == latestRoot) return latestRoot;
|
|
1721
|
+
|
|
1722
|
+
bytes32[_INBOX_ROOT_RING_SIZE] storage ring = _inboxRootRingOf[terminalToken];
|
|
1723
|
+
for (uint256 i; i < _INBOX_ROOT_RING_SIZE;) {
|
|
1724
|
+
bytes32 retained = ring[i];
|
|
1725
|
+
// Skip empty slots; a real inbox root is never `bytes32(0)`.
|
|
1726
|
+
if (retained != bytes32(0) && computedRoot == retained) return retained;
|
|
1727
|
+
unchecked {
|
|
1728
|
+
++i;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// No retained root matched. Fall back to the latest root so `_validateBranchRoot` reverts against it.
|
|
1733
|
+
return latestRoot;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1631
1736
|
/// @notice Convert a bytes32 remote address to a local EVM address.
|
|
1632
1737
|
/// @param remote The bytes32 representation of the address.
|
|
1633
1738
|
/// @return The EVM address (lower 20 bytes).
|
|
@@ -61,9 +61,6 @@ library JBSwapPoolLib {
|
|
|
61
61
|
/// @dev The default TWAP observation window in seconds (10 minutes).
|
|
62
62
|
uint32 private constant _DEFAULT_TWAP_WINDOW = 600;
|
|
63
63
|
|
|
64
|
-
/// @dev The minimum acceptable TWAP observation window in seconds (2 minutes).
|
|
65
|
-
uint256 private constant _MIN_TWAP_WINDOW = 120;
|
|
66
|
-
|
|
67
64
|
/// @dev The TWAP observation window used for V4 geomean oracle queries in seconds (2 minutes).
|
|
68
65
|
uint32 private constant _V4_TWAP_WINDOW = 120;
|
|
69
66
|
|
|
@@ -34,6 +34,15 @@ interface IJBSucker is IERC165 {
|
|
|
34
34
|
address caller
|
|
35
35
|
);
|
|
36
36
|
|
|
37
|
+
/// @notice Emitted when a single leaf in a batch `claim(JBClaim[])` fails so the rest of the batch can proceed.
|
|
38
|
+
/// @dev The failing leaf's state changes are fully reverted (the batch routes each leaf through an external
|
|
39
|
+
/// `this.claim` sub-call), so the leaf remains claimable later once the underlying cause is resolved (e.g. its
|
|
40
|
+
/// inbox root arrives, or a transient mint/add-to-balance dependency recovers).
|
|
41
|
+
/// @param token The terminal token address of the failing leaf.
|
|
42
|
+
/// @param index The leaf index in the inbox tree.
|
|
43
|
+
/// @param caller The address that submitted the batch.
|
|
44
|
+
event ClaimFailed(address indexed token, uint256 index, address caller);
|
|
45
|
+
|
|
37
46
|
/// @notice Emitted when a leaf is inserted into the outbox tree.
|
|
38
47
|
/// @param beneficiary The beneficiary on the remote chain.
|
|
39
48
|
/// @param token The terminal token address.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|