@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
@@ -110,6 +110,9 @@ contract JBTerminalStore is IJBTerminalStore {
110
110
  /// @dev Increases as projects use their allowance.
111
111
  /// @dev The used surplus allowance is represented as a fixed point number with the same amount of decimals as the
112
112
  /// terminal it applies to.
113
+ /// @dev Surplus allowance usage is keyed by `ruleset.id`, not cycle number. Implicit cycle progression
114
+ /// (duration-based auto-cycling) does not reset allowance — this is by design. Projects must queue a new ruleset
115
+ /// to get a fresh allowance.
113
116
  /// @custom:param terminal The terminal the surplus allowance applies to.
114
117
  /// @custom:param projectId The ID of the project to get the used surplus allowance of.
115
118
  /// @custom:param token The token the surplus allowance applies to in the terminal.
@@ -172,7 +175,7 @@ contract JBTerminalStore is IJBTerminalStore {
172
175
  }
173
176
 
174
177
  // Record each accounting context.
175
- for (uint256 i; i < contexts.length; i++) {
178
+ for (uint256 i; i < contexts.length;) {
176
179
  JBAccountingContext calldata context = contexts[i];
177
180
 
178
181
  // Make sure the token accounting context isn't already set.
@@ -213,6 +216,9 @@ contract JBTerminalStore is IJBTerminalStore {
213
216
 
214
217
  // Add the context to the list.
215
218
  _accountingContextsOf[msg.sender][projectId].push(context);
219
+ unchecked {
220
+ ++i;
221
+ }
216
222
  }
217
223
  }
218
224
 
@@ -228,12 +234,12 @@ contract JBTerminalStore is IJBTerminalStore {
228
234
 
229
235
  /// @notice Records a cash out from a project.
230
236
  /// @dev Cashs out the project's tokens according to values provided by the ruleset's data hook. If the ruleset has
231
- /// no
232
- /// data hook, cashs out tokens along a cash out bonding curve that is a function of the number of tokens being
237
+ /// no data hook, cashs out tokens along a cash out bonding curve that is a function of the number of tokens being
233
238
  /// burned.
234
239
  /// @param holder The account that is cashing out tokens.
235
240
  /// @param projectId The ID of the project being cashing out from.
236
- /// @param cashOutCount The number of project tokens to cash out, as a fixed point number with 18 decimals.
241
+ /// @param cashOutCount The number of project tokens to cash out, as supplied by the caller and later burned by the
242
+ /// terminal, as a fixed point number with 18 decimals.
237
243
  /// @param tokenToReclaim The token being reclaimed by the cash out.
238
244
  /// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address. Passed through to data
239
245
  /// hooks so they can skip their own fees when value stays in the protocol (e.g. project-to-project routing).
@@ -277,26 +283,29 @@ contract JBTerminalStore is IJBTerminalStore {
277
283
 
278
284
  if (hookSpecifications.length != 0) {
279
285
  uint256 numberOfSpecifications = hookSpecifications.length;
280
- for (uint256 i; i < numberOfSpecifications; i++) {
286
+ for (uint256 i; i < numberOfSpecifications;) {
281
287
  uint256 specificationAmount = hookSpecifications[i].amount;
282
288
  if (specificationAmount != 0) {
283
289
  balanceDiff += specificationAmount;
284
290
  }
291
+ unchecked {
292
+ ++i;
293
+ }
285
294
  }
286
295
  }
287
296
 
297
+ // Cache the balance slot to avoid redundant storage reads.
298
+ uint256 currentBalance = balanceOf[msg.sender][projectId][tokenToReclaim];
299
+
288
300
  // The amount being reclaimed must be within the project's balance.
289
- if (balanceDiff > balanceOf[msg.sender][projectId][tokenToReclaim]) {
290
- revert JBTerminalStore_InadequateTerminalStoreBalance(
291
- balanceDiff, balanceOf[msg.sender][projectId][tokenToReclaim]
292
- );
301
+ if (balanceDiff > currentBalance) {
302
+ revert JBTerminalStore_InadequateTerminalStoreBalance(balanceDiff, currentBalance);
293
303
  }
294
304
 
295
305
  // Remove the reclaimed funds from the project's balance.
296
306
  if (balanceDiff != 0) {
297
307
  unchecked {
298
- balanceOf[msg.sender][projectId][tokenToReclaim] =
299
- balanceOf[msg.sender][projectId][tokenToReclaim] - balanceDiff;
308
+ balanceOf[msg.sender][projectId][tokenToReclaim] = currentBalance - balanceDiff;
300
309
  }
301
310
  }
302
311
  }
@@ -338,8 +347,9 @@ contract JBTerminalStore is IJBTerminalStore {
338
347
 
339
348
  // Add the correct balance difference to the token balance of the project.
340
349
  if (balanceDiff != 0) {
341
- balanceOf[msg.sender][projectId][amount.token] =
342
- balanceOf[msg.sender][projectId][amount.token] + balanceDiff;
350
+ // Cache the balance slot to avoid redundant storage reads.
351
+ uint256 currentBalance = balanceOf[msg.sender][projectId][amount.token];
352
+ balanceOf[msg.sender][projectId][amount.token] = currentBalance + balanceDiff;
343
353
  }
344
354
  }
345
355
 
@@ -385,26 +395,20 @@ contract JBTerminalStore is IJBTerminalStore {
385
395
  })
386
396
  });
387
397
 
388
- // The amount being paid out must be available.
389
- if (amountPaidOut > balanceOf[msg.sender][projectId][token]) {
390
- revert JBTerminalStore_InadequateTerminalStoreBalance(
391
- amountPaidOut, balanceOf[msg.sender][projectId][token]
392
- );
393
- }
398
+ // Cache the balance slot to avoid redundant storage reads.
399
+ uint256 currentBalance = balanceOf[msg.sender][projectId][token];
394
400
 
395
- // Removed the paid out funds from the project's token balance.
396
- unchecked {
397
- balanceOf[msg.sender][projectId][token] = balanceOf[msg.sender][projectId][token] - amountPaidOut;
401
+ // The amount being paid out must be available.
402
+ if (amountPaidOut > currentBalance) {
403
+ revert JBTerminalStore_InadequateTerminalStoreBalance(amountPaidOut, currentBalance);
398
404
  }
399
405
 
400
406
  // The new total amount which has been paid out during this ruleset.
401
407
  uint256 newUsedPayoutLimitOf =
402
408
  usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] + amount;
403
409
 
404
- // Store the new amount.
405
- usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] = newUsedPayoutLimitOf;
406
-
407
410
  // Amount must be within what is still available to pay out.
411
+ // Validate BEFORE writing to storage to avoid wasting gas on SSTORE when the tx will revert.
408
412
  uint256 payoutLimit = IJBController(address(DIRECTORY.controllerOf(projectId))).FUND_ACCESS_LIMITS()
409
413
  .payoutLimitOf({
410
414
  projectId: projectId, rulesetId: ruleset.id, terminal: msg.sender, token: token, currency: currency
@@ -414,6 +418,14 @@ contract JBTerminalStore is IJBTerminalStore {
414
418
  if (newUsedPayoutLimitOf > payoutLimit || payoutLimit == 0) {
415
419
  revert JBTerminalStore_InadequateControllerPayoutLimit(newUsedPayoutLimitOf, payoutLimit);
416
420
  }
421
+
422
+ // Removed the paid out funds from the project's token balance.
423
+ unchecked {
424
+ balanceOf[msg.sender][projectId][token] = currentBalance - amountPaidOut;
425
+ }
426
+
427
+ // Store the new used payout limit.
428
+ usedPayoutLimitOf[msg.sender][projectId][token][ruleset.cycleNumber][currency] = newUsedPayoutLimitOf;
417
429
  }
418
430
 
419
431
  /// @notice Records the migration of funds from this store.
@@ -493,17 +505,12 @@ contract JBTerminalStore is IJBTerminalStore {
493
505
  // The amount being used must be available in the surplus.
494
506
  if (usedAmount > surplus) revert JBTerminalStore_InadequateTerminalStoreBalance(usedAmount, surplus);
495
507
 
496
- // Update the project's balance.
497
- balanceOf[msg.sender][projectId][token] = balanceOf[msg.sender][projectId][token] - usedAmount;
498
-
499
508
  // Get a reference to the new used surplus allowance for this ruleset ID.
500
509
  uint256 newUsedSurplusAllowanceOf =
501
510
  usedSurplusAllowanceOf[msg.sender][projectId][token][ruleset.id][currency] + amount;
502
511
 
503
- // Store the incremented value.
504
- usedSurplusAllowanceOf[msg.sender][projectId][token][ruleset.id][currency] = newUsedSurplusAllowanceOf;
505
-
506
512
  // There must be sufficient surplus allowance available.
513
+ // Validate BEFORE writing to storage to avoid wasting gas on SSTORE when the tx will revert.
507
514
  uint256 surplusAllowance = IJBController(address(DIRECTORY.controllerOf(projectId))).FUND_ACCESS_LIMITS()
508
515
  .surplusAllowanceOf({
509
516
  projectId: projectId, rulesetId: ruleset.id, terminal: msg.sender, token: token, currency: currency
@@ -513,6 +520,15 @@ contract JBTerminalStore is IJBTerminalStore {
513
520
  if (newUsedSurplusAllowanceOf > surplusAllowance || surplusAllowance == 0) {
514
521
  revert JBTerminalStore_InadequateControllerAllowance(newUsedSurplusAllowanceOf, surplusAllowance);
515
522
  }
523
+
524
+ // Cache the balance slot to avoid redundant storage reads.
525
+ uint256 currentBalance = balanceOf[msg.sender][projectId][token];
526
+
527
+ // Update the project's balance.
528
+ balanceOf[msg.sender][projectId][token] = currentBalance - usedAmount;
529
+
530
+ // Store the incremented value.
531
+ usedSurplusAllowanceOf[msg.sender][projectId][token][ruleset.id][currency] = newUsedSurplusAllowanceOf;
516
532
  }
517
533
 
518
534
  //*********************************************************************//
@@ -774,9 +790,17 @@ contract JBTerminalStore is IJBTerminalStore {
774
790
  override
775
791
  returns (uint256)
776
792
  {
793
+ // Fetch the current ruleset once — used for both surplus calculation and cash out tax rate.
794
+ JBRuleset memory ruleset = RULESETS.currentOf(projectId);
795
+
777
796
  // Aggregate surplus across the terminals, optionally filtered by the specified tokens.
778
797
  uint256 currentSurplus = _currentSurplusOf({
779
- projectId: projectId, terminals: terminals, tokens: tokens, decimals: decimals, currency: currency
798
+ projectId: projectId,
799
+ terminals: terminals,
800
+ tokens: tokens,
801
+ decimals: decimals,
802
+ currency: currency,
803
+ ruleset: ruleset
780
804
  });
781
805
 
782
806
  // If there's no surplus, nothing can be reclaimed.
@@ -789,15 +813,12 @@ contract JBTerminalStore is IJBTerminalStore {
789
813
  // Can't cash out more tokens than are in the total supply.
790
814
  if (cashOutCount > totalSupply) return 0;
791
815
 
792
- // Get the cash out tax rate from the current ruleset.
793
- uint256 cashOutTaxRate = RULESETS.currentOf(projectId).cashOutTaxRate();
794
-
795
816
  // Return the amount of surplus terminal tokens that would be reclaimed.
796
817
  return JBCashOuts.cashOutFrom({
797
818
  surplus: currentSurplus,
798
819
  cashOutCount: cashOutCount,
799
820
  totalSupply: totalSupply,
800
- cashOutTaxRate: cashOutTaxRate
821
+ cashOutTaxRate: ruleset.cashOutTaxRate()
801
822
  });
802
823
  }
803
824
 
@@ -806,6 +827,9 @@ contract JBTerminalStore is IJBTerminalStore {
806
827
  //*********************************************************************//
807
828
 
808
829
  /// @notice Computes the surplus relevant for a cash out (total or local, depending on ruleset flag).
830
+ /// @dev When `useTotalSurplusForCashOuts` is enabled, surplus is aggregated from ALL registered terminals without
831
+ /// validation. Projects MUST only register trusted terminals — an untrusted terminal can over-report surplus and
832
+ /// cause the executing terminal to overpay cash-outs.
809
833
  /// @param terminal The terminal the cash out is being recorded from.
810
834
  /// @param projectId The ID of the project being cashed out from.
811
835
  /// @param tokenToReclaim The token being reclaimed.
@@ -895,18 +919,23 @@ contract JBTerminalStore is IJBTerminalStore {
895
919
  JBAccountingContext memory accountingContext = _accountingContextForTokenOf[terminal][projectId][tokenToReclaim];
896
920
 
897
921
  // Get the total number of outstanding project tokens.
898
- uint256 totalSupply =
922
+ uint256 effectiveTotalSupply =
899
923
  IJBController(address(DIRECTORY.controllerOf(projectId))).totalTokenSupplyWithReservedTokensOf(projectId);
900
924
 
901
925
  // Can't cash out more tokens than are in the supply.
902
- if (cashOutCount > totalSupply) revert JBTerminalStore_InsufficientTokens(cashOutCount, totalSupply);
926
+ if (cashOutCount > effectiveTotalSupply) {
927
+ revert JBTerminalStore_InsufficientTokens(cashOutCount, effectiveTotalSupply);
928
+ }
903
929
 
904
- // SECURITY NOTE: The data hook has absolute control over cash-out economics.
905
- // It can set totalSupply, cashOutCount, and cashOutTaxRate to arbitrary values,
930
+ // SECURITY NOTE: The data hook has absolute control over cash-out pricing.
931
+ // It can set effectiveTotalSupply, effectiveCashOutCount, and cashOutTaxRate to arbitrary values,
906
932
  // completely overriding the terminal's bonding curve math. For example, setting
907
- // totalSupply = surplus makes reclaimAmount = cashOutCount, bypassing the curve.
933
+ // effectiveTotalSupply = surplus makes reclaimAmount = effectiveCashOutCount, bypassing the curve.
934
+ // The terminal still burns the caller-supplied cashOutCount after pricing completes.
908
935
  // Project owners MUST audit their data hooks with the same rigor as the terminal.
909
936
 
937
+ uint256 effectiveCashOutCount = cashOutCount;
938
+
910
939
  // If the ruleset has a data hook which is enabled for cash outs, use it to derive a claim amount and memo.
911
940
  if (ruleset.useDataHookForCashOut() && ruleset.dataHook() != address(0)) {
912
941
  // Build the cash out context field-by-field — the struct has 11 fields, too many for a literal.
@@ -916,7 +945,7 @@ contract JBTerminalStore is IJBTerminalStore {
916
945
  context.projectId = projectId;
917
946
  context.rulesetId = ruleset.id;
918
947
  context.cashOutCount = cashOutCount;
919
- context.totalSupply = totalSupply;
948
+ context.totalSupply = effectiveTotalSupply;
920
949
  context.surplus = JBTokenAmount({
921
950
  token: accountingContext.token,
922
951
  value: surplus,
@@ -928,14 +957,17 @@ contract JBTerminalStore is IJBTerminalStore {
928
957
  context.beneficiaryIsFeeless = beneficiaryIsFeeless;
929
958
  context.metadata = metadata;
930
959
 
931
- (cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications) =
960
+ (cashOutTaxRate, effectiveCashOutCount, effectiveTotalSupply, hookSpecifications) =
932
961
  IJBRulesetDataHook(ruleset.dataHook()).beforeCashOutRecordedWith(context);
933
962
 
934
963
  // Noop specifications are informational only, so they can't also request forwarded funds.
935
- for (uint256 i; i < hookSpecifications.length; i++) {
964
+ for (uint256 i; i < hookSpecifications.length;) {
936
965
  if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
937
966
  revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
938
967
  }
968
+ unchecked {
969
+ ++i;
970
+ }
939
971
  }
940
972
  } else {
941
973
  cashOutTaxRate = ruleset.cashOutTaxRate();
@@ -944,7 +976,10 @@ contract JBTerminalStore is IJBTerminalStore {
944
976
  // Apply the bonding curve to calculate how much of the surplus is reclaimable.
945
977
  if (surplus != 0) {
946
978
  reclaimAmount = JBCashOuts.cashOutFrom({
947
- surplus: surplus, cashOutCount: cashOutCount, totalSupply: totalSupply, cashOutTaxRate: cashOutTaxRate
979
+ surplus: surplus,
980
+ cashOutCount: effectiveCashOutCount,
981
+ totalSupply: effectiveTotalSupply,
982
+ cashOutTaxRate: cashOutTaxRate
948
983
  });
949
984
  }
950
985
  }
@@ -1020,7 +1055,7 @@ contract JBTerminalStore is IJBTerminalStore {
1020
1055
  balanceDiff = amount.value;
1021
1056
 
1022
1057
  // Ensure that the specifications have valid amounts.
1023
- for (uint256 i; i < hookSpecifications.length; i++) {
1058
+ for (uint256 i; i < hookSpecifications.length;) {
1024
1059
  if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
1025
1060
  revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
1026
1061
  }
@@ -1036,6 +1071,9 @@ contract JBTerminalStore is IJBTerminalStore {
1036
1071
  // Decrement the total amount being added to the local balance.
1037
1072
  balanceDiff -= specifiedAmount;
1038
1073
  }
1074
+ unchecked {
1075
+ ++i;
1076
+ }
1039
1077
  }
1040
1078
 
1041
1079
  // If there's no amount being recorded, there's nothing left to do.
@@ -1078,15 +1116,46 @@ contract JBTerminalStore is IJBTerminalStore {
1078
1116
  internal
1079
1117
  view
1080
1118
  returns (uint256 surplus)
1119
+ {
1120
+ // Fetch the ruleset once and delegate to the overload that accepts it.
1121
+ return _currentSurplusOf({
1122
+ projectId: projectId,
1123
+ terminals: terminals,
1124
+ tokens: tokens,
1125
+ decimals: decimals,
1126
+ currency: currency,
1127
+ ruleset: RULESETS.currentOf(projectId)
1128
+ });
1129
+ }
1130
+
1131
+ /// @notice Gets the current surplus amount for a project across specified terminals and tokens, using a
1132
+ /// pre-fetched ruleset.
1133
+ /// @dev Use this overload when the caller already has the current ruleset to avoid a redundant
1134
+ /// `RULESETS.currentOf()` call.
1135
+ /// @param projectId The ID of the project to get surplus for.
1136
+ /// @param terminals The terminals to include. If empty, all project terminals are used.
1137
+ /// @param tokens The tokens to include. If empty, all tokens per terminal are used.
1138
+ /// @param decimals The number of decimals to expect in the resulting fixed point number.
1139
+ /// @param currency The currency the resulting amount should be in terms of.
1140
+ /// @param ruleset The project's current ruleset.
1141
+ /// @return surplus The current surplus amount.
1142
+ function _currentSurplusOf(
1143
+ uint256 projectId,
1144
+ IJBTerminal[] memory terminals,
1145
+ address[] memory tokens,
1146
+ uint256 decimals,
1147
+ uint256 currency,
1148
+ JBRuleset memory ruleset
1149
+ )
1150
+ internal
1151
+ view
1152
+ returns (uint256 surplus)
1081
1153
  {
1082
1154
  // If specific terminals were provided, use them. Otherwise, get all terminals from the directory.
1083
1155
  IJBTerminal[] memory resolvedTerminals = terminals.length != 0 ? terminals : DIRECTORY.terminalsOf(projectId);
1084
1156
 
1085
- // The ruleset determines payout limits, which affect surplus. Fetch it once for all terminals.
1086
- JBRuleset memory ruleset = RULESETS.currentOf(projectId);
1087
-
1088
1157
  // Sum surplus across each terminal.
1089
- for (uint256 i; i < resolvedTerminals.length; i++) {
1158
+ for (uint256 i; i < resolvedTerminals.length;) {
1090
1159
  address terminal = address(resolvedTerminals[i]);
1091
1160
 
1092
1161
  // Build the list of accounting contexts to include in this terminal's surplus calculation.
@@ -1094,8 +1163,11 @@ contract JBTerminalStore is IJBTerminalStore {
1094
1163
  if (tokens.length != 0) {
1095
1164
  // Specific tokens requested: look up each token's accounting context at this terminal.
1096
1165
  accountingContexts = new JBAccountingContext[](tokens.length);
1097
- for (uint256 j; j < tokens.length; j++) {
1166
+ for (uint256 j; j < tokens.length;) {
1098
1167
  accountingContexts[j] = _accountingContextForTokenOf[terminal][projectId][tokens[j]];
1168
+ unchecked {
1169
+ ++j;
1170
+ }
1099
1171
  }
1100
1172
  } else {
1101
1173
  // No token filter: use all accounting contexts registered at this terminal.
@@ -1111,6 +1183,9 @@ contract JBTerminalStore is IJBTerminalStore {
1111
1183
  targetDecimals: decimals,
1112
1184
  targetCurrency: currency
1113
1185
  });
1186
+ unchecked {
1187
+ ++i;
1188
+ }
1114
1189
  }
1115
1190
  }
1116
1191
 
@@ -1143,7 +1218,7 @@ contract JBTerminalStore is IJBTerminalStore {
1143
1218
  uint256 numberOfTokenAccountingContexts = accountingContexts.length;
1144
1219
 
1145
1220
  // Add payout limits from each token.
1146
- for (uint256 i; i < numberOfTokenAccountingContexts; i++) {
1221
+ for (uint256 i; i < numberOfTokenAccountingContexts;) {
1147
1222
  uint256 tokenSurplus = _tokenSurplusFrom({
1148
1223
  terminal: terminal,
1149
1224
  projectId: projectId,
@@ -1154,6 +1229,9 @@ contract JBTerminalStore is IJBTerminalStore {
1154
1229
  });
1155
1230
  // Increment the surplus with any remaining balance.
1156
1231
  if (tokenSurplus > 0) surplus += tokenSurplus;
1232
+ unchecked {
1233
+ ++i;
1234
+ }
1157
1235
  }
1158
1236
  }
1159
1237
 
@@ -1193,6 +1271,7 @@ contract JBTerminalStore is IJBTerminalStore {
1193
1271
  });
1194
1272
 
1195
1273
  // Add up all the balances.
1274
+ // slither-disable-next-line calls-loop
1196
1275
  surplus = (surplus == 0 || accountingContext.currency == targetCurrency)
1197
1276
  ? surplus
1198
1277
  : mulDiv({
@@ -1208,6 +1287,7 @@ contract JBTerminalStore is IJBTerminalStore {
1208
1287
  });
1209
1288
 
1210
1289
  // Get a reference to the payout limit during the ruleset for the token.
1290
+ // slither-disable-next-line calls-loop
1211
1291
  JBCurrencyAmount[] memory payoutLimits = IJBController(address(DIRECTORY.controllerOf(projectId)))
1212
1292
  .FUND_ACCESS_LIMITS()
1213
1293
  .payoutLimitsOf({
@@ -1218,7 +1298,7 @@ contract JBTerminalStore is IJBTerminalStore {
1218
1298
  uint256 numberOfPayoutLimits = payoutLimits.length;
1219
1299
 
1220
1300
  // Loop through each payout limit to determine the cumulative normalized payout limit remaining.
1221
- for (uint256 i; i < numberOfPayoutLimits; i++) {
1301
+ for (uint256 i; i < numberOfPayoutLimits;) {
1222
1302
  JBCurrencyAmount memory payoutLimit = payoutLimits[i];
1223
1303
 
1224
1304
  // Set the payout limit value to the amount still available to pay out during the ruleset.
@@ -1247,6 +1327,7 @@ contract JBTerminalStore is IJBTerminalStore {
1247
1327
 
1248
1328
  // Convert the `payoutLimit`'s amount to be in terms of the provided currency.
1249
1329
  if (payoutLimit.amount != 0 && payoutLimit.currency != targetCurrency) {
1330
+ // slither-disable-next-line calls-loop
1250
1331
  uint256 converted = mulDiv({
1251
1332
  x: payoutLimit.amount,
1252
1333
  y: 10 ** _MAX_FIXED_POINT_FIDELITY, // Use `_MAX_FIXED_POINT_FIDELITY` to keep as much of the
@@ -1269,6 +1350,9 @@ contract JBTerminalStore is IJBTerminalStore {
1269
1350
  } else {
1270
1351
  return 0;
1271
1352
  }
1353
+ unchecked {
1354
+ ++i;
1355
+ }
1272
1356
  }
1273
1357
  }
1274
1358
  }
package/src/JBTokens.sol CHANGED
@@ -253,9 +253,12 @@ contract JBTokens is JBControlled, IJBTokens {
253
253
  // Save a reference to whether there a token exists.
254
254
  bool tokensWereClaimed = token != IJBToken(address(0));
255
255
 
256
+ // Cache the total supply to avoid a redundant external call on revert.
257
+ uint256 supply = totalSupplyOf(projectId);
258
+
256
259
  // The total supply after minting can't exceed the maximum value storable in a uint208.
257
- if (totalSupplyOf(projectId) + count > type(uint208).max) {
258
- revert JBTokens_OverflowAlert(totalSupplyOf(projectId) + count, type(uint208).max);
260
+ if (supply + count > type(uint208).max) {
261
+ revert JBTokens_OverflowAlert(supply + count, type(uint208).max);
259
262
  }
260
263
 
261
264
  if (tokensWereClaimed) {
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.0;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IJBControlled} from "./../interfaces/IJBControlled.sol";
5
5
  import {IJBDirectory} from "./../interfaces/IJBDirectory.sol";
@@ -39,10 +39,17 @@ abstract contract JBControlled is IJBControlled {
39
39
  DIRECTORY = directory;
40
40
  }
41
41
 
42
+ //*********************************************************************//
43
+ // -------------------------- internal views ------------------------- //
44
+ //*********************************************************************//
45
+
42
46
  /// @notice Only allows the controller of the specified project to proceed.
43
47
  function _onlyControllerOf(uint256 projectId) internal view {
44
- if (address(DIRECTORY.controllerOf(projectId)) != msg.sender) {
45
- revert JBControlled_ControllerUnauthorized(address(DIRECTORY.controllerOf(projectId)));
48
+ // Cache the controller address to avoid a redundant external call on revert.
49
+ // slither-disable-next-line calls-loop
50
+ address controller = address(DIRECTORY.controllerOf(projectId));
51
+ if (controller != msg.sender) {
52
+ revert JBControlled_ControllerUnauthorized(controller);
46
53
  }
47
54
  }
48
55
  }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.0;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {Context} from "@openzeppelin/contracts/utils/Context.sol";
5
5
 
@@ -17,8 +17,9 @@ interface IJBRulesetDataHook is IERC165 {
17
17
  /// @notice Calculates data before a cash out is recorded in the terminal store.
18
18
  /// @param context The context passed to this data hook by the `cashOutTokensOf(...)` function.
19
19
  /// @return cashOutTaxRate The rate determining the reclaimable amount for a given surplus and token supply.
20
- /// @return cashOutCount The number of tokens to consider cashed out.
21
- /// @return totalSupply The total number of tokens to consider existing.
20
+ /// @return effectiveCashOutCount The effective token count to use for pricing the cash out. The terminal still
21
+ /// burns the caller-supplied token count.
22
+ /// @return effectiveTotalSupply The effective total supply to use for pricing the cash out.
22
23
  /// @return hookSpecifications The amount and data to send to cash out hooks instead of returning to the
23
24
  /// beneficiary.
24
25
  function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
@@ -26,8 +27,8 @@ interface IJBRulesetDataHook is IERC165 {
26
27
  view
27
28
  returns (
28
29
  uint256 cashOutTaxRate,
29
- uint256 cashOutCount,
30
- uint256 totalSupply,
30
+ uint256 effectiveCashOutCount,
31
+ uint256 effectiveTotalSupply,
31
32
  JBCashOutHookSpecification[] memory hookSpecifications
32
33
  );
33
34
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.17;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {mulDiv} from "@prb/math/src/Common.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.0;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  /// @notice Global constants used across Juicebox contracts.
5
5
  library JBConstants {
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.0;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  library JBCurrencyIds {
5
5
  uint32 public constant ETH = 1;
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.17;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {mulDiv} from "@prb/math/src/Common.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.17;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  library JBFixedPointNumber {
5
5
  function adjustDecimals(uint256 value, uint256 decimals, uint256 targetDecimals) internal pure returns (uint256) {
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.17;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  /**
5
5
  * @notice Library to parse and create metadata to store {id: data} entries.
@@ -214,7 +214,7 @@ library JBMetadataResolver {
214
214
  uint256 numberOfDatas = datas.length;
215
215
 
216
216
  // Add each metadata to the array, each padded to 32 bytes
217
- for (uint256 i; i < numberOfDatas; i++) {
217
+ for (uint256 i; i < numberOfDatas;) {
218
218
  metadata = abi.encodePacked(metadata, datas[i]);
219
219
  paddedLength = metadata.length % JBMetadataResolver.WORD_SIZE == 0
220
220
  ? metadata.length
@@ -223,6 +223,9 @@ library JBMetadataResolver {
223
223
  assembly ("memory-safe") {
224
224
  mstore(metadata, paddedLength)
225
225
  }
226
+ unchecked {
227
+ ++i;
228
+ }
226
229
  }
227
230
  }
228
231
 
@@ -69,7 +69,7 @@ library JBPayoutSplitGroupLib {
69
69
  leftoverAmount = amount;
70
70
 
71
71
  // Transfer between all splits.
72
- for (uint256 i; i < payoutSplits.length; i++) {
72
+ for (uint256 i; i < payoutSplits.length;) {
73
73
  // Get a reference to the split being iterated on.
74
74
  JBSplit memory split = payoutSplits[i];
75
75
 
@@ -77,6 +77,7 @@ library JBPayoutSplitGroupLib {
77
77
  uint256 payoutAmount = mulDiv(leftoverAmount, split.percent, leftoverPercentage);
78
78
 
79
79
  // The final payout amount after taking out any fees.
80
+ // slither-disable-next-line calls-loop
80
81
  uint256 netPayoutAmount = _sendPayoutToSplit({
81
82
  store: store, split: split, projectId: projectId, token: token, amount: payoutAmount, caller: caller
82
83
  });
@@ -107,6 +108,9 @@ library JBPayoutSplitGroupLib {
107
108
  netAmount: netPayoutAmount,
108
109
  caller: caller
109
110
  });
111
+ unchecked {
112
+ ++i;
113
+ }
110
114
  }
111
115
  }
112
116
 
@@ -133,7 +137,7 @@ library JBPayoutSplitGroupLib {
133
137
  // split from DoS-ing the entire payout. Failed splits' amounts are returned to the project balance via
134
138
  // `recordAddedBalanceFor`. Payout limit consumption is correct because the project authorized the
135
139
  // distribution.
136
- // slither-disable-next-line reentrancy-events
140
+ // slither-disable-next-line reentrancy-events,calls-loop
137
141
  try IJBPayoutSplitGroupExecutor(address(this))
138
142
  .executePayout({
139
143
  split: split, projectId: projectId, token: token, amount: amount, originalMessageSender: caller
@@ -147,6 +151,7 @@ library JBPayoutSplitGroupLib {
147
151
  });
148
152
 
149
153
  // Add balance back to the project.
154
+ // slither-disable-next-line calls-loop
150
155
  store.recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
151
156
 
152
157
  // Since the payout failed the netPayoutAmount is zero.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.17;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBRuleset} from "./../structs/JBRuleset.sol";
5
5
  import {JBRulesetMetadata} from "./../structs/JBRulesetMetadata.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.0;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  /// @notice Group IDs that categorize splits.
5
5
  library JBSplitGroupIds {