@bananapus/core-v6 0.0.30 → 0.0.32
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/ADMINISTRATION.md +43 -13
- package/ARCHITECTURE.md +62 -137
- package/AUDIT_INSTRUCTIONS.md +149 -428
- package/CHANGELOG.md +73 -0
- package/README.md +90 -201
- package/RISKS.md +27 -12
- package/SKILLS.md +31 -441
- package/STYLE_GUIDE.md +52 -19
- package/USER_JOURNEYS.md +76 -627
- package/package.json +1 -2
- package/references/entrypoints.md +160 -0
- package/references/types-errors-events.md +297 -0
- package/script/Deploy.s.sol +7 -2
- package/script/DeployPeriphery.s.sol +51 -4
- package/src/JBController.sol +45 -17
- package/src/JBDirectory.sol +26 -13
- package/src/JBFundAccessLimits.sol +28 -7
- package/src/JBMultiTerminal.sol +180 -86
- package/src/JBPermissions.sol +17 -17
- package/src/JBRulesets.sol +82 -23
- package/src/JBSplits.sol +31 -12
- package/src/JBTerminalStore.sol +137 -53
- package/src/JBTokens.sol +5 -2
- package/src/abstract/JBControlled.sol +10 -3
- package/src/abstract/JBPermissioned.sol +1 -1
- package/src/interfaces/IJBRulesetDataHook.sol +5 -4
- package/src/libraries/JBCashOuts.sol +1 -1
- package/src/libraries/JBConstants.sol +1 -1
- package/src/libraries/JBCurrencyIds.sol +1 -1
- package/src/libraries/JBFees.sol +1 -1
- package/src/libraries/JBFixedPointNumber.sol +1 -1
- package/src/libraries/JBMetadataResolver.sol +5 -2
- package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
- package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
- package/src/libraries/JBSplitGroupIds.sol +1 -1
- package/src/libraries/JBSurplus.sol +5 -2
- package/src/structs/JBSplit.sol +4 -1
- package/test/TestForwardedTokenConsumption.sol +419 -0
- package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
- package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
- package/CHANGE_LOG.md +0 -479
package/src/JBRulesets.sol
CHANGED
|
@@ -355,7 +355,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
355
355
|
/// @dev If a current ruleset of the project is not found, returns an empty ruleset with all properties set to 0.
|
|
356
356
|
/// @dev The first cycle returns the stored ruleset directly (cycleNumber=1, original weight). Subsequent cycles
|
|
357
357
|
/// simulate cycling with weight decay via `_simulateCycledRulesetBasedOn`. Payout limits reset each cycle because
|
|
358
|
-
/// the
|
|
358
|
+
/// they are keyed by `cycleNumber`, but the simulated cycle keeps the same `rulesetId` / config id as the stored
|
|
359
|
+
/// ruleset it is based on.
|
|
359
360
|
/// @param projectId The ID of the project to get the current ruleset of.
|
|
360
361
|
/// @return ruleset The project's current ruleset.
|
|
361
362
|
function currentOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
|
|
@@ -671,17 +672,19 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
671
672
|
revert JBRulesets_WeightCacheRequired(projectId);
|
|
672
673
|
}
|
|
673
674
|
|
|
674
|
-
|
|
675
|
-
|
|
675
|
+
// Cache the cut factor and max percent to avoid recomputing each iteration.
|
|
676
|
+
uint256 cutFactor = JBConstants.MAX_WEIGHT_CUT_PERCENT - baseRulesetWeightCutPercent;
|
|
677
|
+
uint256 maxPercent = JBConstants.MAX_WEIGHT_CUT_PERCENT;
|
|
678
|
+
|
|
679
|
+
for (uint256 i; i < weightCutMultiple;) {
|
|
676
680
|
// Base the new weight on the specified ruleset's weight.
|
|
677
|
-
weight = mulDiv(
|
|
678
|
-
weight,
|
|
679
|
-
JBConstants.MAX_WEIGHT_CUT_PERCENT - baseRulesetWeightCutPercent,
|
|
680
|
-
JBConstants.MAX_WEIGHT_CUT_PERCENT
|
|
681
|
-
);
|
|
681
|
+
weight = mulDiv(weight, cutFactor, maxPercent);
|
|
682
682
|
|
|
683
683
|
// The calculation doesn't need to continue if the weight is 0.
|
|
684
684
|
if (weight == 0) break;
|
|
685
|
+
unchecked {
|
|
686
|
+
++i;
|
|
687
|
+
}
|
|
685
688
|
}
|
|
686
689
|
}
|
|
687
690
|
|
|
@@ -720,6 +723,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
720
723
|
}
|
|
721
724
|
|
|
722
725
|
// Get a reference to the latest ruleset's struct.
|
|
726
|
+
// Note: full metadata is loaded because `_approvalStatusOf` forwards the struct to external approval hooks.
|
|
723
727
|
JBRuleset memory baseRuleset = _getStructFor({projectId: projectId, rulesetId: latestId});
|
|
724
728
|
|
|
725
729
|
// Get a reference to the approval status.
|
|
@@ -742,7 +746,9 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
742
746
|
&& approvalStatus != JBApprovalStatus.ApprovalExpected
|
|
743
747
|
&& approvalStatus != JBApprovalStatus.Empty)
|
|
744
748
|
) {
|
|
745
|
-
|
|
749
|
+
// Metadata not needed — the fallback ruleset is only used for intrinsic fields (start, basedOnId, etc.)
|
|
750
|
+
// and not forwarded to any external approval hook.
|
|
751
|
+
baseRuleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: baseRuleset.basedOnId});
|
|
746
752
|
}
|
|
747
753
|
|
|
748
754
|
// Make sure the ruleset starts after the base ruleset.
|
|
@@ -895,11 +901,14 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
895
901
|
// slither-disable-next-line incorrect-equality
|
|
896
902
|
if (ruleset.basedOnId == 0) return JBApprovalStatus.Empty;
|
|
897
903
|
|
|
898
|
-
//
|
|
899
|
-
|
|
904
|
+
// Read only the packed user properties to extract the approval hook address,
|
|
905
|
+
// avoiding the cost of loading the full parent ruleset struct.
|
|
906
|
+
uint256 packedUserProperties = _packedUserPropertiesOf[projectId][ruleset.basedOnId];
|
|
907
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
908
|
+
IJBRulesetApprovalHook approvalHook = IJBRulesetApprovalHook(address(uint160(packedUserProperties)));
|
|
900
909
|
|
|
901
910
|
// If there is no approval hook, it's considered empty.
|
|
902
|
-
if (
|
|
911
|
+
if (approvalHook == IJBRulesetApprovalHook(address(0))) {
|
|
903
912
|
return JBApprovalStatus.Empty;
|
|
904
913
|
}
|
|
905
914
|
|
|
@@ -908,9 +917,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
908
917
|
// Note: A malicious hook that consumes all gas (e.g. infinite loop) could still DoS via gas exhaustion.
|
|
909
918
|
// This is accepted risk since the project owner chose their own approval hook.
|
|
910
919
|
// slither-disable-next-line calls-loop
|
|
911
|
-
try
|
|
912
|
-
JBApprovalStatus status
|
|
913
|
-
) {
|
|
920
|
+
try approvalHook.approvalStatusOf({projectId: projectId, ruleset: ruleset}) returns (JBApprovalStatus status) {
|
|
914
921
|
return status;
|
|
915
922
|
} catch {
|
|
916
923
|
return JBApprovalStatus.Failed;
|
|
@@ -927,8 +934,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
927
934
|
// Get a reference to the project's latest ruleset.
|
|
928
935
|
uint256 rulesetId = latestRulesetIdOf[projectId];
|
|
929
936
|
|
|
930
|
-
// Get the struct for the latest ruleset.
|
|
931
|
-
JBRuleset memory ruleset =
|
|
937
|
+
// Get the struct for the latest ruleset (metadata not needed — only traversal fields are checked).
|
|
938
|
+
JBRuleset memory ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: rulesetId});
|
|
932
939
|
|
|
933
940
|
// Loop through all most recently queued rulesets until an approvable one is found, or we've proven one can't
|
|
934
941
|
// exist.
|
|
@@ -944,12 +951,64 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
944
951
|
return ruleset.id;
|
|
945
952
|
}
|
|
946
953
|
|
|
947
|
-
ruleset =
|
|
954
|
+
ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: ruleset.basedOnId});
|
|
948
955
|
} while (ruleset.cycleNumber != 0);
|
|
949
956
|
|
|
950
957
|
return 0;
|
|
951
958
|
}
|
|
952
959
|
|
|
960
|
+
/// @notice Unpack a ruleset's intrinsic and user properties without loading metadata.
|
|
961
|
+
/// @dev Saves one cold SLOAD (~2,100 gas) compared to `_getStructFor`. Use this for linked-list traversal where
|
|
962
|
+
/// only `id`, `start`, `duration`, `basedOnId`, `cycleNumber`, `weight`, `weightCutPercent`, and `approvalHook`
|
|
963
|
+
/// are needed.
|
|
964
|
+
/// @param projectId The ID of the project the ruleset belongs to.
|
|
965
|
+
/// @param rulesetId The ID of the ruleset to get the struct for.
|
|
966
|
+
/// @return ruleset A ruleset struct with `metadata` set to 0.
|
|
967
|
+
function _getStructWithoutMetadataFor(
|
|
968
|
+
uint256 projectId,
|
|
969
|
+
uint256 rulesetId
|
|
970
|
+
)
|
|
971
|
+
internal
|
|
972
|
+
view
|
|
973
|
+
returns (JBRuleset memory ruleset)
|
|
974
|
+
{
|
|
975
|
+
// Return an empty ruleset if the specified `rulesetId` is 0.
|
|
976
|
+
// slither-disable-next-line incorrect-equality
|
|
977
|
+
if (rulesetId == 0) return ruleset;
|
|
978
|
+
|
|
979
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
980
|
+
ruleset.id = uint48(rulesetId);
|
|
981
|
+
|
|
982
|
+
uint256 packedIntrinsicProperties = _packedIntrinsicPropertiesOf[projectId][rulesetId];
|
|
983
|
+
|
|
984
|
+
// `weight` in bits 0-111 bits.
|
|
985
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
986
|
+
ruleset.weight = uint112(packedIntrinsicProperties);
|
|
987
|
+
// `basedOnId` in bits 112-159 bits.
|
|
988
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
989
|
+
ruleset.basedOnId = uint48(packedIntrinsicProperties >> 112);
|
|
990
|
+
// `start` in bits 160-207 bits.
|
|
991
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
992
|
+
ruleset.start = uint48(packedIntrinsicProperties >> 160);
|
|
993
|
+
// `cycleNumber` in bits 208-255 bits.
|
|
994
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
995
|
+
ruleset.cycleNumber = uint48(packedIntrinsicProperties >> 208);
|
|
996
|
+
|
|
997
|
+
uint256 packedUserProperties = _packedUserPropertiesOf[projectId][rulesetId];
|
|
998
|
+
|
|
999
|
+
// approval hook in bits 0-159 bits.
|
|
1000
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1001
|
+
ruleset.approvalHook = IJBRulesetApprovalHook(address(uint160(packedUserProperties)));
|
|
1002
|
+
// `duration` in bits 160-191 bits.
|
|
1003
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1004
|
+
ruleset.duration = uint32(packedUserProperties >> 160);
|
|
1005
|
+
// weight cut percent in bits 192-223 bits.
|
|
1006
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1007
|
+
ruleset.weightCutPercent = uint32(packedUserProperties >> 192);
|
|
1008
|
+
|
|
1009
|
+
// metadata intentionally not loaded — saves one cold SLOAD (~2,100 gas).
|
|
1010
|
+
}
|
|
1011
|
+
|
|
953
1012
|
/// @notice Unpack a ruleset's packed stored values into an easy-to-work-with ruleset struct.
|
|
954
1013
|
/// @param projectId The ID of the project the ruleset belongs to.
|
|
955
1014
|
/// @param rulesetId The ID of the ruleset to get the full struct for.
|
|
@@ -1066,8 +1125,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
1066
1125
|
// Get a reference to the ID of the project's latest ruleset.
|
|
1067
1126
|
rulesetId = latestRulesetIdOf[projectId];
|
|
1068
1127
|
|
|
1069
|
-
// Get the struct for the latest ruleset.
|
|
1070
|
-
JBRuleset memory ruleset =
|
|
1128
|
+
// Get the struct for the latest ruleset (metadata not needed — only traversal fields are checked).
|
|
1129
|
+
JBRuleset memory ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: rulesetId});
|
|
1071
1130
|
|
|
1072
1131
|
// There is no upcoming ruleset if the latest ruleset has already started.
|
|
1073
1132
|
// slither-disable-next-line incorrect-equality
|
|
@@ -1085,7 +1144,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
1085
1144
|
|
|
1086
1145
|
// Find the base ruleset that is not still queued.
|
|
1087
1146
|
while (true) {
|
|
1088
|
-
baseRuleset =
|
|
1147
|
+
baseRuleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: basedOnId});
|
|
1089
1148
|
|
|
1090
1149
|
// If the base ruleset starts in the future,
|
|
1091
1150
|
if (block.timestamp < baseRuleset.start) {
|
|
@@ -1099,8 +1158,8 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
1099
1158
|
}
|
|
1100
1159
|
}
|
|
1101
1160
|
|
|
1102
|
-
// Get the ruleset struct for the ID found.
|
|
1103
|
-
ruleset =
|
|
1161
|
+
// Get the ruleset struct for the ID found (metadata not needed — only `start` and `duration` are checked).
|
|
1162
|
+
ruleset = _getStructWithoutMetadataFor({projectId: projectId, rulesetId: rulesetId});
|
|
1104
1163
|
|
|
1105
1164
|
// If the latest ruleset doesn't start until after another base ruleset return 0.
|
|
1106
1165
|
if (baseRuleset.duration != 0 && block.timestamp < ruleset.start - baseRuleset.duration) {
|
package/src/JBSplits.sol
CHANGED
|
@@ -97,15 +97,13 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
97
97
|
// Cache whether the controller check has already passed to avoid repeated external calls.
|
|
98
98
|
bool controllerChecked;
|
|
99
99
|
|
|
100
|
-
// Set each grouped splits.
|
|
101
|
-
for (uint256 i; i < splitGroups.length;
|
|
102
|
-
// Get a reference to the grouped split being iterated on.
|
|
103
|
-
JBSplitGroup memory splitGroup = splitGroups[i];
|
|
104
|
-
|
|
100
|
+
// Set each grouped splits. Access calldata directly to avoid copying each split group to memory.
|
|
101
|
+
for (uint256 i; i < splitGroups.length;) {
|
|
105
102
|
// Self-auth: lower 160 bits must match msg.sender AND upper 96 bits must be non-zero.
|
|
106
103
|
// GroupIds with zero upper bits are reserved for protocol use (e.g. terminal payout groups)
|
|
107
104
|
// and always require controller authorization to prevent token contracts from hijacking payouts.
|
|
108
|
-
bool isSelfManaged =
|
|
105
|
+
bool isSelfManaged =
|
|
106
|
+
splitGroups[i].groupId >> 160 != 0 && address(uint160(splitGroups[i].groupId)) == msg.sender;
|
|
109
107
|
|
|
110
108
|
if (!isSelfManaged && !controllerChecked) {
|
|
111
109
|
_onlyControllerOf(projectId);
|
|
@@ -114,8 +112,14 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
114
112
|
|
|
115
113
|
// Set the splits for the group.
|
|
116
114
|
_setSplitsOf({
|
|
117
|
-
projectId: projectId,
|
|
115
|
+
projectId: projectId,
|
|
116
|
+
rulesetId: rulesetId,
|
|
117
|
+
groupId: splitGroups[i].groupId,
|
|
118
|
+
splits: splitGroups[i].splits
|
|
118
119
|
});
|
|
120
|
+
unchecked {
|
|
121
|
+
++i;
|
|
122
|
+
}
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
|
|
@@ -168,7 +172,7 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
168
172
|
uint256 numberOfCurrentSplits = currentSplits.length;
|
|
169
173
|
|
|
170
174
|
// Check to see if all locked splits are included in the array of splits which is being set.
|
|
171
|
-
for (uint256 i; i < numberOfCurrentSplits;
|
|
175
|
+
for (uint256 i; i < numberOfCurrentSplits;) {
|
|
172
176
|
// If not locked, continue.
|
|
173
177
|
if (
|
|
174
178
|
block.timestamp < currentSplits[i].lockedUntil
|
|
@@ -176,6 +180,9 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
176
180
|
) {
|
|
177
181
|
revert JBSplits_PreviousLockedSplitsNotIncluded(projectId, rulesetId);
|
|
178
182
|
}
|
|
183
|
+
unchecked {
|
|
184
|
+
++i;
|
|
185
|
+
}
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
// Add up all the `percent`s to make sure their total is under 100%.
|
|
@@ -184,7 +191,7 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
184
191
|
// Keep a reference to the number of splits to set.
|
|
185
192
|
uint256 numberOfSplits = splits.length;
|
|
186
193
|
|
|
187
|
-
for (uint256 i; i < numberOfSplits;
|
|
194
|
+
for (uint256 i; i < numberOfSplits;) {
|
|
188
195
|
// Set the split being iterated on.
|
|
189
196
|
JBSplit memory split = splits[i];
|
|
190
197
|
|
|
@@ -228,6 +235,9 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
228
235
|
emit SetSplit({
|
|
229
236
|
projectId: projectId, rulesetId: rulesetId, groupId: groupId, split: split, caller: msg.sender
|
|
230
237
|
});
|
|
238
|
+
unchecked {
|
|
239
|
+
++i;
|
|
240
|
+
}
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
// Store the number of splits for the project, ruleset, and group.
|
|
@@ -235,9 +245,12 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
235
245
|
|
|
236
246
|
// Clean up stale storage slots if the new split count is less than the previous count.
|
|
237
247
|
// This zeroes out leftover packed data to reclaim gas via storage refunds.
|
|
238
|
-
for (uint256 i = numberOfSplits; i < numberOfCurrentSplits;
|
|
248
|
+
for (uint256 i = numberOfSplits; i < numberOfCurrentSplits;) {
|
|
239
249
|
delete _packedSplitParts1Of[projectId][rulesetId][groupId][i];
|
|
240
250
|
delete _packedSplitParts2Of[projectId][rulesetId][groupId][i];
|
|
251
|
+
unchecked {
|
|
252
|
+
++i;
|
|
253
|
+
}
|
|
241
254
|
}
|
|
242
255
|
}
|
|
243
256
|
|
|
@@ -267,7 +280,7 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
267
280
|
JBSplit[] memory splits = new JBSplit[](splitCount);
|
|
268
281
|
|
|
269
282
|
// Loop through each split and unpack the values into structs.
|
|
270
|
-
for (uint256 i; i < splitCount;
|
|
283
|
+
for (uint256 i; i < splitCount;) {
|
|
271
284
|
// Get a reference to the first part of the split's packed data.
|
|
272
285
|
uint256 packedSplitPart1 = _packedSplitParts1Of[projectId][rulesetId][groupId][i];
|
|
273
286
|
|
|
@@ -301,6 +314,9 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
301
314
|
|
|
302
315
|
// Add the split to the value being returned.
|
|
303
316
|
splits[i] = split;
|
|
317
|
+
unchecked {
|
|
318
|
+
++i;
|
|
319
|
+
}
|
|
304
320
|
}
|
|
305
321
|
|
|
306
322
|
return splits;
|
|
@@ -314,7 +330,7 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
314
330
|
// Keep a reference to the number of splits.
|
|
315
331
|
uint256 numberOfSplits = splits.length;
|
|
316
332
|
|
|
317
|
-
for (uint256 i; i < numberOfSplits;
|
|
333
|
+
for (uint256 i; i < numberOfSplits;) {
|
|
318
334
|
// Set the split being iterated on.
|
|
319
335
|
JBSplit memory split = splits[i];
|
|
320
336
|
|
|
@@ -326,6 +342,9 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
326
342
|
&& split.preferAddToBalance == lockedSplit.preferAddToBalance
|
|
327
343
|
&& split.lockedUntil >= lockedSplit.lockedUntil
|
|
328
344
|
) return true;
|
|
345
|
+
unchecked {
|
|
346
|
+
++i;
|
|
347
|
+
}
|
|
329
348
|
}
|
|
330
349
|
|
|
331
350
|
return false;
|