@bananapus/core-v6 0.0.42 → 0.0.44

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +3 -3
  3. package/package.json +2 -2
  4. package/references/entrypoints.md +3 -1
  5. package/references/types-errors-events.md +2 -3
  6. package/src/JBChainlinkV3PriceFeed.sol +14 -6
  7. package/src/JBChainlinkV3SequencerPriceFeed.sol +5 -6
  8. package/src/JBController.sol +84 -68
  9. package/src/JBDirectory.sol +4 -7
  10. package/src/JBERC20.sol +8 -7
  11. package/src/JBFeelessAddresses.sol +32 -13
  12. package/src/JBFundAccessLimits.sol +12 -4
  13. package/src/JBMultiTerminal.sol +39 -61
  14. package/src/JBPermissions.sol +6 -3
  15. package/src/JBPrices.sol +18 -12
  16. package/src/JBRulesets.sol +4 -25
  17. package/src/JBSplits.sol +13 -5
  18. package/src/JBTerminalStore.sol +46 -82
  19. package/src/JBTokens.sol +11 -13
  20. package/src/abstract/JBControlled.sol +1 -2
  21. package/src/interfaces/IJBController.sol +0 -6
  22. package/src/interfaces/IJBFeelessAddresses.sol +17 -7
  23. package/src/libraries/JBCashOuts.sol +6 -2
  24. package/src/libraries/JBCurrencyIds.sol +3 -0
  25. package/src/libraries/JBMetadataResolver.sol +20 -12
  26. package/src/libraries/JBPayoutSplitGroupLib.sol +8 -3
  27. package/src/libraries/JBRulesetMetadataResolver.sol +4 -4
  28. package/src/libraries/JBSplitGroupIds.sol +1 -0
  29. package/src/periphery/JBDeadline1Day.sol +1 -0
  30. package/src/periphery/JBDeadline3Days.sol +1 -0
  31. package/src/periphery/JBDeadline3Hours.sol +1 -0
  32. package/src/periphery/JBDeadline7Days.sol +1 -0
  33. package/src/structs/JBBeforeCashOutRecordedContext.sol +3 -2
  34. package/src/structs/JBRulesetMetadata.sol +3 -3
  35. package/test/helpers/JBTest.sol +3 -3
package/src/JBPrices.sol CHANGED
@@ -26,9 +26,9 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
26
26
  //*********************************************************************//
27
27
 
28
28
  error JBPrices_PriceFeedAlreadyExists(IJBPriceFeed feed);
29
- error JBPrices_PriceFeedNotFound();
30
- error JBPrices_ZeroPricingCurrency();
31
- error JBPrices_ZeroUnitCurrency();
29
+ error JBPrices_PriceFeedNotFound(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency);
30
+ error JBPrices_ZeroPricingCurrency(uint256 projectId, uint256 pricingCurrency);
31
+ error JBPrices_ZeroUnitCurrency(uint256 projectId, uint256 unitCurrency);
32
32
 
33
33
  //*********************************************************************//
34
34
  // ------------------------- public constants ------------------------ //
@@ -111,20 +111,23 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
111
111
  projectId == DEFAULT_PROJECT_ID ? _checkOwner() : _onlyControllerOf(projectId);
112
112
 
113
113
  // Make sure the pricing currency isn't 0.
114
- if (pricingCurrency == 0) revert JBPrices_ZeroPricingCurrency();
114
+ if (pricingCurrency == 0) {
115
+ revert JBPrices_ZeroPricingCurrency({projectId: projectId, pricingCurrency: pricingCurrency});
116
+ }
115
117
 
116
118
  // Make sure the unit currency isn't 0.
117
- if (unitCurrency == 0) revert JBPrices_ZeroUnitCurrency();
119
+ if (unitCurrency == 0) revert JBPrices_ZeroUnitCurrency({projectId: projectId, unitCurrency: unitCurrency});
118
120
 
119
121
  // Make sure there isn't already a default price feed for the pair or its inverse.
120
122
  if (
121
123
  priceFeedFor[DEFAULT_PROJECT_ID][pricingCurrency][unitCurrency] != IJBPriceFeed(address(0))
122
124
  || priceFeedFor[DEFAULT_PROJECT_ID][unitCurrency][pricingCurrency] != IJBPriceFeed(address(0))
123
125
  ) {
124
- revert JBPrices_PriceFeedAlreadyExists(priceFeedFor[DEFAULT_PROJECT_ID][pricingCurrency][unitCurrency]
125
- != IJBPriceFeed(address(0))
126
+ revert JBPrices_PriceFeedAlreadyExists({
127
+ feed: priceFeedFor[DEFAULT_PROJECT_ID][pricingCurrency][unitCurrency] != IJBPriceFeed(address(0))
126
128
  ? priceFeedFor[DEFAULT_PROJECT_ID][pricingCurrency][unitCurrency]
127
- : priceFeedFor[DEFAULT_PROJECT_ID][unitCurrency][pricingCurrency]);
129
+ : priceFeedFor[DEFAULT_PROJECT_ID][unitCurrency][pricingCurrency]
130
+ });
128
131
  }
