@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.
- package/CHANGELOG.md +1 -1
- package/README.md +3 -3
- package/package.json +2 -2
- package/references/entrypoints.md +3 -1
- package/references/types-errors-events.md +2 -3
- package/src/JBChainlinkV3PriceFeed.sol +14 -6
- package/src/JBChainlinkV3SequencerPriceFeed.sol +5 -6
- package/src/JBController.sol +84 -68
- package/src/JBDirectory.sol +4 -7
- package/src/JBERC20.sol +8 -7
- package/src/JBFeelessAddresses.sol +32 -13
- package/src/JBFundAccessLimits.sol +12 -4
- package/src/JBMultiTerminal.sol +39 -61
- package/src/JBPermissions.sol +6 -3
- package/src/JBPrices.sol +18 -12
- package/src/JBRulesets.sol +4 -25
- package/src/JBSplits.sol +13 -5
- package/src/JBTerminalStore.sol +46 -82
- package/src/JBTokens.sol +11 -13
- package/src/abstract/JBControlled.sol +1 -2
- package/src/interfaces/IJBController.sol +0 -6
- package/src/interfaces/IJBFeelessAddresses.sol +17 -7
- package/src/libraries/JBCashOuts.sol +6 -2
- package/src/libraries/JBCurrencyIds.sol +3 -0
- package/src/libraries/JBMetadataResolver.sol +20 -12
- package/src/libraries/JBPayoutSplitGroupLib.sol +8 -3
- package/src/libraries/JBRulesetMetadataResolver.sol +4 -4
- package/src/libraries/JBSplitGroupIds.sol +1 -0
- package/src/periphery/JBDeadline1Day.sol +1 -0
- package/src/periphery/JBDeadline3Days.sol +1 -0
- package/src/periphery/JBDeadline3Hours.sol +1 -0
- package/src/periphery/JBDeadline7Days.sol +1 -0
- package/src/structs/JBBeforeCashOutRecordedContext.sol +3 -2
- package/src/structs/JBRulesetMetadata.sol +3 -3
- 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)
|
|
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(
|
|
125
|
-
|
|
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(
|
|
136
|
-
|
|
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
|
//*********************************************************************//
|
package/src/JBRulesets.sol
CHANGED
|
@@ -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)
|
|
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)
|
|
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
|
|
package/src/JBTerminalStore.sol
CHANGED
|
@@ -40,8 +40,10 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
40
40
|
//*********************************************************************//
|
|
41
41
|
|
|
42
42
|
error JBTerminalStore_AccountingContextAlreadySet(address token);
|
|
43
|
-
error JBTerminalStore_AccountingContextDecimalsMismatch(
|
|
44
|
-
|
|
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(
|
|
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
|
|
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.
|
|
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())
|
|
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
|
|
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(
|
|
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
|