@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.
Files changed (49) hide show
  1. package/ADMINISTRATION.md +43 -13
  2. package/ARCHITECTURE.md +62 -137
  3. package/AUDIT_INSTRUCTIONS.md +149 -428
  4. package/CHANGELOG.md +73 -0
  5. package/README.md +90 -201
  6. package/RISKS.md +27 -12
  7. package/SKILLS.md +31 -441
  8. package/STYLE_GUIDE.md +52 -19
  9. package/USER_JOURNEYS.md +76 -627
  10. package/package.json +1 -2
  11. package/references/entrypoints.md +160 -0
  12. package/references/types-errors-events.md +297 -0
  13. package/script/Deploy.s.sol +7 -2
  14. package/script/DeployPeriphery.s.sol +51 -4
  15. package/src/JBController.sol +45 -17
  16. package/src/JBDirectory.sol +26 -13
  17. package/src/JBFundAccessLimits.sol +28 -7
  18. package/src/JBMultiTerminal.sol +180 -86
  19. package/src/JBPermissions.sol +17 -17
  20. package/src/JBRulesets.sol +82 -23
  21. package/src/JBSplits.sol +31 -12
  22. package/src/JBTerminalStore.sol +137 -53
  23. package/src/JBTokens.sol +5 -2
  24. package/src/abstract/JBControlled.sol +10 -3
  25. package/src/abstract/JBPermissioned.sol +1 -1
  26. package/src/interfaces/IJBRulesetDataHook.sol +5 -4
  27. package/src/libraries/JBCashOuts.sol +1 -1
  28. package/src/libraries/JBConstants.sol +1 -1
  29. package/src/libraries/JBCurrencyIds.sol +1 -1
  30. package/src/libraries/JBFees.sol +1 -1
  31. package/src/libraries/JBFixedPointNumber.sol +1 -1
  32. package/src/libraries/JBMetadataResolver.sol +5 -2
  33. package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
  34. package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
  35. package/src/libraries/JBSplitGroupIds.sol +1 -1
  36. package/src/libraries/JBSurplus.sol +5 -2
  37. package/src/structs/JBSplit.sol +4 -1
  38. package/test/TestForwardedTokenConsumption.sol +419 -0
  39. package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
  40. package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
  41. package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
  42. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
  43. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
  44. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
  45. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
  46. package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
  47. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
  48. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
  49. package/CHANGE_LOG.md +0 -479
@@ -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 terminal store keys usage by rulesetId, and each cycle produces a new simulated rulesetId.
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
- for (uint256 i; i < weightCutMultiple; i++) {
675
- // The number of times to apply the weight cut percent.
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
- baseRuleset = _getStructFor({projectId: projectId, rulesetId: baseRuleset.basedOnId});
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
- // Get the struct of the ruleset with the approval hook.
899
- JBRuleset memory approvalHookRuleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
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 (approvalHookRuleset.approvalHook == IJBRulesetApprovalHook(address(0))) {
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 approvalHookRuleset.approvalHook.approvalStatusOf({projectId: projectId, ruleset: ruleset}) returns (
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 = _getStructFor({projectId: projectId, rulesetId: rulesetId});
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 = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId});
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 = _getStructFor({projectId: projectId, rulesetId: rulesetId});
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 = _getStructFor({projectId: projectId, rulesetId: basedOnId});
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 = _getStructFor({projectId: projectId, rulesetId: rulesetId});
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; i++) {
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 = splitGroup.groupId >> 160 != 0 && address(uint160(splitGroup.groupId)) == msg.sender;
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, rulesetId: rulesetId, groupId: splitGroup.groupId, splits: splitGroup.splits
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; i++) {
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; i++) {
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; i++) {
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; i++) {
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; i++) {
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;