129
132
 
130
133
  // Make sure this project doesn't already have a price feed for the pair or its inverse.
@@ -132,10 +135,11 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
132
135
  priceFeedFor[projectId][pricingCurrency][unitCurrency] != IJBPriceFeed(address(0))
133
136
  || priceFeedFor[projectId][unitCurrency][pricingCurrency] != IJBPriceFeed(address(0))
134
137
  ) {
135
- revert JBPrices_PriceFeedAlreadyExists(priceFeedFor[projectId][pricingCurrency][unitCurrency]
136
- != IJBPriceFeed(address(0))
138
+ revert JBPrices_PriceFeedAlreadyExists({
139
+ feed: priceFeedFor[projectId][pricingCurrency][unitCurrency] != IJBPriceFeed(address(0))
137
140
  ? priceFeedFor[projectId][pricingCurrency][unitCurrency]
138
- : priceFeedFor[projectId][unitCurrency][pricingCurrency]);
141
+ : priceFeedFor[projectId][unitCurrency][pricingCurrency]
142
+ });
139
143
  }
140
144
 
141
145
  // Price feed immutability is by design to prevent admin-key attacks on price oracles.
@@ -212,7 +216,9 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
212
216
  }
213
217
 
214
218
  // No price feed available, revert.
215
- revert JBPrices_PriceFeedNotFound();
219
+ revert JBPrices_PriceFeedNotFound({
220
+ projectId: projectId, pricingCurrency: pricingCurrency, unitCurrency: unitCurrency
221
+ });
216
222
  }
217
223
 
218
224
  //*********************************************************************//
@@ -123,7 +123,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
123
123
 
124
124
  // Weight cut percent must be less than or equal to 100%.
125
125
  if (weightCutPercent > JBConstants.MAX_WEIGHT_CUT_PERCENT) {
126
- revert JBRulesets_InvalidWeightCutPercent(weightCutPercent);
126
+ revert JBRulesets_InvalidWeightCutPercent({percent: weightCutPercent});
127
127
  }
128
128
 
129
129
  // Weight must fit into a uint112.
@@ -137,7 +137,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
137
137
  // Make sure the min start date fits in a uint48, and that the start date of the following ruleset will also fit
138
138
  // within the max.
139
139
  if (mustStartAtOrAfter + duration > type(uint48).max) {
140
- revert JBRulesets_InvalidRulesetEndTime(mustStartAtOrAfter + duration, type(uint48).max);
140
+ revert JBRulesets_InvalidRulesetEndTime({timestamp: mustStartAtOrAfter + duration, limit: type(uint48).max});
141
141
  }
142
142
 
143
143
  // Approval hook should be a valid contract, supporting the correct interface
@@ -152,7 +152,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
152
152
  // with the
153
153
  // wrong interface
154
154
  } catch {
155
- revert JBRulesets_InvalidRulesetApprovalHook(approvalHook); // No ERC165 support
155
+ revert JBRulesets_InvalidRulesetApprovalHook({hook: approvalHook}); // No ERC165 support
156
156
  }
157
157
  }
158
158
 
@@ -217,7 +217,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
217
217
  _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: false});
218
218
 
219
219
  // Nothing to cache if the target ruleset doesn't have a duration or a weight cut percent.
220
- // slither-disable-next-line incorrect-equality
221
220
  if (targetRuleset.duration == 0 || targetRuleset.weightCutPercent == 0) return;
222
221
 
223
222
  // Get a reference to the current cache.
@@ -356,7 +355,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
356
355
  /// @return ruleset The project's current ruleset.
357
356
  function currentOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
358
357
  // If the project does not have a ruleset, return an empty struct.
359
- // slither-disable-next-line incorrect-equality
360
358
  if (latestRulesetIdOf[projectId] == 0) {
361
359
  return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
362
360
  }
@@ -375,7 +373,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
375
373
 
376
374
  // Check to see if this ruleset's approval hook is approved if it exists.
377
375
  // If so, return it.
378
- // slither-disable-next-line incorrect-equality
379
376
  if (approvalStatus == JBApprovalStatus.Approved || approvalStatus == JBApprovalStatus.Empty) {
380
377
  return ruleset;
381
378
  }
@@ -413,7 +410,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
413
410
  }
414
411
 
415
412
  // If the base has no duration, it's still the current one.
416
- // slither-disable-next-line incorrect-equality
417
413
  if (ruleset.duration == 0) return ruleset;
418
414
 
419
415
  // Return a simulation of the current ruleset.
