@bananapus/suckers-v6 0.0.78 → 1.0.0
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 +3 -3
- 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/JBSuckerRegistry.sol
CHANGED
|
@@ -14,6 +14,7 @@ import {Context} from "@openzeppelin/contracts/utils/Context.sol";
|
|
|
14
14
|
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
|
|
15
15
|
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
16
16
|
|
|
17
|
+
import {JBChainAccounting} from "./structs/JBChainAccounting.sol";
|
|
17
18
|
import {JBPeerChainContext} from "./structs/JBPeerChainContext.sol";
|
|
18
19
|
import {JBPeerChainValue} from "./structs/JBPeerChainValue.sol";
|
|
19
20
|
import {JBSuckerState} from "./enums/JBSuckerState.sol";
|
|
@@ -22,7 +23,9 @@ import {IJBSuckerDeployer} from "./interfaces/IJBSuckerDeployer.sol";
|
|
|
22
23
|
import {IJBSuckerRegistry} from "./interfaces/IJBSuckerRegistry.sol";
|
|
23
24
|
import {JBSuckerDeployerConfig} from "./structs/JBSuckerDeployerConfig.sol";
|
|
24
25
|
import {JBSuckersPair} from "./structs/JBSuckersPair.sol";
|
|
26
|
+
import {PeerAccountScratch} from "./structs/PeerAccountScratch.sol";
|
|
25
27
|
import {PeerValueScratch} from "./structs/PeerValueScratch.sol";
|
|
28
|
+
import {RemoteValueParams} from "./structs/RemoteValueParams.sol";
|
|
26
29
|
|
|
27
30
|
/// @notice The canonical registry that deploys, tracks, and governs cross-chain suckers for Juicebox projects. It
|
|
28
31
|
/// maintains an allowlist of approved deployer contracts, allows multiple active suckers per peer chain for bridge
|
|
@@ -156,18 +159,78 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
156
159
|
return exists && (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED);
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
/// @notice
|
|
160
|
-
///
|
|
161
|
-
///
|
|
162
|
-
///
|
|
162
|
+
/// @notice The freshest accounting record per source chain that a project's suckers hold, for re-gossiping to a
|
|
163
|
+
/// peer.
|
|
164
|
+
/// @dev A sucker building an outbound gossip bundle calls this to gather the project's full cross-chain knowledge
|
|
165
|
+
/// (the registry is the only place a hub chain's per-peer suckers are visible together), then prepends its own
|
|
166
|
+
/// local record. Records are deduped per chain (freshest wins; an active sucker's record supersedes a deprecated
|
|
167
|
+
/// one's), and the destination chain and the local chain are excluded. Suckers and records that revert are
|
|
168
|
+
/// silently skipped.
|
|
169
|
+
/// @param projectId The ID of the project.
|
|
170
|
+
/// @param exceptChainId The destination chain to exclude (it has authoritative data about itself).
|
|
171
|
+
/// @return accounts The deduped raw accounting records, one per known source chain.
|
|
172
|
+
function peerChainAccountsOf(
|
|
173
|
+
uint256 projectId,
|
|
174
|
+
uint256 exceptChainId
|
|
175
|
+
)
|
|
176
|
+
external
|
|
177
|
+
view
|
|
178
|
+
override
|
|
179
|
+
returns (JBChainAccounting[] memory accounts)
|
|
180
|
+
{
|
|
181
|
+
address[] memory allSuckers = _suckersOf[projectId].keys();
|
|
182
|
+
|
|
183
|
+
// Bound the distinct-chain scratch by the total records across the project's suckers.
|
|
184
|
+
(, uint256 totalChains) = _peerChainIdsBySucker(allSuckers);
|
|
185
|
+
PeerAccountScratch memory scratch = PeerAccountScratch({
|
|
186
|
+
chainIds: new uint256[](totalChains),
|
|
187
|
+
records: new JBChainAccounting[](totalChains),
|
|
188
|
+
hasActiveRecord: new bool[](totalChains),
|
|
189
|
+
chainCount: 0
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
uint256 len = allSuckers.length;
|
|
193
|
+
for (uint256 i; i < len;) {
|
|
194
|
+
(, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
|
|
195
|
+
// Include both active and deprecated suckers; deprecated only fill a gap no active sucker answers.
|
|
196
|
+
if (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED) {
|
|
197
|
+
_gatherSuckerAccounts({
|
|
198
|
+
scratch: scratch,
|
|
199
|
+
sucker: allSuckers[i],
|
|
200
|
+
isActive: val == _SUCKER_EXISTS,
|
|
201
|
+
exceptChainId: exceptChainId
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
unchecked {
|
|
205
|
+
++i;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Trim to the populated chains.
|
|
210
|
+
accounts = new JBChainAccounting[](scratch.chainCount);
|
|
211
|
+
for (uint256 k; k < scratch.chainCount;) {
|
|
212
|
+
accounts[k] = scratch.records[k];
|
|
213
|
+
unchecked {
|
|
214
|
+
++k;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// @notice Values one peer chain's raw balance held by one sucker into a currency, with peer chain ID and
|
|
220
|
+
/// freshness.
|
|
221
|
+
/// @dev Exposed as an external self-call boundary so `totalRemoteBalanceOf` can `try` it and drop a single
|
|
222
|
+
/// (sucker, chain) whose price feed is missing without losing that sucker's other chains. A context whose currency
|
|
223
|
+
/// already matches `currency` folds in at par (no feed read); a missing cross-currency feed reverts, and the
|
|
224
|
+
/// aggregator catches it and skips just this (sucker, chain).
|
|
163
225
|
/// @param sucker The sucker to read.
|
|
226
|
+
/// @param chainId The peer chain to read.
|
|
164
227
|
/// @param projectId The project whose price feeds to use.
|
|
165
228
|
/// @param currency The currency to value into.
|
|
166
229
|
/// @param decimals The decimal precision for the returned value.
|
|
167
|
-
/// @return A `JBPeerChainValue` with the valued balance, the
|
|
168
|
-
/// key.
|
|
230
|
+
/// @return A `JBPeerChainValue` with the valued balance, the peer chain ID, and its snapshot freshness key.
|
|
169
231
|
function remoteBalanceOf(
|
|
170
232
|
address sucker,
|
|
233
|
+
uint256 chainId,
|
|
171
234
|
uint256 projectId,
|
|
172
235
|
uint256 currency,
|
|
173
236
|
uint256 decimals
|
|
@@ -176,10 +239,8 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
176
239
|
view
|
|
177
240
|
returns (JBPeerChainValue memory)
|
|
178
241
|
{
|
|
179
|
-
// Read this sucker's raw
|
|
180
|
-
|
|
181
|
-
(JBPeerChainContext[] memory contexts, uint256 chainId, uint256 snapshot) =
|
|
182
|
-
IJBSucker(sucker).peerChainContextsOf();
|
|
242
|
+
// Read this sucker's raw contexts for the chain: one per distinct local currency, plus the freshness key.
|
|
243
|
+
(JBPeerChainContext[] memory contexts, uint256 snapshot) = IJBSucker(sucker).peerChainContextsOf(chainId);
|
|
183
244
|
|
|
184
245
|
// Value each context's balance out of the currency and decimals it was recorded in, into the requested
|
|
185
246
|
// `currency` and `decimals`, and sum across every context. A context already denominated in `currency` folds
|
|
@@ -205,18 +266,21 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
205
266
|
return JBPeerChainValue({value: value, peerChainId: chainId, snapshotTimestamp: snapshot});
|
|
206
267
|
}
|
|
207
268
|
|
|
208
|
-
/// @notice Values one
|
|
209
|
-
///
|
|
210
|
-
///
|
|
211
|
-
///
|
|
269
|
+
/// @notice Values one peer chain's raw surplus held by one sucker into a currency, with peer chain ID and
|
|
270
|
+
/// freshness.
|
|
271
|
+
/// @dev Exposed as an external self-call boundary so `totalRemoteSurplusOf` can `try` it and drop a single
|
|
272
|
+
/// (sucker, chain) whose price feed is missing without losing that sucker's other chains. A context whose currency
|
|
273
|
+
/// already matches `currency` folds in at par (no feed read); a missing cross-currency feed reverts, and the
|
|
274
|
+
/// aggregator catches it and skips just this (sucker, chain).
|
|
212
275
|
/// @param sucker The sucker to read.
|
|
276
|
+
/// @param chainId The peer chain to read.
|
|
213
277
|
/// @param projectId The project whose price feeds to use.
|
|
214
278
|
/// @param currency The currency to value into.
|
|
215
279
|
/// @param decimals The decimal precision for the returned value.
|
|
216
|
-
/// @return A `JBPeerChainValue` with the valued surplus, the
|
|
217
|
-
/// key.
|
|
280
|
+
/// @return A `JBPeerChainValue` with the valued surplus, the peer chain ID, and its snapshot freshness key.
|
|
218
281
|
function remoteSurplusOf(
|
|
219
282
|
address sucker,
|
|
283
|
+
uint256 chainId,
|
|
220
284
|
uint256 projectId,
|
|
221
285
|
uint256 currency,
|
|
222
286
|
uint256 decimals
|
|
@@ -225,10 +289,8 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
225
289
|
view
|
|
226
290
|
returns (JBPeerChainValue memory)
|
|
227
291
|
{
|
|
228
|
-
// Read this sucker's raw
|
|
229
|
-
|
|
230
|
-
(JBPeerChainContext[] memory contexts, uint256 chainId, uint256 snapshot) =
|
|
231
|
-
IJBSucker(sucker).peerChainContextsOf();
|
|
292
|
+
// Read this sucker's raw contexts for the chain: one per distinct local currency, plus the freshness key.
|
|
293
|
+
(JBPeerChainContext[] memory contexts, uint256 snapshot) = IJBSucker(sucker).peerChainContextsOf(chainId);
|
|
232
294
|
|
|
233
295
|
// Value each context's surplus out of the currency and decimals it was recorded in, into the requested
|
|
234
296
|
// `currency` and `decimals`, and sum across every context. A context already denominated in `currency` folds
|
|
@@ -255,29 +317,41 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
255
317
|
}
|
|
256
318
|
|
|
257
319
|
/// @notice The cumulative total supply across all remote peer chains for a project.
|
|
258
|
-
/// @dev
|
|
259
|
-
///
|
|
260
|
-
///
|
|
320
|
+
/// @dev Each sucker now holds an accounting record per source chain it has heard about (its direct peer plus chains
|
|
321
|
+
/// gossiped through it), so this aggregates over every (sucker, chain) pair and dedups per chain. Includes
|
|
322
|
+
/// deprecated suckers only when no active sucker answers for the same peer chain, to prevent undercounting during
|
|
323
|
+
/// migration windows without letting stale deprecated records dominate live routes. Silently skips suckers and
|
|
324
|
+
/// records that revert.
|
|
261
325
|
/// @param projectId The ID of the project.
|
|
262
326
|
/// @return totalSupply The combined peer chain total supply.
|
|
263
327
|
function remoteTotalSupplyOf(uint256 projectId) external view override returns (uint256 totalSupply) {
|
|
264
328
|
address[] memory allSuckers = _suckersOf[projectId].keys();
|
|
265
|
-
uint256 len = allSuckers.length;
|
|
266
329
|
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
PeerValueScratch memory scratch = _peerValueScratch(
|
|
330
|
+
// Gather each sucker's known peer chains once, and size the per-chain dedup scratch by the total across them.
|
|
331
|
+
(uint256[][] memory chainIdsBySucker, uint256 totalChains) = _peerChainIdsBySucker(allSuckers);
|
|
332
|
+
PeerValueScratch memory scratch = _peerValueScratch(totalChains);
|
|
270
333
|
|
|
334
|
+
uint256 len = allSuckers.length;
|
|
271
335
|
for (uint256 i; i < len;) {
|
|
272
336
|
(, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
|
|
273
337
|
// Include both active and deprecated suckers in aggregate economic views.
|
|
274
338
|
if (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
339
|
+
bool isActive = val == _SUCKER_EXISTS;
|
|
340
|
+
uint256[] memory chainIds = chainIdsBySucker[i];
|
|
341
|
+
uint256 numChains = chainIds.length;
|
|
342
|
+
for (uint256 c; c < numChains;) {
|
|
343
|
+
// One call returns this chain's value, peer chain ID, and freshness key together.
|
|
344
|
+
try IJBSucker(allSuckers[i]).peerChainTotalSupplyValue(chainIds[c]) returns (
|
|
345
|
+
JBPeerChainValue memory read
|
|
346
|
+
) {
|
|
347
|
+
scratch.chainCount = _recordPeerChainValue({
|
|
348
|
+
scratch: scratch, read: read, sucker: allSuckers[i], isActive: isActive
|
|
349
|
+
});
|
|
350
|
+
} catch {}
|
|
351
|
+
unchecked {
|
|
352
|
+
++c;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
281
355
|
}
|
|
282
356
|
unchecked {
|
|
283
357
|
++i;
|
|
@@ -365,11 +439,11 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
365
439
|
}
|
|
366
440
|
|
|
367
441
|
/// @notice The cumulative peer-chain balance across all remote peer chains for a project, valued into a currency.
|
|
368
|
-
/// @dev
|
|
369
|
-
/// Includes deprecated suckers only when no active sucker answers for the same peer
|
|
370
|
-
/// during migration windows without letting stale deprecated
|
|
371
|
-
/// currency already matches is taken at par (no feed); a missing cross-currency feed
|
|
372
|
-
/// silently skipped (conservative, bias-low).
|
|
442
|
+
/// @dev Aggregates over every (sucker, chain) pair and dedups per chain by freshest record, then sums each chain's
|
|
443
|
+
/// balance valued into `currency`. Includes deprecated suckers only when no active sucker answers for the same peer
|
|
444
|
+
/// chain, to prevent undercounting during migration windows without letting stale deprecated records dominate live
|
|
445
|
+
/// routes. A context whose currency already matches is taken at par (no feed); a missing cross-currency feed
|
|
446
|
+
/// reverts and that (sucker, chain) is silently skipped (conservative, bias-low).
|
|
373
447
|
/// @param projectId The ID of the project.
|
|
374
448
|
/// @param currency The currency to value the combined balance into.
|
|
375
449
|
/// @param decimals The decimal precision for the returned value.
|
|
@@ -384,85 +458,138 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
384
458
|
override
|
|
385
459
|
returns (uint256 balance)
|
|
386
460
|
{
|
|
387
|
-
|
|
388
|
-
|
|
461
|
+
return _aggregateRemoteValueOf({projectId: projectId, currency: currency, decimals: decimals, surplus: false});
|
|
462
|
+
}
|
|
389
463
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
464
|
+
/// @notice The cumulative peer-chain surplus across all remote peer chains for a project, valued into a currency.
|
|
465
|
+
/// @dev Aggregates over every (sucker, chain) pair and dedups per chain by freshest record, then sums each chain's
|
|
466
|
+
/// surplus valued into `currency`. Includes deprecated suckers only when no active sucker answers for the same peer
|
|
467
|
+
/// chain, to prevent undercounting during migration windows without letting stale deprecated records dominate live
|
|
468
|
+
/// routes. A context whose currency already matches is taken at par (no feed); a missing cross-currency feed
|
|
469
|
+
/// reverts and that (sucker, chain) is silently skipped (conservative, bias-low).
|
|
470
|
+
/// @param projectId The ID of the project.
|
|
471
|
+
/// @param currency The currency to value the combined surplus into.
|
|
472
|
+
/// @param decimals The decimal precision for the returned value.
|
|
473
|
+
/// @return surplus The combined peer chain surplus.
|
|
474
|
+
function totalRemoteSurplusOf(
|
|
475
|
+
uint256 projectId,
|
|
476
|
+
uint256 currency,
|
|
477
|
+
uint256 decimals
|
|
478
|
+
)
|
|
479
|
+
external
|
|
480
|
+
view
|
|
481
|
+
override
|
|
482
|
+
returns (uint256 surplus)
|
|
483
|
+
{
|
|
484
|
+
return _aggregateRemoteValueOf({projectId: projectId, currency: currency, decimals: decimals, surplus: true});
|
|
485
|
+
}
|
|
393
486
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
487
|
+
//*********************************************************************//
|
|
488
|
+
// ------------------------ internal views --------------------------- //
|
|
489
|
+
//*********************************************************************//
|
|
490
|
+
|
|
491
|
+
/// @notice Values every known peer chain held by one sucker and folds each into the per-chain dedup scratch.
|
|
492
|
+
/// @dev Each (sucker, chain) is valued through a registry self-call so a missing price feed reverts only that one
|
|
493
|
+
/// pair (caught here), not the sucker's other chains. Reads the sucker's chains itself, and is extracted from the
|
|
494
|
+
/// aggregate view, to keep both stacks shallow.
|
|
495
|
+
/// @param scratch The per-chain dedup scratch to fold values into.
|
|
496
|
+
/// @param sucker The sucker whose chains to value.
|
|
497
|
+
/// @param isActive Whether the sucker is active (vs deprecated).
|
|
498
|
+
/// @param params The invariant valuation parameters for this aggregation pass.
|
|
499
|
+
function _accrueChainValues(
|
|
500
|
+
PeerValueScratch memory scratch,
|
|
501
|
+
address sucker,
|
|
502
|
+
bool isActive,
|
|
503
|
+
RemoteValueParams memory params
|
|
504
|
+
)
|
|
505
|
+
internal
|
|
506
|
+
view
|
|
507
|
+
{
|
|
508
|
+
uint256[] memory chainIds;
|
|
509
|
+
// Aggregate over the full set — directly-connected plus gossiped (virtual) chains — so cross-chain
|
|
510
|
+
// accounting
|
|
511
|
+
// reflects every chain the project knows, not only its direct bridges.
|
|
512
|
+
try IJBSucker(sucker).peerChainIds(true) returns (uint256[] memory ids) {
|
|
513
|
+
chainIds = ids;
|
|
514
|
+
} catch {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
uint256 numChains = chainIds.length;
|
|
519
|
+
for (uint256 c; c < numChains;) {
|
|
520
|
+
// A registry self-call values one chain's raw contexts so a missing feed reverts only this (sucker, chain)
|
|
521
|
+
// (caught here). Recording inside the `try` keeps this function under the stack-slot limit.
|
|
522
|
+
if (params.surplus) {
|
|
523
|
+
try this.remoteSurplusOf({
|
|
524
|
+
sucker: sucker,
|
|
525
|
+
chainId: chainIds[c],
|
|
526
|
+
projectId: params.projectId,
|
|
527
|
+
currency: params.currency,
|
|
528
|
+
decimals: params.decimals
|
|
529
|
+
}) returns (
|
|
530
|
+
JBPeerChainValue memory value
|
|
531
|
+
) {
|
|
532
|
+
scratch.chainCount = _recordPeerChainValue({
|
|
533
|
+
scratch: scratch, read: value, sucker: sucker, isActive: isActive
|
|
534
|
+
});
|
|
535
|
+
} catch {}
|
|
536
|
+
} else {
|
|
400
537
|
try this.remoteBalanceOf({
|
|
401
|
-
sucker:
|
|
538
|
+
sucker: sucker,
|
|
539
|
+
chainId: chainIds[c],
|
|
540
|
+
projectId: params.projectId,
|
|
541
|
+
currency: params.currency,
|
|
542
|
+
decimals: params.decimals
|
|
402
543
|
}) returns (
|
|
403
|
-
JBPeerChainValue memory
|
|
544
|
+
JBPeerChainValue memory value
|
|
404
545
|
) {
|
|
405
546
|
scratch.chainCount = _recordPeerChainValue({
|
|
406
|
-
scratch: scratch, read:
|
|
547
|
+
scratch: scratch, read: value, sucker: sucker, isActive: isActive
|
|
407
548
|
});
|
|
408
549
|
} catch {}
|
|
409
550
|
}
|
|
410
551
|
unchecked {
|
|
411
|
-
++
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Sum the per-chain selected values.
|
|
416
|
-
for (uint256 k; k < scratch.chainCount;) {
|
|
417
|
-
balance += scratch.values[k];
|
|
418
|
-
unchecked {
|
|
419
|
-
++k;
|
|
552
|
+
++c;
|
|
420
553
|
}
|
|
421
554
|
}
|
|
422
555
|
}
|
|
423
556
|
|
|
424
|
-
/// @notice The cumulative peer-chain surplus across all
|
|
425
|
-
///
|
|
426
|
-
///
|
|
427
|
-
///
|
|
428
|
-
///
|
|
429
|
-
/// silently skipped (conservative, bias-low).
|
|
557
|
+
/// @notice The cumulative peer-chain balance or surplus across all of a project's peer chains, valued into a
|
|
558
|
+
/// currency.
|
|
559
|
+
/// @dev Aggregates over every (sucker, chain) pair and dedups per chain by freshest record (active supersedes
|
|
560
|
+
/// deprecated), then sums the selected per-chain values. Shared by `totalRemoteBalanceOf` and
|
|
561
|
+
/// `totalRemoteSurplusOf`.
|
|
430
562
|
/// @param projectId The ID of the project.
|
|
431
|
-
/// @param currency The currency to value
|
|
563
|
+
/// @param currency The currency to value into.
|
|
432
564
|
/// @param decimals The decimal precision for the returned value.
|
|
433
|
-
/// @
|
|
434
|
-
|
|
565
|
+
/// @param surplus Whether to aggregate surplus (true) or balance (false).
|
|
566
|
+
/// @return total The combined valued amount across every peer chain.
|
|
567
|
+
function _aggregateRemoteValueOf(
|
|
435
568
|
uint256 projectId,
|
|
436
569
|
uint256 currency,
|
|
437
|
-
uint256 decimals
|
|
570
|
+
uint256 decimals,
|
|
571
|
+
bool surplus
|
|
438
572
|
)
|
|
439
|
-
|
|
573
|
+
internal
|
|
440
574
|
view
|
|
441
|
-
|
|
442
|
-
returns (uint256 surplus)
|
|
575
|
+
returns (uint256 total)
|
|
443
576
|
{
|
|
444
577
|
address[] memory allSuckers = _suckersOf[projectId].keys();
|
|
445
|
-
uint256 len = allSuckers.length;
|
|
446
578
|
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
PeerValueScratch memory scratch = _peerValueScratch(
|
|
579
|
+
// Size the per-chain dedup scratch by the total records across the project's suckers.
|
|
580
|
+
(, uint256 totalChains) = _peerChainIdsBySucker(allSuckers);
|
|
581
|
+
PeerValueScratch memory scratch = _peerValueScratch(totalChains);
|
|
582
|
+
RemoteValueParams memory params =
|
|
583
|
+
RemoteValueParams({projectId: projectId, currency: currency, decimals: decimals, surplus: surplus});
|
|
450
584
|
|
|
585
|
+
uint256 len = allSuckers.length;
|
|
451
586
|
for (uint256 i; i < len;) {
|
|
452
587
|
(, uint256 val) = _suckersOf[projectId].tryGet(allSuckers[i]);
|
|
453
588
|
// Include both active and deprecated suckers in aggregate economic views.
|
|
454
589
|
if (val == _SUCKER_EXISTS || val == _SUCKER_DEPRECATED) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
sucker: allSuckers[i], projectId: projectId, currency: currency, decimals: decimals
|
|
459
|
-
}) returns (
|
|
460
|
-
JBPeerChainValue memory read
|
|
461
|
-
) {
|
|
462
|
-
scratch.chainCount = _recordPeerChainValue({
|
|
463
|
-
scratch: scratch, read: read, sucker: allSuckers[i], isActive: val == _SUCKER_EXISTS
|
|
464
|
-
});
|
|
465
|
-
} catch {}
|
|
590
|
+
_accrueChainValues({
|
|
591
|
+
scratch: scratch, sucker: allSuckers[i], isActive: val == _SUCKER_EXISTS, params: params
|
|
592
|
+
});
|
|
466
593
|
}
|
|
467
594
|
unchecked {
|
|
468
595
|
++i;
|
|
@@ -471,22 +598,51 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
471
598
|
|
|
472
599
|
// Sum the per-chain selected values.
|
|
473
600
|
for (uint256 k; k < scratch.chainCount;) {
|
|
474
|
-
|
|
601
|
+
total += scratch.values[k];
|
|
475
602
|
unchecked {
|
|
476
603
|
++k;
|
|
477
604
|
}
|
|
478
605
|
}
|
|
479
606
|
}
|
|
480
607
|
|
|
481
|
-
//*********************************************************************//
|
|
482
|
-
// ------------------------ internal views --------------------------- //
|
|
483
|
-
//*********************************************************************//
|
|
484
|
-
|
|
485
608
|
/// @dev ERC-2771 specifies the context as being a single address (20 bytes).
|
|
486
609
|
function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
|
|
487
610
|
return ERC2771Context._contextSuffixLength();
|
|
488
611
|
}
|
|
489
612
|
|
|
613
|
+
/// @notice Reads one sucker's raw records and folds each into the per-chain gather scratch.
|
|
614
|
+
/// @dev Extracted from `peerChainAccountsOf` to keep its stack shallow. A sucker that reverts contributes nothing.
|
|
615
|
+
/// The destination chain, the local chain, and chain 0 are excluded.
|
|
616
|
+
/// @param scratch The per-chain gather scratch to fold records into.
|
|
617
|
+
/// @param sucker The sucker to read records from.
|
|
618
|
+
/// @param isActive Whether the sucker is active (vs deprecated).
|
|
619
|
+
/// @param exceptChainId The destination chain to exclude.
|
|
620
|
+
function _gatherSuckerAccounts(
|
|
621
|
+
PeerAccountScratch memory scratch,
|
|
622
|
+
address sucker,
|
|
623
|
+
bool isActive,
|
|
624
|
+
uint256 exceptChainId
|
|
625
|
+
)
|
|
626
|
+
internal
|
|
627
|
+
view
|
|
628
|
+
{
|
|
629
|
+
try IJBSucker(sucker).peerChainAccountsOf() returns (JBChainAccounting[] memory records) {
|
|
630
|
+
uint256 numRecords = records.length;
|
|
631
|
+
for (uint256 r; r < numRecords;) {
|
|
632
|
+
// Exclude the destination chain (authoritative about itself), the local chain, and chain 0.
|
|
633
|
+
if (
|
|
634
|
+
records[r].chainId != exceptChainId && records[r].chainId != block.chainid
|
|
635
|
+
&& records[r].chainId != 0
|
|
636
|
+
) {
|
|
637
|
+
_recordPeerChainAccounting({scratch: scratch, record: records[r], isActive: isActive});
|
|
638
|
+
}
|
|
639
|
+
unchecked {
|
|
640
|
+
++r;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} catch {}
|
|
644
|
+
}
|
|
645
|
+
|
|
490
646
|
/// @notice The calldata. Preferred to use over `msg.data`.
|
|
491
647
|
/// @return calldata The `msg.data` of this call.
|
|
492
648
|
function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
|
|
@@ -499,6 +655,44 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
499
655
|
return ERC2771Context._msgSender();
|
|
500
656
|
}
|
|
501
657
|
|
|
658
|
+
/// @notice Reads a sucker's peer chain ID, reverting if the sucker cannot identify a real peer chain.
|
|
659
|
+
/// @param sucker The sucker to query.
|
|
660
|
+
/// @return chainId The non-zero peer chain ID.
|
|
661
|
+
function _peerChainIdOf(IJBSucker sucker) internal view returns (uint256 chainId) {
|
|
662
|
+
chainId = sucker.peerChainId();
|
|
663
|
+
if (chainId == 0) revert JBSuckerRegistry_ZeroPeerChainId({sucker: address(sucker)});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/// @notice Gathers each sucker's known peer chains and the total across them, to size per-chain aggregation
|
|
667
|
+
/// scratch.
|
|
668
|
+
/// @dev Each sucker holds a record per source chain it has heard about, so the distinct-chain count can exceed the
|
|
669
|
+
/// sucker count. A sucker that reverts contributes no chains. The gathered arrays are reused by the caller's
|
|
670
|
+
/// per-chain loop so `peerChainIds()` is read once per sucker.
|
|
671
|
+
/// @param allSuckers The project's suckers (active and deprecated).
|
|
672
|
+
/// @return chainIdsBySucker Each sucker's peer chain IDs, parallel to `allSuckers`; empty for a sucker that
|
|
673
|
+
/// reverts.
|
|
674
|
+
/// @return totalChains The total number of (sucker, chain) entries, the upper bound on distinct peer chains.
|
|
675
|
+
function _peerChainIdsBySucker(address[] memory allSuckers)
|
|
676
|
+
internal
|
|
677
|
+
view
|
|
678
|
+
returns (uint256[][] memory chainIdsBySucker, uint256 totalChains)
|
|
679
|
+
{
|
|
680
|
+
uint256 len = allSuckers.length;
|
|
681
|
+
chainIdsBySucker = new uint256[][](len);
|
|
682
|
+
for (uint256 i; i < len;) {
|
|
683
|
+
// The full set — directly-connected plus gossiped (virtual) chains — drives cross-chain aggregation.
|
|
684
|
+
try IJBSucker(allSuckers[i]).peerChainIds(true) returns (uint256[] memory chainIds) {
|
|
685
|
+
chainIdsBySucker[i] = chainIds;
|
|
686
|
+
totalChains += chainIds.length;
|
|
687
|
+
} catch {
|
|
688
|
+
chainIdsBySucker[i] = new uint256[](0);
|
|
689
|
+
}
|
|
690
|
+
unchecked {
|
|
691
|
+
++i;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
502
696
|
/// @notice Allocates scratch arrays used to collapse many suckers into one aggregate value per peer chain.
|
|
503
697
|
/// @dev `len` is the number of suckers being scanned, which is the maximum possible number of distinct peer
|
|
504
698
|
/// chains. `chainCount` starts at zero and is incremented as new peer chains are discovered.
|
|
@@ -512,12 +706,77 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
512
706
|
scratch.hasActiveValue = new bool[](len);
|
|
513
707
|
}
|
|
514
708
|
|
|
515
|
-
/// @notice
|
|
516
|
-
/// @
|
|
517
|
-
///
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
709
|
+
/// @notice Records one source chain's raw accounting record into a per-chain gather scratch, keeping the freshest.
|
|
710
|
+
/// @dev Mirrors `_recordPeerValue`'s selection rule for raw records: an active sucker's record supersedes a
|
|
711
|
+
/// deprecated one's for the same chain; among same-state records the strictly-fresher timestamp wins; equal
|
|
712
|
+
/// freshness keeps the first writer, since records from one origin chain at one freshness key are identical. Used
|
|
713
|
+
/// to gather records for re-gossiping.
|
|
714
|
+
/// @param scratch The per-chain gather scratch recorded so far.
|
|
715
|
+
/// @param record The record to fold in.
|
|
716
|
+
/// @param isActive Whether the record came from an active sucker.
|
|
717
|
+
function _recordPeerChainAccounting(
|
|
718
|
+
PeerAccountScratch memory scratch,
|
|
719
|
+
JBChainAccounting memory record,
|
|
720
|
+
bool isActive
|
|
721
|
+
)
|
|
722
|
+
internal
|
|
723
|
+
pure
|
|
724
|
+
{
|
|
725
|
+
for (uint256 k; k < scratch.chainCount;) {
|
|
726
|
+
if (scratch.chainIds[k] == record.chainId) {
|
|
727
|
+
if (isActive) {
|
|
728
|
+
// An active record replaces a deprecated one, or a staler active one.
|
|
729
|
+
if (!scratch.hasActiveRecord[k] || record.timestamp > scratch.records[k].timestamp) {
|
|
730
|
+
scratch.records[k] = record;
|
|
731
|
+
}
|
|
732
|
+
scratch.hasActiveRecord[k] = true;
|
|
733
|
+
} else if (!scratch.hasActiveRecord[k] && record.timestamp > scratch.records[k].timestamp) {
|
|
734
|
+
// A deprecated record only fills the gap until an active record for this chain is seen.
|
|
735
|
+
scratch.records[k] = record;
|
|
736
|
+
}
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
unchecked {
|
|
740
|
+
++k;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
scratch.chainIds[scratch.chainCount] = record.chainId;
|
|
745
|
+
scratch.records[scratch.chainCount] = record;
|
|
746
|
+
scratch.hasActiveRecord[scratch.chainCount] = isActive;
|
|
747
|
+
unchecked {
|
|
748
|
+
scratch.chainCount = scratch.chainCount + 1;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/// @notice Records a combined peer-chain read (value, peer chain ID, snapshot freshness key) from one sucker.
|
|
753
|
+
/// @dev A wrapper over `_recordPeerValue` that unpacks the single-call `JBPeerChainValue` read and enforces the
|
|
754
|
+
/// same non-zero peer-chain requirement the registry applies everywhere else. The peer-chain check reverts here
|
|
755
|
+
/// (inside the caller's `try` success body, so the revert propagates) to preserve the prior behavior where a
|
|
756
|
+
/// sucker reporting a zero peer chain ID fails the whole aggregate view.
|
|
757
|
+
/// @param scratch The per-chain aggregate values and freshness keys recorded so far.
|
|
758
|
+
/// @param read The combined value, peer chain ID, and snapshot freshness key returned by the sucker.
|
|
759
|
+
/// @param sucker The sucker the read came from, used only for the zero-peer-chain error.
|
|
760
|
+
/// @param isActive Whether the value came from an active sucker.
|
|
761
|
+
/// @return The updated number of populated chain entries.
|
|
762
|
+
function _recordPeerChainValue(
|
|
763
|
+
PeerValueScratch memory scratch,
|
|
764
|
+
JBPeerChainValue memory read,
|
|
765
|
+
address sucker,
|
|
766
|
+
bool isActive
|
|
767
|
+
)
|
|
768
|
+
internal
|
|
769
|
+
pure
|
|
770
|
+
returns (uint256)
|
|
771
|
+
{
|
|
772
|
+
if (read.peerChainId == 0) revert JBSuckerRegistry_ZeroPeerChainId({sucker: sucker});
|
|
773
|
+
return _recordPeerValue({
|
|
774
|
+
scratch: scratch,
|
|
775
|
+
chainId: read.peerChainId,
|
|
776
|
+
value: read.value,
|
|
777
|
+
snapshotTimestamp: read.snapshotTimestamp,
|
|
778
|
+
isActive: isActive
|
|
779
|
+
});
|
|
521
780
|
}
|
|
522
781
|
|
|
523
782
|
/// @notice Records a project-scoped peer-chain aggregate value.
|
|
@@ -541,6 +800,14 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
541
800
|
pure
|
|
542
801
|
returns (uint256)
|
|
543
802
|
{
|
|
803
|
+
// A freshly-deployed active sucker advertises its direct peer chain through `peerChainIds(true)` before it has
|
|
804
|
+
// received any snapshot, producing an empty sentinel (value 0, timestamp 0). Skip that empty active record so
|
|
805
|
+
// it cannot supersede a deprecated sucker's real record for the chain during a migration window. The timestamp
|
|
806
|
+
// is the discriminator — a real snapshot always stamps a nonzero freshness key, so a zero key means "never
|
|
807
|
+
// synced" and this never drops a legitimately zero-valued synced chain; the value clause keeps the skip to
|
|
808
|
+
// genuinely empty records.
|
|
809
|
+
if (isActive && snapshotTimestamp == 0 && value == 0) return scratch.chainCount;
|
|
810
|
+
|
|
544
811
|
for (uint256 j; j < scratch.chainCount;) {
|
|
545
812
|
if (scratch.chainIds[j] == chainId) {
|
|
546
813
|
// Each sucker caches the entire remote chain's state (not a per-sucker share), so multiple
|
|
@@ -580,36 +847,6 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
580
847
|
}
|
|
581
848
|
}
|
|
582
849
|
|
|
583
|
-
/// @notice Records a combined peer-chain read (value, peer chain ID, snapshot freshness key) from one sucker.
|
|
584
|
-
/// @dev A wrapper over `_recordPeerValue` that unpacks the single-call `JBPeerChainValue` read and enforces the
|
|
585
|
-
/// same non-zero peer-chain requirement the registry applies everywhere else. The peer-chain check reverts here
|
|
586
|
-
/// (inside the caller's `try` success body, so the revert propagates) to preserve the prior behavior where a
|
|
587
|
-
/// sucker reporting a zero peer chain ID fails the whole aggregate view.
|
|
588
|
-
/// @param scratch The per-chain aggregate values and freshness keys recorded so far.
|
|
589
|
-
/// @param read The combined value, peer chain ID, and snapshot freshness key returned by the sucker.
|
|
590
|
-
/// @param sucker The sucker the read came from, used only for the zero-peer-chain error.
|
|
591
|
-
/// @param isActive Whether the value came from an active sucker.
|
|
592
|
-
/// @return The updated number of populated chain entries.
|
|
593
|
-
function _recordPeerChainValue(
|
|
594
|
-
PeerValueScratch memory scratch,
|
|
595
|
-
JBPeerChainValue memory read,
|
|
596
|
-
address sucker,
|
|
597
|
-
bool isActive
|
|
598
|
-
)
|
|
599
|
-
internal
|
|
600
|
-
pure
|
|
601
|
-
returns (uint256)
|
|
602
|
-
{
|
|
603
|
-
if (read.peerChainId == 0) revert JBSuckerRegistry_ZeroPeerChainId({sucker: sucker});
|
|
604
|
-
return _recordPeerValue({
|
|
605
|
-
scratch: scratch,
|
|
606
|
-
chainId: read.peerChainId,
|
|
607
|
-
value: read.value,
|
|
608
|
-
snapshotTimestamp: read.snapshotTimestamp,
|
|
609
|
-
isActive: isActive
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
850
|
/// @notice Values an amount held in one currency/decimals into another, mirroring the terminal store.
|
|
614
851
|
/// @dev Adjusts decimals, then converts currency via the prices contract. Both steps short-circuit on identity, and
|
|
615
852
|
/// the currency step also short-circuits on a zero amount, so a same-currency context consults no feed. A missing
|
|
@@ -785,6 +1022,14 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
785
1022
|
emit SuckerDeprecated({projectId: projectId, sucker: address(sucker), caller: _msgSender()});
|
|
786
1023
|
}
|
|
787
1024
|
|
|
1025
|
+
/// @notice Removes a sucker deployer from the allowlist.
|
|
1026
|
+
/// @dev Can only be called by this contract's owner (initially project ID 1, or JuiceboxDAO).
|
|
1027
|
+
/// @param deployer The address of the deployer to remove.
|
|
1028
|
+
function removeSuckerDeployer(address deployer) public override onlyOwner {
|
|
1029
|
+
suckerDeployerIsAllowed[deployer] = false;
|
|
1030
|
+
emit SuckerDeployerRemoved({deployer: deployer, caller: _msgSender()});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
788
1033
|
/// @notice Set the ETH fee (in wei) paid into the fee project on each toRemote() call.
|
|
789
1034
|
/// @dev Only callable by the contract owner. Fee cannot exceed MAX_TO_REMOTE_FEE.
|
|
790
1035
|
/// @param fee The new fee amount in wei.
|
|
@@ -794,12 +1039,4 @@ contract JBSuckerRegistry is ERC2771Context, Ownable, JBPermissioned, IJBSuckerR
|
|
|
794
1039
|
toRemoteFee = fee;
|
|
795
1040
|
emit ToRemoteFeeChanged({oldFee: oldFee, newFee: fee, caller: _msgSender()});
|
|
796
1041
|
}
|
|
797
|
-
|
|
798
|
-
/// @notice Removes a sucker deployer from the allowlist.
|
|
799
|
-
/// @dev Can only be called by this contract's owner (initially project ID 1, or JuiceboxDAO).
|
|
800
|
-
/// @param deployer The address of the deployer to remove.
|
|
801
|
-
function removeSuckerDeployer(address deployer) public override onlyOwner {
|
|
802
|
-
suckerDeployerIsAllowed[deployer] = false;
|
|
803
|
-
emit SuckerDeployerRemoved({deployer: deployer, caller: _msgSender()});
|
|
804
|
-
}
|
|
805
1042
|
}
|