@bananapus/suckers-v6 0.0.78 → 0.0.79
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/README.md +6 -5
- package/foundry.toml +2 -0
- package/package.json +1 -1
- package/references/entrypoints.md +25 -9
- package/src/JBArbitrumSucker.sol +18 -6
- package/src/JBCCIPSucker.sol +3 -12
- package/src/JBOptimismSucker.sol +5 -2
- package/src/JBSucker.sol +220 -158
- package/src/JBSuckerRegistry.sol +375 -138
- package/src/interfaces/IJBSucker.sol +44 -24
- package/src/interfaces/IJBSuckerRegistry.sol +23 -8
- package/src/libraries/JBSuckerLib.sol +215 -26
- package/src/structs/JBAccountingSnapshot.sol +9 -11
- package/src/structs/JBChainAccounting.sol +32 -0
- package/src/structs/JBMessageRoot.sol +12 -16
- package/src/structs/PeerAccountScratch.sol +18 -0
- package/src/structs/RemoteValueParams.sol +15 -0
package/src/JBSucker.sol
CHANGED
|
@@ -10,7 +10,6 @@ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.s
|
|
|
10
10
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
11
11
|
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
12
12
|
import {IJBTokens} from "@bananapus/core-v6/src/interfaces/IJBTokens.sol";
|
|
13
|
-
import {JBAccountingContext} from "@bananapus/core-v6/src/structs/JBAccountingContext.sol";
|
|
14
13
|
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
15
14
|
import {JBFixedPointNumber} from "@bananapus/core-v6/src/libraries/JBFixedPointNumber.sol";
|
|
16
15
|
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
@@ -37,6 +36,7 @@ import {MerkleLib} from "./utils/MerkleLib.sol";
|
|
|
37
36
|
|
|
38
37
|
// Local: structs (alphabetized)
|
|
39
38
|
import {JBAccountingSnapshot} from "./structs/JBAccountingSnapshot.sol";
|
|
39
|
+
import {JBChainAccounting} from "./structs/JBChainAccounting.sol";
|
|
40
40
|
import {JBClaim} from "./structs/JBClaim.sol";
|
|
41
41
|
import {JBInboxTreeRoot} from "./structs/JBInboxTreeRoot.sol";
|
|
42
42
|
import {JBMessageRoot} from "./structs/JBMessageRoot.sol";
|
|
@@ -185,6 +185,12 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
185
185
|
/// double-spend guard.
|
|
186
186
|
uint256 internal constant _INBOX_ROOT_RING_SIZE = 4;
|
|
187
187
|
|
|
188
|
+
/// @notice Extra destination gas budgeted per source accounting context carried in a gossip bundle.
|
|
189
|
+
/// @dev Covers the receiver's per-context storage writes in `_storeChainAccounting`, so the messaging gas limit
|
|
190
|
+
/// scales with the bundle (via `_messagingGasLimit`) instead of a fixed cap that would bound how large the
|
|
191
|
+
/// cross-chain mesh can grow before the destination call runs out of gas.
|
|
192
|
+
uint256 internal constant _MESSENGER_SOURCE_CONTEXT_GAS_LIMIT = 75_000;
|
|
193
|
+
|
|
188
194
|
/// @notice The depth of the merkle tree used to store the outbox and inbox.
|
|
189
195
|
uint32 internal constant _TREE_DEPTH = 32;
|
|
190
196
|
|
|
@@ -223,10 +229,13 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
223
229
|
/// @custom:param index The leaf's index in the inbox tree.
|
|
224
230
|
mapping(address token => mapping(uint256 index => bytes32)) public override executedLeafHashOf;
|
|
225
231
|
|
|
226
|
-
/// @notice The last known total token supply on
|
|
227
|
-
///
|
|
228
|
-
///
|
|
229
|
-
|
|
232
|
+
/// @notice The last known total token supply on each peer chain, updated each time a bridge message carries that
|
|
233
|
+
/// chain's accounting record.
|
|
234
|
+
/// @dev The registry sums the freshest value across every peer chain to drive cross-chain cash out tax, preventing
|
|
235
|
+
/// a holder who dominates one chain's local supply from bypassing the tax. Returns 0 for a chain no record has been
|
|
236
|
+
/// received for.
|
|
237
|
+
/// @custom:param chainId The peer chain to read the last known total supply of.
|
|
238
|
+
mapping(uint256 chainId => uint256) public peerChainTotalSupplyOf;
|
|
230
239
|
|
|
231
240
|
/// @notice The total retained failed-fee ETH excluded from native add-to-balance accounting.
|
|
232
241
|
uint256 public retainedToRemoteFeeBalance;
|
|
@@ -242,11 +251,12 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
242
251
|
/// @custom:param account The address owed the retained ETH.
|
|
243
252
|
mapping(address account => uint256 amount) public retainedTransportPaymentRefundOf;
|
|
244
253
|
|
|
245
|
-
/// @notice The source
|
|
246
|
-
/// @dev
|
|
247
|
-
///
|
|
248
|
-
/// Returns 0
|
|
249
|
-
|
|
254
|
+
/// @notice The source-chain freshness key of the most recent accepted accounting record for each peer chain.
|
|
255
|
+
/// @dev A record for a peer chain is accepted only when its freshness key is strictly newer than the stored one,
|
|
256
|
+
/// so stale relays cannot roll back that chain's surplus, balance, or supply. Each peer chain is gated
|
|
257
|
+
/// independently. Returns 0 for a chain no record has been received for.
|
|
258
|
+
/// @custom:param chainId The peer chain to read the latest accepted freshness key of.
|
|
259
|
+
mapping(uint256 chainId => uint256) public snapshotTimestampOf;
|
|
250
260
|
|
|
251
261
|
//*********************************************************************//
|
|
252
262
|
// -------------------- internal stored properties ------------------- //
|
|
@@ -301,12 +311,10 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
301
311
|
// -------------------- private stored properties -------------------- //
|
|
302
312
|
//*********************************************************************//
|
|
303
313
|
|
|
304
|
-
/// @notice
|
|
305
|
-
///
|
|
306
|
-
///
|
|
307
|
-
|
|
308
|
-
/// @custom:param token The local token.
|
|
309
|
-
mapping(address token => uint32 currency) private _cachedCurrencyOf;
|
|
314
|
+
/// @notice Whether a peer chain already has an entry in `_peerChainIds`, so the enumerable set inserts each chain
|
|
315
|
+
/// at most once.
|
|
316
|
+
/// @custom:param chainId The peer chain to check membership of.
|
|
317
|
+
mapping(uint256 chainId => bool) private _isKnownPeerChainId;
|
|
310
318
|
|
|
311
319
|
/// @notice The ID of the project (on the local chain) that this sucker is associated with.
|
|
312
320
|
uint256 private _localProjectId;
|
|
@@ -318,10 +326,19 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
318
326
|
/// @dev A zero value preserves the default same-address deterministic peer.
|
|
319
327
|
bytes32 private _peer;
|
|
320
328
|
|
|
321
|
-
/// @notice The peer
|
|
322
|
-
/// @dev
|
|
323
|
-
///
|
|
324
|
-
|
|
329
|
+
/// @notice The set of peer chains this sucker holds an accounting record for.
|
|
330
|
+
/// @dev The registry enumerates this to aggregate every chain's value and to gather records for re-gossiping. A
|
|
331
|
+
/// chain is appended on its first accepted record and never removed, so the set stays small and bounded by the
|
|
332
|
+
/// project's chain count.
|
|
333
|
+
uint256[] private _peerChainIds;
|
|
334
|
+
|
|
335
|
+
/// @notice Each peer chain's raw, un-valued per-context surplus and balance from its latest accepted record.
|
|
336
|
+
/// @dev Stored exactly as received — in the source chain's own token addresses and decimals — so the record can
|
|
337
|
+
/// be
|
|
338
|
+
/// re-gossiped to other chains faithfully and resolved to local currencies at read time. A fresher record for a
|
|
339
|
+
/// chain replaces that chain's set from scratch; a context dropped by the fresher record simply vanishes.
|
|
340
|
+
/// @custom:param chainId The peer chain to read the raw contexts of.
|
|
341
|
+
mapping(uint256 chainId => JBSourceContext[]) private _peerContextsOf;
|
|
325
342
|
|
|
326
343
|
//*********************************************************************//
|
|
327
344
|
// ---------------------------- constructor -------------------------- //
|
|
@@ -579,11 +596,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
579
596
|
});
|
|
580
597
|
}
|
|
581
598
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
sourceContexts: root.sourceContexts
|
|
586
|
-
});
|
|
599
|
+
// Store every accounting record in the gossip bundle that rode along with this root, keeping the freshest per
|
|
600
|
+
// source chain. The token-local inbox update above and this per-chain accounting are gated independently.
|
|
601
|
+
_storeAccountingBundle(root.accounts);
|
|
587
602
|
}
|
|
588
603
|
|
|
589
604
|
/// @notice Receive a peer-chain accounting snapshot from the remote sucker without updating any token-local inbox
|
|
@@ -606,11 +621,8 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
606
621
|
revert JBSucker_InvalidMessageVersion({received: snapshot.version, expected: MESSAGE_VERSION});
|
|
607
622
|
}
|
|
608
623
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
sourceTotalSupply: snapshot.sourceTotalSupply,
|
|
612
|
-
sourceContexts: snapshot.sourceContexts
|
|
613
|
-
});
|
|
624
|
+
// Store every accounting record in the gossip bundle, keeping the freshest per source chain.
|
|
625
|
+
_storeAccountingBundle(snapshot.accounts);
|
|
614
626
|
}
|
|
615
627
|
|
|
616
628
|
/// @notice Configure which remote-chain tokens each local terminal token maps to, enabling (or disabling) those
|
|
@@ -767,7 +779,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
767
779
|
uint256 sourceTimestamp = _nextSourceTimestamp();
|
|
768
780
|
JBAccountingSnapshot memory snapshot = JBSuckerLib.buildAccountingSnapshot({
|
|
769
781
|
directory: DIRECTORY,
|
|
782
|
+
registry: REGISTRY,
|
|
770
783
|
projectId: projectId(),
|
|
784
|
+
exceptChainId: peerChainId(),
|
|
771
785
|
messageVersion: MESSAGE_VERSION,
|
|
772
786
|
sourceTimestamp: sourceTimestamp
|
|
773
787
|
});
|
|
@@ -868,28 +882,112 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
868
882
|
return _outboxOf[token];
|
|
869
883
|
}
|
|
870
884
|
|
|
871
|
-
/// @notice The
|
|
872
|
-
/// chain
|
|
873
|
-
///
|
|
874
|
-
///
|
|
875
|
-
/// @return
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
885
|
+
/// @notice The raw, un-valued accounting record this sucker holds for every peer chain it has heard about.
|
|
886
|
+
/// @dev The registry reads this to gather a project's full cross-chain knowledge and re-gossip it. Records are
|
|
887
|
+
/// returned exactly as received (in each source chain's own token addresses and decimals) so the next receiver
|
|
888
|
+
/// resolves them to its own local currencies independently.
|
|
889
|
+
/// @return accounts One raw accounting record per known peer chain.
|
|
890
|
+
function peerChainAccountsOf() external view returns (JBChainAccounting[] memory accounts) {
|
|
891
|
+
uint256[] storage chainIds = _peerChainIds;
|
|
892
|
+
uint256 numChains = chainIds.length;
|
|
893
|
+
accounts = new JBChainAccounting[](numChains);
|
|
894
|
+
for (uint256 i; i < numChains;) {
|
|
895
|
+
uint256 chainId = chainIds[i];
|
|
896
|
+
accounts[i] = JBChainAccounting({
|
|
897
|
+
chainId: chainId,
|
|
898
|
+
totalSupply: peerChainTotalSupplyOf[chainId],
|
|
899
|
+
contexts: _peerContextsOf[chainId],
|
|
900
|
+
timestamp: snapshotTimestampOf[chainId]
|
|
901
|
+
});
|
|
902
|
+
unchecked {
|
|
903
|
+
++i;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/// @notice One peer chain's per-currency surplus and balance from its latest accepted record, plus that record's
|
|
909
|
+
/// freshness key.
|
|
910
|
+
/// @dev Resolves each raw stored context to its local currency and folds same-currency, same-decimals entries
|
|
911
|
+
/// together. The result is un-valued — the registry values each context into a requested currency; the sucker
|
|
912
|
+
/// consults no price oracle. Contexts that share a currency but carry different decimals are kept separate because
|
|
913
|
+
/// the raw amounts are on different scales and cannot be summed directly.
|
|
914
|
+
/// @param chainId The peer chain to read the contexts of.
|
|
915
|
+
/// @return contexts The per-currency surplus and balance for the chain.
|
|
916
|
+
/// @return snapshot The source freshness key of the chain's latest accepted record.
|
|
917
|
+
function peerChainContextsOf(uint256 chainId)
|
|
879
918
|
external
|
|
880
919
|
view
|
|
881
|
-
returns (JBPeerChainContext[] memory contexts, uint256
|
|
920
|
+
returns (JBPeerChainContext[] memory contexts, uint256 snapshot)
|
|
882
921
|
{
|
|
883
|
-
|
|
922
|
+
JBSourceContext[] storage rawContexts = _peerContextsOf[chainId];
|
|
923
|
+
uint256 numRaw = rawContexts.length;
|
|
924
|
+
|
|
925
|
+
// Copy the raw contexts to memory and resolve each source-local token to a local token (mapping first, identity
|
|
926
|
+
// fallback). The bytecode-heavy currency resolution and fold then run in the library.
|
|
927
|
+
JBSourceContext[] memory raw = new JBSourceContext[](numRaw);
|
|
928
|
+
address[] memory localTokens = new address[](numRaw);
|
|
929
|
+
for (uint256 i; i < numRaw;) {
|
|
930
|
+
JBSourceContext storage ctx = rawContexts[i];
|
|
931
|
+
raw[i] = ctx;
|
|
932
|
+
address contextToken = _localTokenForRemoteToken[ctx.token];
|
|
933
|
+
localTokens[i] = contextToken == address(0) ? _toAddress(ctx.token) : contextToken;
|
|
934
|
+
unchecked {
|
|
935
|
+
++i;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
contexts = JBSuckerLib.foldPeerContexts({
|
|
940
|
+
directory: DIRECTORY, projectId: projectId(), localTokens: localTokens, rawContexts: raw
|
|
941
|
+
});
|
|
942
|
+
snapshot = snapshotTimestampOf[chainId];
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/// @notice The peer chains this sucker reports accounting for.
|
|
946
|
+
/// @dev With `includeVirtual` false, returns only the directly-connected peer chain (the one this sucker is bridged
|
|
947
|
+
/// to). With `includeVirtual` true, also returns every other chain it has learned about through gossip relayed by
|
|
948
|
+
/// that peer. The directly-connected peer is always present (with a zero value until its first record) so the
|
|
949
|
+
/// registry can enumerate the chain the moment this sucker is deployed. Until the sucker receives its first record,
|
|
950
|
+
/// that entry is an empty sentinel (value 0, freshness 0) which the registry skips, so a freshly-deployed active
|
|
951
|
+
/// sucker takes over a chain's accounting only once it has synced real data — a deprecated sucker's record keeps
|
|
952
|
+
/// answering for the chain during the migration window.
|
|
953
|
+
/// @param includeVirtual Whether to also include virtually-known (gossiped) peer chains.
|
|
954
|
+
/// @return chainIds The peer chain IDs.
|
|
955
|
+
function peerChainIds(bool includeVirtual) external view returns (uint256[] memory chainIds) {
|
|
956
|
+
uint256 directPeer = peerChainId();
|
|
957
|
+
// The direct peer is a real remote chain — never the local chain or chain 0.
|
|
958
|
+
bool directValid = directPeer != 0 && directPeer != block.chainid;
|
|
959
|
+
|
|
960
|
+
// Directly-connected: just the bridged peer chain.
|
|
961
|
+
if (!includeVirtual) {
|
|
962
|
+
if (!directValid) return new uint256[](0);
|
|
963
|
+
chainIds = new uint256[](1);
|
|
964
|
+
chainIds[0] = directPeer;
|
|
965
|
+
return chainIds;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Virtual-inclusive: every chain heard about, plus the direct peer if no record has placed it there yet.
|
|
969
|
+
bool appendDirect = directValid && !_isKnownPeerChainId[directPeer];
|
|
970
|
+
uint256 len = _peerChainIds.length;
|
|
971
|
+
chainIds = new uint256[](appendDirect ? len + 1 : len);
|
|
972
|
+
for (uint256 i; i < len;) {
|
|
973
|
+
chainIds[i] = _peerChainIds[i];
|
|
974
|
+
unchecked {
|
|
975
|
+
++i;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (appendDirect) chainIds[len] = directPeer;
|
|
884
979
|
}
|
|
885
980
|
|
|
886
|
-
/// @notice
|
|
887
|
-
/// @dev Lets
|
|
888
|
-
///
|
|
981
|
+
/// @notice One peer chain's total supply bundled with the peer chain ID and the record's freshness key.
|
|
982
|
+
/// @dev Lets the registry read the value, the peer chain it belongs to, and its freshness in one call. The `value`
|
|
983
|
+
/// matches `peerChainTotalSupplyOf(chainId)`.
|
|
984
|
+
/// @param chainId The peer chain to read the total supply value of.
|
|
889
985
|
/// @return A `JBPeerChainValue` with the total supply, peer chain ID, and snapshot freshness key.
|
|
890
|
-
function peerChainTotalSupplyValue() external view returns (JBPeerChainValue memory) {
|
|
986
|
+
function peerChainTotalSupplyValue(uint256 chainId) external view returns (JBPeerChainValue memory) {
|
|
891
987
|
return JBPeerChainValue({
|
|
892
|
-
value:
|
|
988
|
+
value: peerChainTotalSupplyOf[chainId],
|
|
989
|
+
peerChainId: chainId,
|
|
990
|
+
snapshotTimestamp: snapshotTimestampOf[chainId]
|
|
893
991
|
});
|
|
894
992
|
}
|
|
895
993
|
|
|
@@ -1308,13 +1406,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1308
1406
|
});
|
|
1309
1407
|
}
|
|
1310
1408
|
|
|
1311
|
-
/// @notice What is the maximum time it takes for a message to be received on the other side.
|
|
1312
|
-
/// @dev Be sure to keep in mind if a message fails having to retry and the time it takes to retry.
|
|
1313
|
-
/// @return The maximum time it takes for a message to be received on the other side.
|
|
1314
|
-
function _maxMessagingDelay() internal pure virtual returns (uint40) {
|
|
1315
|
-
return 14 days;
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
1409
|
/// @notice Cash out project tokens for terminal tokens.
|
|
1319
1410
|
/// @param projectToken The project token to cash out (unused, kept for interface compatibility).
|
|
1320
1411
|
/// @param count The number of project tokens to cash out.
|
|
@@ -1486,75 +1577,72 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1486
1577
|
internal
|
|
1487
1578
|
virtual;
|
|
1488
1579
|
|
|
1489
|
-
/// @notice Store
|
|
1490
|
-
/// @dev
|
|
1491
|
-
///
|
|
1492
|
-
/// @param
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1580
|
+
/// @notice Store every record in a cross-chain accounting bundle, keeping the freshest record per source chain.
|
|
1581
|
+
/// @dev Shared by both inbound paths — `fromRemote` (root) and `fromRemoteAccounting` — so a root send and an
|
|
1582
|
+
/// accounting-only send propagate the same gossip bundle.
|
|
1583
|
+
/// @param accounts The per-source-chain accounting records carried by an inbound message.
|
|
1584
|
+
function _storeAccountingBundle(JBChainAccounting[] calldata accounts) internal {
|
|
1585
|
+
uint256 numAccounts = accounts.length;
|
|
1586
|
+
for (uint256 i; i < numAccounts;) {
|
|
1587
|
+
JBChainAccounting calldata account = accounts[i];
|
|
1588
|
+
_storeChainAccounting({
|
|
1589
|
+
chainId: account.chainId,
|
|
1590
|
+
sourceTimestamp: account.timestamp,
|
|
1591
|
+
sourceTotalSupply: account.totalSupply,
|
|
1592
|
+
sourceContexts: account.contexts
|
|
1593
|
+
});
|
|
1594
|
+
unchecked {
|
|
1595
|
+
++i;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/// @notice Store one source chain's accounting record if it is fresher than the one already held for that chain.
|
|
1601
|
+
/// @dev A record for the local chain is ignored — a chain reads its own accounting directly. Each peer chain is
|
|
1602
|
+
/// gated independently on a strictly-newer freshness key, so a stale relay cannot roll back any chain and records
|
|
1603
|
+
/// delivered out of order converge. Contexts are stored raw (in the source chain's own token addresses) so the
|
|
1604
|
+
/// record can be re-gossiped faithfully; each receiver resolves them to its own local currencies at read time.
|
|
1605
|
+
/// @param chainId The source chain the record describes.
|
|
1606
|
+
/// @param sourceTimestamp The record's source-chain freshness key.
|
|
1607
|
+
/// @param sourceTotalSupply The source chain's total project-token supply.
|
|
1608
|
+
/// @param sourceContexts The source chain's raw per-context surplus and balance.
|
|
1609
|
+
function _storeChainAccounting(
|
|
1610
|
+
uint256 chainId,
|
|
1496
1611
|
uint256 sourceTimestamp,
|
|
1497
1612
|
uint256 sourceTotalSupply,
|
|
1498
1613
|
JBSourceContext[] calldata sourceContexts
|
|
1499
1614
|
)
|
|
1500
1615
|
internal
|
|
1501
1616
|
{
|
|
1502
|
-
//
|
|
1503
|
-
|
|
1617
|
+
// A chain reads its own accounting locally, so never store a record describing the local chain — even one a
|
|
1618
|
+
// peer forwarded back to us. Chain 0 is not a real chain ID, so reject it as malformed.
|
|
1619
|
+
if (chainId == block.chainid || chainId == 0) return;
|
|
1504
1620
|
|
|
1505
|
-
//
|
|
1506
|
-
|
|
1621
|
+
// Accept a record only when its source freshness key is strictly newer than the last accepted one for this
|
|
1622
|
+
// chain. Ignoring (not reverting) means bridge delivery order cannot roll a chain back.
|
|
1623
|
+
if (sourceTimestamp <= snapshotTimestampOf[chainId]) return;
|
|
1624
|
+
|
|
1625
|
+
// Advance this chain's freshness key, which the registry uses to dedup redundant same-chain records.
|
|
1626
|
+
snapshotTimestampOf[chainId] = sourceTimestamp;
|
|
1507
1627
|
|
|
1508
1628
|
// Update unconditionally — a legitimate zero supply must clear phantom cached supply.
|
|
1509
|
-
|
|
1629
|
+
peerChainTotalSupplyOf[chainId] = sourceTotalSupply;
|
|
1630
|
+
|
|
1631
|
+
// Append the chain to the enumerable set on its first accepted record so the registry can enumerate it.
|
|
1632
|
+
if (!_isKnownPeerChainId[chainId]) {
|
|
1633
|
+
_isKnownPeerChainId[chainId] = true;
|
|
1634
|
+
_peerChainIds.push(chainId);
|
|
1635
|
+
}
|
|
1510
1636
|
|
|
1511
|
-
// Rebuild
|
|
1512
|
-
//
|
|
1513
|
-
|
|
1637
|
+
// Rebuild this chain's raw context set from scratch. A context dropped by the fresher record is simply absent.
|
|
1638
|
+
// Each context is stored verbatim so it can be re-gossiped faithfully; folding to a local currency happens at
|
|
1639
|
+
// read time in `peerChainContextsOf`.
|
|
1640
|
+
delete _peerContextsOf[chainId];
|
|
1641
|
+
JBSourceContext[] storage storedContexts = _peerContextsOf[chainId];
|
|
1514
1642
|
|
|
1515
|
-
// Fold each source context into the local currency it resolves to. Resolution prefers the token mapping (so a
|
|
1516
|
-
// same-asset token at a different remote address binds to the right local context) and falls back to identity
|
|
1517
|
-
// for same-address tokens; the local currency is then derived from that resolved local token's authoritative
|
|
1518
|
-
// accounting context, NOT trusted from the wire.
|
|
1519
1643
|
uint256 numContexts = sourceContexts.length;
|
|
1520
1644
|
for (uint256 i; i < numContexts;) {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
address contextToken = _localTokenForRemoteToken[ctx.token];
|
|
1524
|
-
if (contextToken == address(0)) contextToken = _toAddress(ctx.token);
|
|
1525
|
-
(uint32 contextCurrency, bool authoritative) = _localCurrencyOf(contextToken);
|
|
1526
|
-
|
|
1527
|
-
// Cache an authoritative currency so later snapshots reuse it instead of re-reading the terminal. The
|
|
1528
|
-
// accounting-context currency is immutable, so the cache never goes stale; a not-yet-configured token is
|
|
1529
|
-
// left uncached and re-read next time.
|
|
1530
|
-
if (authoritative && _cachedCurrencyOf[contextToken] == 0) {
|
|
1531
|
-
_cachedCurrencyOf[contextToken] = contextCurrency;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// Accumulate into an existing entry that matches on BOTH currency AND decimals, or append a new one. The
|
|
1535
|
-
// decimals must match: `surplus`/`balance` are raw, un-valued token amounts, so two contexts that share a
|
|
1536
|
-
// currency but carry different decimals are on different scales and cannot be summed directly.
|
|
1537
|
-
uint256 numStored = _peerContexts.length;
|
|
1538
|
-
bool merged;
|
|
1539
|
-
for (uint256 j; j < numStored;) {
|
|
1540
|
-
if (_peerContexts[j].currency == contextCurrency && _peerContexts[j].decimals == ctx.decimals) {
|
|
1541
|
-
_peerContexts[j].surplus = _saturatingAddU128(_peerContexts[j].surplus, ctx.surplus);
|
|
1542
|
-
_peerContexts[j].balance = _saturatingAddU128(_peerContexts[j].balance, ctx.balance);
|
|
1543
|
-
merged = true;
|
|
1544
|
-
break;
|
|
1545
|
-
}
|
|
1546
|
-
unchecked {
|
|
1547
|
-
++j;
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
if (!merged) {
|
|
1551
|
-
_peerContexts.push(
|
|
1552
|
-
JBPeerChainContext({
|
|
1553
|
-
currency: contextCurrency, decimals: ctx.decimals, surplus: ctx.surplus, balance: ctx.balance
|
|
1554
|
-
})
|
|
1555
|
-
);
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1645
|
+
storedContexts.push(sourceContexts[i]);
|
|
1558
1646
|
unchecked {
|
|
1559
1647
|
++i;
|
|
1560
1648
|
}
|
|
@@ -1811,43 +1899,30 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1811
1899
|
return ERC2771Context._contextSuffixLength();
|
|
1812
1900
|
}
|
|
1813
1901
|
|
|
1814
|
-
/// @notice
|
|
1815
|
-
///
|
|
1816
|
-
///
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
///
|
|
1822
|
-
///
|
|
1823
|
-
///
|
|
1824
|
-
///
|
|
1825
|
-
///
|
|
1826
|
-
///
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
(bool terminalOk, bytes memory terminalData) =
|
|
1836
|
-
address(DIRECTORY).staticcall(abi.encodeCall(IJBDirectory.primaryTerminalOf, (forProjectId, token)));
|
|
1837
|
-
if (terminalOk && terminalData.length >= 32) {
|
|
1838
|
-
address terminal = abi.decode(terminalData, (address));
|
|
1839
|
-
if (terminal != address(0)) {
|
|
1840
|
-
// Read the token's accounting context. The struct encodes to three words.
|
|
1841
|
-
(bool contextOk, bytes memory contextData) =
|
|
1842
|
-
terminal.staticcall(abi.encodeCall(IJBTerminal.accountingContextForTokenOf, (forProjectId, token)));
|
|
1843
|
-
if (contextOk && contextData.length >= 96) {
|
|
1844
|
-
JBAccountingContext memory accountingContext = abi.decode(contextData, (JBAccountingContext));
|
|
1845
|
-
if (accountingContext.currency != 0) return (accountingContext.currency, true);
|
|
1846
|
-
}
|
|
1902
|
+
/// @notice What is the maximum time it takes for a message to be received on the other side.
|
|
1903
|
+
/// @dev Be sure to keep in mind if a message fails having to retry and the time it takes to retry.
|
|
1904
|
+
/// @return The maximum time it takes for a message to be received on the other side.
|
|
1905
|
+
function _maxMessagingDelay() internal pure virtual returns (uint40) {
|
|
1906
|
+
return 14 days;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
/// @notice The destination gas limit a gossip-carrying message needs to store its bundle on the remote chain.
|
|
1910
|
+
/// @dev A fixed base (`MESSENGER_BASE_GAS_LIMIT`) plus `_MESSENGER_SOURCE_CONTEXT_GAS_LIMIT` per source context, so
|
|
1911
|
+
/// the budget grows with the bundle. Without this, a fixed cap would bound the mesh: once the bundle's contexts
|
|
1912
|
+
/// exceed the cap, the destination `fromRemote`/`fromRemoteAccounting` runs out of gas. Every bridge variant sizes
|
|
1913
|
+
/// its outbound gas limit from this so the OP-stack, Arbitrum, and CCIP suckers scale identically.
|
|
1914
|
+
/// @param accounts The accounting records carried by the message.
|
|
1915
|
+
/// @return gasLimit The destination gas limit to request from the bridge.
|
|
1916
|
+
function _messagingGasLimit(JBChainAccounting[] memory accounts) internal pure returns (uint256 gasLimit) {
|
|
1917
|
+
uint256 contextCount;
|
|
1918
|
+
uint256 numAccounts = accounts.length;
|
|
1919
|
+
for (uint256 i; i < numAccounts;) {
|
|
1920
|
+
contextCount += accounts[i].contexts.length;
|
|
1921
|
+
unchecked {
|
|
1922
|
+
++i;
|
|
1847
1923
|
}
|
|
1848
1924
|
}
|
|
1849
|
-
|
|
1850
|
-
return (uint32(uint160(token)), false);
|
|
1925
|
+
return MESSENGER_BASE_GAS_LIMIT + (contextCount * _MESSENGER_SOURCE_CONTEXT_GAS_LIMIT);
|
|
1851
1926
|
}
|
|
1852
1927
|
|
|
1853
1928
|
/// @notice The calldata. Preferred to use over `msg.data`.
|
|
@@ -1899,21 +1974,6 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
1899
1974
|
}
|
|
1900
1975
|
}
|
|
1901
1976
|
|
|
1902
|
-
/// @notice Adds two `uint128` amounts, saturating at `type(uint128).max` instead of overflowing.
|
|
1903
|
-
/// @dev Saturation keeps a pathological peer snapshot from reverting the receive path; the cap can only
|
|
1904
|
-
/// under-report a remote amount, the safe direction.
|
|
1905
|
-
/// @param a The first amount.
|
|
1906
|
-
/// @param b The second amount.
|
|
1907
|
-
/// @return The saturated sum.
|
|
1908
|
-
function _saturatingAddU128(uint128 a, uint128 b) internal pure returns (uint128) {
|
|
1909
|
-
unchecked {
|
|
1910
|
-
uint256 sum = uint256(a) + uint256(b);
|
|
1911
|
-
// The cast only runs when `sum <= type(uint128).max`, so it cannot truncate.
|
|
1912
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1913
|
-
return sum > type(uint128).max ? type(uint128).max : uint128(sum);
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
1977
|
/// @notice Selects which retained inbox root a proof should be validated against, honoring a small window of
|
|
1918
1978
|
/// recently-accepted roots rather than only the latest.
|
|
1919
1979
|
/// @dev Computes the branch root implied by the proof once, then returns the first retained ring root it matches.
|
|
@@ -2020,7 +2080,9 @@ abstract contract JBSucker is ERC2771Context, JBPermissioned, Initializable, ERC
|
|
|
2020
2080
|
|
|
2021
2081
|
JBMessageRoot memory message = JBSuckerLib.buildSnapshotMessage({
|
|
2022
2082
|
directory: DIRECTORY,
|
|
2083
|
+
registry: REGISTRY,
|
|
2023
2084
|
projectId: projectId(),
|
|
2085
|
+
exceptChainId: peerChainId(),
|
|
2024
2086
|
remoteToken: remoteToken.addr,
|
|
2025
2087
|
amount: amount,
|
|
2026
2088
|
nonce: nonce,
|