@@ -466,7 +462,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
466
462
  /// @return ruleset The struct for the project's upcoming ruleset.
467
463
  function upcomingOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
468
464
  // If the project does not have a latest ruleset, return an empty struct.
469
- // slither-disable-next-line incorrect-equality
470
465
  if (latestRulesetIdOf[projectId] == 0) {
471
466
  return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
472
467
  }
@@ -487,7 +482,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
487
482
 
488
483
  // If the approval hook is empty, expects approval, or has approved the ruleset, return it.
489
484
  if (
490
- // slither-disable-next-line incorrect-equality
491
485
  approvalStatus == JBApprovalStatus.Approved || approvalStatus == JBApprovalStatus.ApprovalExpected
492
486
  || approvalStatus == JBApprovalStatus.Empty
493
487
  ) return ruleset;
@@ -510,7 +504,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
510
504
  }
511
505
 
512
506
  // There's no queued if the current has a duration of 0.
513
- // slither-disable-next-line incorrect-equality
514
507
  if (ruleset.duration == 0) return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
515
508
 
516
509
  // Get a reference to the approval status.
@@ -518,7 +511,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
518
511
 
519
512
  // Check to see if this ruleset's approval hook hasn't failed.
520
513
  // If so, return a ruleset based on it.
521
- // slither-disable-next-line incorrect-equality
522
514
  if (approvalStatus == JBApprovalStatus.Approved || approvalStatus == JBApprovalStatus.Empty) {
523
515
  return _simulateCycledRulesetBasedOn({projectId: projectId, baseRuleset: ruleset, allowMidRuleset: false});
524
516
  }
@@ -528,7 +520,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
528
520
  ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: true});
529
521
 
530
522
  // There's no queued if the base, which must still be the current, has a duration of 0.
531
- // slither-disable-next-line incorrect-equality
532
523
  if (ruleset.duration == 0) return _getStructFor({projectId: 0, rulesetId: 0, withMetadata: false});
533
524
 
534
525
  // Return a simulated cycled ruleset.
@@ -557,7 +548,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
557
548
  returns (uint256)
558
549
  {
559
550
  // A subsequent ruleset to one with a duration of 0 should be the next number.
560
- // slither-disable-next-line incorrect-equality
561
551
  if (baseRulesetDuration == 0) {
562
552
  return baseRulesetCycleNumber + 1;
563
553
  }
@@ -585,7 +575,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
585
575
  returns (uint256 start)
586
576
  {
587
577
  // A subsequent ruleset to one with a duration of 0 should start as soon as possible.
588
- // slither-disable-next-line incorrect-equality
589
578
  if (baseRulesetDuration == 0) return mustStartAtOrAfter;
590
579
 
591
580
  // The time when the ruleset immediately after the specified ruleset starts.
@@ -598,7 +587,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
598
587
 
599
588
  // The amount of seconds since the `mustStartAtOrAfter` time which results in a start time that might satisfy
600
589
  // the specified limits.
601
- // slither-disable-next-line weak-prng
602
590
  uint256 timeFromImmediateStartMultiple = (mustStartAtOrAfter - nextImmediateStart) % baseRulesetDuration;
603
591
 
604
592
  // A reference to the first possible start timestamp.
@@ -634,7 +622,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
634
622
  returns (uint256 weight)
635
623
  {
636
624
  // A subsequent ruleset to one with a duration of 0 should have the next possible weight.
637
- // slither-disable-next-line incorrect-equality
638
625
  if (baseRulesetDuration == 0) {
639
626
  return mulDiv(
640
627
  baseRulesetWeight,
@@ -647,7 +634,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
647
634
  weight = baseRulesetWeight;
648
635
 
649
636
  // If the weight cut percent is 0, the weight doesn't change.
650
- // slither-disable-next-line incorrect-equality
651
637
  if (baseRulesetWeightCutPercent == 0) return weight;
652
638
 
653
639
  // The difference between the start of the base ruleset and the proposed start.
@@ -678,7 +664,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
678
664
  // If too many iterations remain after cache lookup, require the cache to be populated first.
679
665
  // This prevents gas exhaustion for short-duration rulesets with large cycle counts.
680
666
  if (weightCutMultiple > _WEIGHT_CUT_MULTIPLE_CACHE_LOOKUP_THRESHOLD) {
681
- revert JBRulesets_WeightCacheRequired(projectId);
667
+ revert JBRulesets_WeightCacheRequired({projectId: projectId});
682
668
  }
683
669
 
684
670
  // Cache the cut factor and max percent to avoid recomputing each iteration.
@@ -719,7 +705,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
719
705
  uint256 latestId = latestRulesetIdOf[projectId];
720
706
 
721
707
  // If the project doesn't have a ruleset yet, initialize one.
722
- // slither-disable-next-line incorrect-equality
723
708
  if (latestId == 0) {
724
709
  // Use an empty ruleset as the base.
725
710
  return _initializeRulesetFor({
@@ -807,7 +792,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
807
792
  internal
808
793
  {
809
794
  // If there is no base, initialize a first ruleset.
810
- // slither-disable-next-line incorrect-equality
811
795
  if (baseRuleset.cycleNumber == 0) {
812
796
  // Set fresh intrinsic properties.
813
797
  _packAndStoreIntrinsicPropertiesOf({
@@ -910,7 +894,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
910
894
  /// @return The approval status of the project.
911
895
  function _approvalStatusOf(uint256 projectId, JBRuleset memory ruleset) internal view returns (JBApprovalStatus) {
912
896
  // If there is no ruleset ID to check the approval hook of, the approval hook is empty.
913
- // slither-disable-next-line incorrect-equality
914
897
  if (ruleset.basedOnId == 0) return JBApprovalStatus.Empty;
915
898
 
916
899
  // Read only the packed user properties to extract the approval hook address,
@@ -928,7 +911,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
928
911
  // Wrap in try/catch to prevent a reverting approval hook from permanently freezing the project.
929
912
  // Note: A malicious hook that consumes all gas (e.g. infinite loop) could still DoS via gas exhaustion.
930
913
  // This is accepted risk since the project owner chose their own approval hook.
931
- // slither-disable-next-line calls-loop
932
914
  try approvalHook.approvalStatusOf({projectId: projectId, ruleset: ruleset}) returns (JBApprovalStatus status) {
933
915
  return status;
934
916
  } catch {
@@ -988,7 +970,6 @@ contract JBRulesets is JBControlled, IJBRulesets {
988
970
  returns (JBRuleset memory ruleset)
989
971
  {
990
972
  // Return an empty ruleset if the specified `rulesetId` is 0.
991
- // slither-disable-next-line incorrect-equality
992
973
  if (rulesetId == 0) return ruleset;
993
974
 
994
975
  // forge-lint: disable-next-line(unsafe-typecast)
@@ -1104,12 +1085,10 @@ contract JBRulesets is JBControlled, IJBRulesets {
1104
1085
  JBRuleset memory ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: false});
1105
1086
 
1106
1087
  // There is no upcoming ruleset if the latest ruleset has already started.
1107
- // slither-disable-next-line incorrect-equality
1108
1088
  // forge-lint: disable-next-line(block-timestamp)
1109
1089
  if (block.timestamp >= ruleset.start) return 0;
1110
1090
 
1111
1091
  // If this is the first ruleset, it is queued.
1112
- // slither-disable-next-line incorrect-equality
1113
1092
  if (ruleset.cycleNumber == 1) return rulesetId;
1114
1093
 
1115
1094
  // Get a reference to the ID of the ruleset the latest ruleset was based on.
package/src/JBSplits.sol CHANGED
@@ -20,8 +20,8 @@ contract JBSplits is JBControlled, IJBSplits {
20
20
  //*********************************************************************//
21
21
 
22
22
  error JBSplits_PreviousLockedSplitsNotIncluded(uint256 projectId, uint256 rulesetId);
23
- error JBSplits_TotalPercentExceeds100();
24
- error JBSplits_ZeroSplitPercent();
23
+ error JBSplits_TotalPercentExceeds100(uint256 projectId, uint256 rulesetId, uint256 groupId, uint256 percentTotal);
24
+ error JBSplits_ZeroSplitPercent(uint256 projectId, uint256 rulesetId, uint256 groupId, uint256 splitIndex);
25
25
 
26
26
  //*********************************************************************//
27
27
  // ------------------------- public constants ------------------------ //
@@ -180,7 +180,7 @@ contract JBSplits is JBControlled, IJBSplits {
180
180
  block.timestamp < currentSplits[i].lockedUntil
181
181
  && !_includesLockedSplits({splits: splits, lockedSplit: currentSplits[i]})
182
182
  ) {
183
- revert JBSplits_PreviousLockedSplitsNotIncluded(projectId, rulesetId);
183
+ revert JBSplits_PreviousLockedSplitsNotIncluded({projectId: projectId, rulesetId: rulesetId});
184
184
  }
185
185
  unchecked {
186
186
  ++i;
@@ -198,13 +198,21 @@ contract JBSplits is JBControlled, IJBSplits {
198
198
  JBSplit memory split = splits[i];
199
199
 
200
200
  // The percent should be greater than 0.
201
- if (split.percent == 0) revert JBSplits_ZeroSplitPercent();
201
+ if (split.percent == 0) {
202
+ revert JBSplits_ZeroSplitPercent({
203
+ projectId: projectId, rulesetId: rulesetId, groupId: groupId, splitIndex: i
204
+ });
205
+ }
202
206
 
203
207
  // Add to the `percent` total.
204
208
  percentTotal += split.percent;
205
209
 
206
210
  // Ensure the total does not exceed 100%.
207
- if (percentTotal > JBConstants.SPLITS_TOTAL_PERCENT) revert JBSplits_TotalPercentExceeds100();
211
+ if (percentTotal > JBConstants.SPLITS_TOTAL_PERCENT) {
212
+ revert JBSplits_TotalPercentExceeds100({
213
+ projectId: projectId, rulesetId: rulesetId, groupId: groupId, percentTotal: percentTotal
214
+ });
215
+ }
208
216
 
209
217
  uint256 packedSplitParts1;
210
218
 
@@ -40,8 +40,10 @@ contract JBTerminalStore is IJBTerminalStore {
40
40
  //*********************************************************************//
41
41
 
42
42
  error JBTerminalStore_AccountingContextAlreadySet(address token);
43
- error JBTerminalStore_AccountingContextDecimalsMismatch();
44
- error JBTerminalStore_AddingAccountingContextNotAllowed();
43
+ error JBTerminalStore_AccountingContextDecimalsMismatch(
44
+ address token, uint256 providedDecimals, uint256 expectedDecimals
45
+ );
46
+ error JBTerminalStore_AddingAccountingContextNotAllowed(uint256 projectId, uint256 rulesetId, address terminal);
45
47
  error JBTerminalStore_InadequateControllerAllowance(uint256 amount, uint256 allowance);
46
48
  error JBTerminalStore_InadequateControllerPayoutLimit(uint256 amount, uint256 limit);
47
49
  error JBTerminalStore_InadequateTerminalStoreBalance(uint256 amount, uint256 balance);
@@ -49,10 +51,10 @@ contract JBTerminalStore is IJBTerminalStore {
49
51
  error JBTerminalStore_InvalidAmountToForwardHook(uint256 amount, uint256 paidAmount);
50
52
  error JBTerminalStore_NoopHookSpecHasAmount(uint256 amount);
51
53
  error JBTerminalStore_RulesetNotFound(uint256 projectId);
52
- error JBTerminalStore_RulesetPaymentPaused();
53
- error JBTerminalStore_TerminalMigrationNotAllowed();
54
+ error JBTerminalStore_RulesetPaymentPaused(uint256 projectId, uint256 rulesetId);
55
+ error JBTerminalStore_TerminalMigrationNotAllowed(uint256 projectId, uint256 rulesetId);
54
56
  error JBTerminalStore_Uint224Overflow(uint256 value);
55
- error JBTerminalStore_ZeroAccountingContextCurrency();
57
+ error JBTerminalStore_ZeroAccountingContextCurrency(address token);
56
58
 
57
59
  //*********************************************************************//
58
60
  // -------------------------- internal constants --------------------- //
@@ -176,7 +178,9 @@ contract JBTerminalStore is IJBTerminalStore {
176
178
 
177
179
  // Make sure that if there's a ruleset, it allows adding accounting contexts.
178
180
  if (ruleset.id != 0 && !ruleset.allowAddAccountingContext()) {
179
- revert JBTerminalStore_AddingAccountingContextNotAllowed();
181
+ revert JBTerminalStore_AddingAccountingContextNotAllowed({
182
+ projectId: projectId, rulesetId: ruleset.id, terminal: msg.sender
183
+ });
180
184
  }
181
185
 
182
186
  // Record each accounting context.
@@ -185,20 +189,22 @@ contract JBTerminalStore is IJBTerminalStore {
185
189
 
186
190
  // Make sure the token accounting context isn't already set.
187
191
  if (_accountingContextForTokenOf[msg.sender][projectId][context.token].token != address(0)) {
188
- revert JBTerminalStore_AccountingContextAlreadySet(context.token);
192
+ revert JBTerminalStore_AccountingContextAlreadySet({token: context.token});
189
193
  }
190
194
 
191
195
  // Keep track of a flag indicating if we know the provided decimals are incorrect.
192
196
  bool knownInvalidDecimals;
197
+ uint256 expectedDecimals;
193
198
 
194
199
  // Check if the token is the native token and has the correct decimals.
195
200
  if (context.token == JBConstants.NATIVE_TOKEN && context.decimals != 18) {
196
201
  knownInvalidDecimals = true;
202
+ expectedDecimals = 18;
197
203
  } else if (context.token != JBConstants.NATIVE_TOKEN && context.token.code.length > 0) {
198
- // slither-disable-next-line calls-loop
199
204
  try IERC20Metadata(context.token).decimals() returns (uint8 decimals) {
200
205
  if (context.decimals != decimals) {
201
206
  knownInvalidDecimals = true;
207
+ expectedDecimals = decimals;
202
208
  }
203
209
  } catch {
204
210
  // The token didn't support `decimals`.
@@ -210,11 +216,13 @@ contract JBTerminalStore is IJBTerminalStore {
210
216
 
211
217
  // Make sure the decimals are correct.
212
218
  if (knownInvalidDecimals) {
213
- revert JBTerminalStore_AccountingContextDecimalsMismatch();
219
+ revert JBTerminalStore_AccountingContextDecimalsMismatch({
220
+ token: context.token, providedDecimals: context.decimals, expectedDecimals: expectedDecimals
221
+ });
214
222
  }
215
223
 
216
224
  // Make sure the currency is non-zero.
217
- if (context.currency == 0) revert JBTerminalStore_ZeroAccountingContextCurrency();
225
+ if (context.currency == 0) revert JBTerminalStore_ZeroAccountingContextCurrency({token: context.token});
218
226
 
219
227
  // Store the accounting context.
220
228
  _accountingContextForTokenOf[msg.sender][projectId][context.token] = context;
@@ -303,7 +311,7 @@ contract JBTerminalStore is IJBTerminalStore {
303
311
 
304
312
  // The amount being reclaimed must be within the project's balance.
305
313
  if (balanceDiff > currentBalance) {
306
- revert JBTerminalStore_InadequateTerminalStoreBalance(balanceDiff, currentBalance);
314
+ revert JBTerminalStore_InadequateTerminalStoreBalance({amount: balanceDiff, balance: currentBalance});
307
315
  }
308
316
 
309
317
  // Remove the reclaimed funds from the project's balance.
@@ -404,7 +412,7 @@ contract JBTerminalStore is IJBTerminalStore {
404
412
 
405
413
  // The amount being paid out must be available.
406
414
  if (amountPaidOut > currentBalance) {
407
- revert JBTerminalStore_InadequateTerminalStoreBalance(amountPaidOut, currentBalance);
415
+ revert JBTerminalStore_InadequateTerminalStoreBalance({amount: amountPaidOut, balance: currentBalance});
408
416
  }
409
417
 
410
418
  // The new total amount which has been paid out during this ruleset.
@@ -420,7 +428,7 @@ contract JBTerminalStore is IJBTerminalStore {
420
428
 
421
429
  // Make sure the new used amount is within the payout limit.
422
430
  if (newUsedPayoutLimitOf > payoutLimit || payoutLimit == 0) {
423
- revert JBTerminalStore_InadequateControllerPayoutLimit(newUsedPayoutLimitOf, payoutLimit);
431
+ revert JBTerminalStore_InadequateControllerPayoutLimit({amount: newUsedPayoutLimitOf, limit: payoutLimit});
424
432
  }
425
433
 
426
434
  // Removed the paid out funds from the project's token balance.
@@ -444,7 +452,7 @@ contract JBTerminalStore is IJBTerminalStore {
444
452
 
445
453
  // Terminal migration must be allowed.
446
454
  if (!ruleset.allowTerminalMigration()) {
447
- revert JBTerminalStore_TerminalMigrationNotAllowed();
455
+ revert JBTerminalStore_TerminalMigrationNotAllowed({projectId: projectId, rulesetId: ruleset.id});
448
456
  }
449
457
 
450
458
  // Return the current balance, which is the amount being migrated.
@@ -525,7 +533,9 @@ contract JBTerminalStore is IJBTerminalStore {
525
533
 
526
534
  // Make sure the new used amount is within the allowance.
527
535
  if (newUsedSurplusAllowanceOf > surplusAllowance || surplusAllowance == 0) {
528
- revert JBTerminalStore_InadequateControllerAllowance(newUsedSurplusAllowanceOf, surplusAllowance);
536
+ revert JBTerminalStore_InadequateControllerAllowance({
537
+ amount: newUsedSurplusAllowanceOf, allowance: surplusAllowance
538
+ });
529
539
  }
530
540
 
531
541
  // Cache the balance slot to avoid redundant storage reads.
@@ -837,57 +847,6 @@ contract JBTerminalStore is IJBTerminalStore {
837
847
  // -------------------------- internal views ------------------------- //
838
848
  //*********************************************************************//
839
849
 
840
- /// @notice Computes the surplus relevant for a cash out (total or local, depending on ruleset flag).
841
- /// @dev When `useTotalSurplusForCashOuts` is enabled, surplus is aggregated from ALL registered terminals without
842
- /// validation. Projects MUST only register trusted terminals — an untrusted terminal can over-report surplus and
843
- /// cause the executing terminal to overpay cash-outs.
844
- /// @param terminal The terminal recording the cash out.
845
- /// @param projectId The ID of the project to cash out from.
846
- /// @param tokenToReclaim The token to reclaim.
847
- /// @param ruleset The ruleset during the cash out.
848
- /// @return The surplus amount in the token's native decimals and currency.
849
- function _cashOutSurplusOf(
850
- address terminal,
851
- uint256 projectId,
852
- address tokenToReclaim,
853
- JBRuleset memory ruleset
854
- )
855
- internal
856
- view
857
- returns (uint256)
858
- {
859
- // Look up the accounting context (decimals, currency) for the token being reclaimed at this terminal.
860
- JBAccountingContext memory accountingContext = _accountingContextForTokenOf[terminal][projectId][tokenToReclaim];
861
-
862
- // If the ruleset uses total surplus, aggregate across ALL terminals and ALL tokens.
863
- if (ruleset.useTotalSurplusForCashOuts()) {
864
- return JBSurplus.currentSurplusOf({
865
- projectId: projectId,
866
- // Get every terminal the project has registered.
867
- terminals: DIRECTORY.terminalsOf(projectId),
868
- // Empty tokens array = include all tokens at each terminal.
869
- tokens: new address[](0),
870
- // Express the result in the reclaimed token's decimals and currency.
871
- decimals: accountingContext.decimals,
872
- currency: accountingContext.currency
873
- });
874
- }
875
-
876
- // Otherwise, only account for the specific token's surplus at this terminal.
877
- JBAccountingContext[] memory singleContext = new JBAccountingContext[](1);
878
- singleContext[0] = accountingContext;
879
-
880
- // Compute surplus from only this terminal using only the reclaimed token's balance.
881
- return _surplusFrom({
882
- terminal: terminal,
883
- projectId: projectId,
884
- accountingContexts: singleContext,
885
- ruleset: ruleset,
886
- targetDecimals: accountingContext.decimals,
887
- targetCurrency: accountingContext.currency
888
- });
889
- }
890
-
891
850
  /// @notice Calls the data hook, validates noop specifications, and computes the bonding curve reclaim amount.
892
851
  /// @dev Extracted from `_computeCashOutFrom` to keep it under the EVM stack depth limit (16 slots).
893
852
  /// @param ruleset The current ruleset (used to resolve the data hook address).
@@ -915,7 +874,7 @@ contract JBTerminalStore is IJBTerminalStore {
915
874
  // Noop specifications are informational only, so they can't also request forwarded funds.
916
875
  for (uint256 i; i < hookSpecifications.length;) {
917
876
  if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
918
- revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
877
+ revert JBTerminalStore_NoopHookSpecHasAmount({amount: hookSpecifications[i].amount});
919
878
  }
920
879
  unchecked {
921
880
  ++i;
@@ -969,21 +928,25 @@ contract JBTerminalStore is IJBTerminalStore {
969
928
  // Get a reference to the project's current ruleset.
970
929
  ruleset = RULESETS.currentOf(projectId);
971
930
 
972
- // Get the project's current surplus for the token being reclaimed.
973
- uint256 surplus = _cashOutSurplusOf({
974
- terminal: terminal, projectId: projectId, tokenToReclaim: tokenToReclaim, ruleset: ruleset
975
- });
976
-
977
931
  // Get the accounting context for the token being reclaimed.
978
932
  JBAccountingContext memory accountingContext = _accountingContextForTokenOf[terminal][projectId][tokenToReclaim];
979
933
 
934
+ // Get the project's current surplus across ALL terminals and ALL tokens.
935
+ uint256 surplus = JBSurplus.currentSurplusOf({
936
+ projectId: projectId,
937
+ terminals: DIRECTORY.terminalsOf(projectId),
938
+ tokens: new address[](0),
939
+ decimals: accountingContext.decimals,
940
+ currency: accountingContext.currency
941
+ });
942
+
980
943
  // Get the total number of outstanding project tokens.
981
944
  uint256 effectiveTotalSupply =
982
945
  IJBController(address(DIRECTORY.controllerOf(projectId))).totalTokenSupplyWithReservedTokensOf(projectId);
983
946
 
984
947
  // Can't cash out more tokens than are in the supply.
985
948
  if (cashOutCount > effectiveTotalSupply) {
986
- revert JBTerminalStore_InsufficientTokens(cashOutCount, effectiveTotalSupply);
949
+ revert JBTerminalStore_InsufficientTokens({count: cashOutCount, totalSupply: effectiveTotalSupply});
987
950
  }
988
951
 
989
952
  // SECURITY NOTE: The data hook has absolute control over cash-out pricing.
@@ -991,7 +954,7 @@ contract JBTerminalStore is IJBTerminalStore {
991
954
  // completely overriding the terminal's bonding curve math. For example, setting
992
955
  // effectiveTotalSupply = surplus makes reclaimAmount = effectiveCashOutCount, bypassing the curve.
993
956
  // The terminal still burns the caller-supplied cashOutCount after pricing completes.
994
- // Project owners MUST audit their data hooks with the same rigor as the terminal.
957
+ // Project owners must review their data hooks with the same rigor as the terminal.
995
958
 
996
959
  // If the ruleset has a data hook which is enabled for cash outs, use it to derive a claim amount and memo.
997
960
  if (ruleset.useDataHookForCashOut() && ruleset.dataHook() != address(0)) {
@@ -1009,7 +972,7 @@ contract JBTerminalStore is IJBTerminalStore {
1009
972
  decimals: accountingContext.decimals,
1010
973
  currency: accountingContext.currency
1011
974
  });
1012
- context.useTotalSurplus = ruleset.useTotalSurplusForCashOuts();
975
+ context.scopeCashOutsToLocalBalances = ruleset.scopeCashOutsToLocalBalances();
1013
976
  context.cashOutTaxRate = ruleset.cashOutTaxRate();
1014
977
  context.beneficiaryIsFeeless = beneficiaryIsFeeless;
1015
978
  context.metadata = metadata;
@@ -1067,7 +1030,9 @@ contract JBTerminalStore is IJBTerminalStore {
1067
1030
  if (ruleset.cycleNumber == 0) revert JBTerminalStore_RulesetNotFound(projectId);
1068
1031
 
1069
1032
  // The ruleset must not have payments paused.
1070
- if (ruleset.pausePay()) revert JBTerminalStore_RulesetPaymentPaused();
1033
+ if (ruleset.pausePay()) {
1034
+ revert JBTerminalStore_RulesetPaymentPaused({projectId: projectId, rulesetId: ruleset.id});
1035
+ }
1071
1036
 
1072
1037
  // The weight according to which new tokens are to be minted, as a fixed point number with 18 decimals.
1073
1038
  uint256 weight;
@@ -1075,7 +1040,7 @@ contract JBTerminalStore is IJBTerminalStore {
1075
1040
  // SECURITY NOTE: The data hook has absolute control over payment token minting.
1076
1041
  // It can return an arbitrary weight (overriding the ruleset's weight) and hook specifications
1077
1042
  // that divert payment funds to external hooks before they reach the project's balance.
1078
- // Project owners MUST audit their data hooks with the same rigor as the terminal.
1043
+ // Project owners must review their data hooks with the same rigor as the terminal.
1079
1044
 
1080
1045
  // If the ruleset has a data hook enabled for payments, use it to derive a weight and memo.
1081
1046
  if (ruleset.useDataHookForPay() && ruleset.dataHook() != address(0)) {
@@ -1105,7 +1070,7 @@ contract JBTerminalStore is IJBTerminalStore {
1105
1070
  // Ensure that the specifications have valid amounts.
1106
1071
  for (uint256 i; i < hookSpecifications.length;) {
1107
1072
  if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
1108
- revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
1073
+ revert JBTerminalStore_NoopHookSpecHasAmount({amount: hookSpecifications[i].amount});
1109
1074
  }
1110
1075
 
1111
1076
  uint256 specifiedAmount = hookSpecifications[i].amount;
@@ -1113,7 +1078,9 @@ contract JBTerminalStore is IJBTerminalStore {
1113
1078
  // Can't send more to hook than was paid.
1114
1079
  if (specifiedAmount != 0) {
1115
1080
  if (specifiedAmount > balanceDiff) {
1116
- revert JBTerminalStore_InvalidAmountToForwardHook(specifiedAmount, balanceDiff);
1081
+ revert JBTerminalStore_InvalidAmountToForwardHook({
1082
+ amount: specifiedAmount, paidAmount: balanceDiff
1083
+ });
1117
1084
  }
1118
1085
 
1119
1086
  // Decrement the total amount being added to the local balance.
@@ -1319,7 +1286,6 @@ contract JBTerminalStore is IJBTerminalStore {
1319
1286
  });
1320
1287
 
1321
1288
  // Add up all the balances.
1322
- // slither-disable-next-line calls-loop
1323
1289
  surplus = (surplus == 0 || accountingContext.currency == targetCurrency)
1324
1290
  ? surplus
1325
1291
  : mulDiv({
@@ -1335,7 +1301,6 @@ contract JBTerminalStore is IJBTerminalStore {
1335
1301
  });
1336
1302
 
1337
1303
  // Get a reference to the payout limit during the ruleset for the token.
1338
- // slither-disable-next-line calls-loop
1339
1304
  JBCurrencyAmount[] memory payoutLimits = IJBController(address(DIRECTORY.controllerOf(projectId)))
1340
1305
  .FUND_ACCESS_LIMITS()
1341
1306
  .payoutLimitsOf({
@@ -1375,7 +1340,6 @@ contract JBTerminalStore is IJBTerminalStore {
1375
1340
 
1376
1341
  // Convert the `payoutLimit`'s amount to be in terms of the provided currency.
1377
1342
  if (payoutLimit.amount != 0 && payoutLimit.currency != targetCurrency) {
1378
- // slither-disable-next-line calls-loop
1379
1343
  uint256 converted = mulDiv({
1380
1344
  x: payoutLimit.amount,
1381
1345
  y: 10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the