@bananapus/core-v6 0.0.31 → 0.0.33

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/core-v6",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -522,23 +522,28 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
522
522
  // Get a reference to the project's ruleset.
523
523
  JBRuleset memory ruleset = _currentRulesetOf(projectId);
524
524
 
525
+ // Cache common values used in both permission checks.
526
+ address sender = _msgSender();
527
+ bool senderIsTerminal = _isTerminalOf(projectId, sender);
528
+
525
529
  // Minting is restricted to: the project's owner, addresses with permission to `MINT_TOKENS`, the project's
526
530
  // terminals, and the project's data hook.
527
531
  _requirePermissionAllowingOverrideFrom({
528
532
  account: PROJECTS.ownerOf(projectId),
529
533
  projectId: projectId,
530
534
  permissionId: JBPermissionIds.MINT_TOKENS,
531
- alsoGrantAccessIf: _isTerminalOf(projectId, _msgSender()) || _msgSender() == ruleset.dataHook()
532
- || _hasDataHookMintPermissionFor(projectId, ruleset, _msgSender())
535
+ alsoGrantAccessIf: senderIsTerminal || sender == ruleset.dataHook()
536
+ || _hasDataHookMintPermissionFor(projectId, ruleset, sender)
533
537
  });
534
538
 
535
539
  // If the message sender is not the project's terminal or data hook, the ruleset must have `allowOwnerMinting`
536
540
  // set to `true`.
537
541
  if (
538
- ruleset.id != 0 && !ruleset.allowOwnerMinting() && !_isTerminalOf(projectId, _msgSender())
539
- && _msgSender() != address(ruleset.dataHook())
540
- && !_hasDataHookMintPermissionFor(projectId, ruleset, _msgSender())
541
- ) revert JBController_MintNotAllowedAndNotTerminalOrHook(_msgSender());
542
+ ruleset.id != 0 && !ruleset.allowOwnerMinting() && !senderIsTerminal && sender != ruleset.dataHook()
543
+ && !_hasDataHookMintPermissionFor(projectId, ruleset, sender)
544
+ ) {
545
+ revert JBController_MintNotAllowedAndNotTerminalOrHook(sender);
546
+ }
542
547
 
543
548
  // Determine the reserved percent to use.
544
549
  reservedPercent = useReservedPercent ? ruleset.reservedPercent() : 0;
@@ -559,7 +564,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
559
564
  beneficiaryTokenCount: beneficiaryTokenCount,
560
565
  memo: memo,
561
566
  reservedPercent: reservedPercent,
562
- caller: _msgSender()
567
+ caller: sender
563
568
  });
564
569
 
565
570
  // Add any reserved tokens to the pending reserved token balance.
@@ -748,12 +753,15 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
748
753
  rulesets = new JBRulesetWithMetadata[](numberOfRulesets);
749
754
 
750
755
  // Populate the array with rulesets AND their metadata.
751
- for (uint256 i; i < numberOfRulesets; i++) {
756
+ for (uint256 i; i < numberOfRulesets;) {
752
757
  // Set the ruleset being iterated on.
753
758
  JBRuleset memory baseRuleset = baseRulesets[i];
754
759
 
755
760
  // Set the returned value.
756
761
  rulesets[i] = JBRulesetWithMetadata({ruleset: baseRuleset, metadata: baseRuleset.expandMetadata()});
762
+ unchecked {
763
+ ++i;
764
+ }
757
765
  }
758
766
  }
759
767
 
@@ -896,7 +904,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
896
904
  // Initialize an array of terminals to populate.
897
905
  IJBTerminal[] memory terminals = new IJBTerminal[](terminalConfigurations.length);
898
906
 
899
- for (uint256 i; i < terminalConfigurations.length; i++) {
907
+ for (uint256 i; i < terminalConfigurations.length;) {
900
908
  // Set the terminal configuration being iterated on.
901
909
  JBTerminalConfig memory terminalConfig = terminalConfigurations[i];
902
910
 
@@ -909,6 +917,9 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
909
917
 
910
918
  // Add the terminal.
911
919
  terminals[i] = terminalConfig.terminal;
920
+ unchecked {
921
+ ++i;
922
+ }
912
923
  }
913
924
 
914
925
  // Set the terminals in the directory.
@@ -928,7 +939,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
928
939
  internal
929
940
  returns (uint256 rulesetId)
930
941
  {
931
- for (uint256 i; i < rulesetConfigurations.length; i++) {
942
+ for (uint256 i; i < rulesetConfigurations.length;) {
932
943
  // Get a reference to the ruleset config being iterated on.
933
944
  JBRulesetConfig memory rulesetConfig = rulesetConfigurations[i];
934
945
 
@@ -974,6 +985,9 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
974
985
  if (i == rulesetConfigurations.length - 1) {
975
986
  rulesetId = ruleset.id;
976
987
  }
988
+ unchecked {
989
+ ++i;
990
+ }
977
991
  }
978
992
  }
979
993
 
@@ -1004,8 +1018,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
1004
1018
  // Keep a reference to the number of splits being iterated on.
1005
1019
  uint256 numberOfSplits = splits.length;
1006
1020
 
1021
+ // Cache _msgSender() before the loop.
1022
+ address messageSender = _msgSender();
1023
+
1007
1024
  // Send the tokens to the splits.
1008
- for (uint256 i; i < numberOfSplits; i++) {
1025
+ for (uint256 i; i < numberOfSplits;) {
1009
1026
  // Get a reference to the split being iterated on.
1010
1027
  JBSplit memory split = splits[i];
1011
1028
 
@@ -1048,7 +1065,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
1048
1065
  } else {
1049
1066
  // Pay the project using the split's beneficiary if one was provided. Otherwise, use the message
1050
1067
  // sender.
1051
- address beneficiary = split.beneficiary != address(0) ? split.beneficiary : _msgSender();
1068
+ address beneficiary = split.beneficiary != address(0) ? split.beneficiary : messageSender;
1052
1069
 
1053
1070
  if (split.projectId != 0) {
1054
1071
  // Get a reference to the receiving project's primary payment terminal for the token.
@@ -1086,7 +1103,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
1086
1103
  split: split,
1087
1104
  tokenCount: splitTokenCount,
1088
1105
  reason: reason,
1089
- caller: _msgSender()
1106
+ caller: messageSender
1090
1107
  });
1091
1108
 
1092
1109
  // If it fails, transfer the tokens from this contract to the beneficiary.
@@ -1115,8 +1132,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
1115
1132
  groupId: groupId,
1116
1133
  split: split,
1117
1134
  tokenCount: splitTokenCount,
1118
- caller: _msgSender()
1135
+ caller: messageSender
1119
1136
  });
1137
+ unchecked {
1138
+ ++i;
1139
+ }
1120
1140
  }
1121
1141
  }
1122
1142
 
@@ -210,23 +210,23 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
210
210
  /// @param projectId The ID of the project whose terminals are being set.
211
211
  /// @param terminals An array of terminal addresses to set for the project.
212
212
  function setTerminalsOf(uint256 projectId, IJBTerminal[] calldata terminals) external override {
213
+ // Cache the controller to avoid redundant storage reads.
214
+ IERC165 controller = controllerOf[projectId];
215
+
213
216
  // Enforce permissions.
214
217
  _requirePermissionAllowingOverrideFrom({
215
218
  account: PROJECTS.ownerOf(projectId),
216
219
  projectId: projectId,
217
220
  permissionId: JBPermissionIds.SET_TERMINALS,
218
- alsoGrantAccessIf: msg.sender == address(controllerOf[projectId])
221
+ alsoGrantAccessIf: msg.sender == address(controller)
219
222
  });
220
223
 
221
- // Keep a reference to the project's controller.
222
- IERC165 controller = controllerOf[projectId];
223
-
224
224
  // Get a reference to the flag indicating whether the project is allowed to set its terminals.
225
225
  bool allowSetTerminals = !controller.supportsInterface(type(IJBDirectoryAccessControl).interfaceId)
226
226
  || IJBDirectoryAccessControl(address(controller)).setTerminalsAllowed(projectId);
227
227
 
228
228
  // If the caller is not the project's controller, the project's ruleset must allow setting terminals.
229
- if (msg.sender != address(controllerOf[projectId]) && !allowSetTerminals) {
229
+ if (msg.sender != address(controller) && !allowSetTerminals) {
230
230
  revert JBDirectory_SetTerminalsNotAllowed(projectId);
231
231
  }
232
232
 
@@ -235,9 +235,15 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
235
235
 
236
236
  // If there are any duplicates, revert.
237
237
  if (terminals.length > 1) {
238
- for (uint256 i; i < terminals.length; i++) {
239
- for (uint256 j = i + 1; j < terminals.length; j++) {
238
+ for (uint256 i; i < terminals.length;) {
239
+ for (uint256 j = i + 1; j < terminals.length;) {
240
240
  if (terminals[i] == terminals[j]) revert JBDirectory_DuplicateTerminals(terminals[i]);
241
+ unchecked {
242
+ ++j;
243
+ }
244
+ }
245
+ unchecked {
246
+ ++i;
241
247
  }
242
248
  }
243
249
  }
@@ -267,14 +273,14 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
267
273
  return primaryTerminal;
268
274
  }
269
275
 
270
- // Keep a reference to the project's terminals.
271
- IJBTerminal[] memory terminals = _terminalsOf[projectId];
276
+ // Keep a storage reference to the project's terminals to avoid copying the array to memory.
277
+ IJBTerminal[] storage terminals = _terminalsOf[projectId];
272
278
 
273
279
  // Keep a reference to the number of terminals the project has.
274
280
  uint256 numberOfTerminals = terminals.length;
275
281
 
276
282
  // Return the first terminal which accepts the specified token.
277
- for (uint256 i; i < numberOfTerminals; i++) {
283
+ for (uint256 i; i < numberOfTerminals;) {
278
284
  // Keep a reference to the terminal being iterated on.
279
285
  IJBTerminal terminal = terminals[i];
280
286
 
@@ -283,6 +289,9 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
283
289
  if (terminal.accountingContextForTokenOf({projectId: projectId, token: token}).token != address(0)) {
284
290
  return terminal;
285
291
  }
292
+ unchecked {
293
+ ++i;
294
+ }
286
295
  }
287
296
 
288
297
  // Not found.
@@ -305,15 +314,18 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
305
314
  /// @param terminal The terminal to check for.
306
315
  /// @return A flag indicating whether the project uses the terminal.
307
316
  function isTerminalOf(uint256 projectId, IJBTerminal terminal) public view override returns (bool) {
308
- // Keep a reference to the project's terminals.
309
- IJBTerminal[] memory terminals = _terminalsOf[projectId];
317
+ // Keep a storage reference to the project's terminals to avoid copying the array to memory.
318
+ IJBTerminal[] storage terminals = _terminalsOf[projectId];
310
319
 
311
320
  // Keep a reference to the number of terminals the project has.
312
321
  uint256 numberOfTerminals = terminals.length;
313
322
 
314
323
  // Loop through and return true if the terminal is found.
315
- for (uint256 i; i < numberOfTerminals; i++) {
324
+ for (uint256 i; i < numberOfTerminals;) {
316
325
  if (terminals[i] == terminal) return true;
326
+ unchecked {
327
+ ++i;
328
+ }
317
329
  }
318
330
 
319
331
  // Otherwise, return false.
@@ -82,7 +82,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
82
82
  uint256 numberOfFundAccessLimitGroups = fundAccessLimitGroups.length;
83
83
 
84
84
  // Set payout limits if there are any.
85
- for (uint256 i; i < numberOfFundAccessLimitGroups; i++) {
85
+ for (uint256 i; i < numberOfFundAccessLimitGroups;) {
86
86
  // Set the limits being iterated on.
87
87
  JBFundAccessLimitGroup calldata fundAccessLimitGroup = fundAccessLimitGroups[i];
88
88
 
@@ -90,7 +90,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
90
90
  uint256 numberOfPayoutLimits = fundAccessLimitGroup.payoutLimits.length;
91
91
 
92
92
  // Iterate through each payout limit to validate and store them.
93
- for (uint256 j; j < numberOfPayoutLimits; j++) {
93
+ for (uint256 j; j < numberOfPayoutLimits;) {
94
94
  // Set the payout limit being iterated on.
95
95
  JBCurrencyAmount calldata payoutLimit = fundAccessLimitGroup.payoutLimits[j];
96
96
 
@@ -106,13 +106,16 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
106
106
  uint256(payoutLimit.amount) | (uint256(payoutLimit.currency) << 224)
107
107
  );
108
108
  }
109
+ unchecked {
110
+ ++j;
111
+ }
109
112
  }
110
113
 
111
114
  // Keep a reference to the number of surplus allowances.
112
115
  uint256 numberOfSurplusAllowances = fundAccessLimitGroup.surplusAllowances.length;
113
116
 
114
117
  // Iterate through each surplus allowance to validate and store them.
115
- for (uint256 j; j < numberOfSurplusAllowances; j++) {
118
+ for (uint256 j; j < numberOfSurplusAllowances;) {
116
119
  // Set the surplus allowance being iterated on.
117
120
  JBCurrencyAmount calldata surplusAllowance = fundAccessLimitGroup.surplusAllowances[j];
118
121
 
@@ -128,6 +131,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
128
131
  uint256(surplusAllowance.amount) | (uint256(surplusAllowance.currency) << 224)
129
132
  );
130
133
  }
134
+ unchecked {
135
+ ++j;
136
+ }
131
137
  }
132
138
 
133
139
  emit SetFundAccessLimits({
@@ -136,6 +142,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
136
142
  fundAccessLimitGroup: fundAccessLimitGroup,
137
143
  caller: msg.sender
138
144
  });
145
+ unchecked {
146
+ ++i;
147
+ }
139
148
  }
140
149
  }
141
150
 
@@ -171,7 +180,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
171
180
  uint256 numberOfData = data.length;
172
181
 
173
182
  // Iterate through the stored packed values and return the value of the matching currency.
174
- for (uint256 i; i < numberOfData; i++) {
183
+ for (uint256 i; i < numberOfData;) {
175
184
  // Set the data being iterated on.
176
185
  uint256 packedPayoutLimitData = data[i];
177
186
 
@@ -180,6 +189,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
180
189
  // forge-lint: disable-next-line(unsafe-typecast)
181
190
  return uint256(uint224(packedPayoutLimitData));
182
191
  }
192
+ unchecked {
193
+ ++i;
194
+ }
183
195
  }
184
196
  }
185
197
 
@@ -213,7 +225,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
213
225
  payoutLimits = new JBCurrencyAmount[](numberOfData);
214
226
 
215
227
  // Iterate through the packed values and format the returned value.
216
- for (uint256 i; i < numberOfData; i++) {
228
+ for (uint256 i; i < numberOfData;) {
217
229
  // Set the data being iterated on.
218
230
  uint256 packedPayoutLimitData = packedPayoutLimitsData[i];
219
231
 
@@ -225,6 +237,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
225
237
  // forge-lint: disable-next-line(unsafe-typecast)
226
238
  amount: uint224(packedPayoutLimitData)
227
239
  });
240
+ unchecked {
241
+ ++i;
242
+ }
228
243
  }
229
244
  }
230
245
 
@@ -257,7 +272,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
257
272
  uint256 numberOfData = packedSurplusAllowancesData.length;
258
273
 
259
274
  // Iterate through the stored packed values and format the returned value.
260
- for (uint256 i; i < numberOfData; i++) {
275
+ for (uint256 i; i < numberOfData;) {
261
276
  // Set the data being iterated on.
262
277
  uint256 packedSurplusAllowanceData = packedSurplusAllowancesData[i];
263
278
 
@@ -266,6 +281,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
266
281
  // forge-lint: disable-next-line(unsafe-typecast)
267
282
  return uint256(uint224(packedSurplusAllowanceData));
268
283
  }
284
+ unchecked {
285
+ ++i;
286
+ }
269
287
  }
270
288
  }
271
289
 
@@ -301,7 +319,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
301
319
  surplusAllowances = new JBCurrencyAmount[](numberOfData);
302
320
 
303
321
  // Iterate through the stored packed values and format the returned value.
304
- for (uint256 i; i < numberOfData; i++) {
322
+ for (uint256 i; i < numberOfData;) {
305
323
  // Set the data being iterated on.
306
324
  uint256 packedSurplusAllowanceData = packedSurplusAllowancesData[i];
307
325
 
@@ -313,6 +331,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
313
331
  // forge-lint: disable-next-line(unsafe-typecast)
314
332
  amount: uint224(packedSurplusAllowanceData)
315
333
  });
334
+ unchecked {
335
+ ++i;
336
+ }
316
337
  }
317
338
  }
318
339
  }
@@ -207,9 +207,12 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
207
207
  STORE.recordAccountingContextOf({projectId: projectId, contexts: accountingContexts});
208
208
 
209
209
  // Emit an event for each accounting context.
210
- for (uint256 i; i < accountingContexts.length; i++) {
210
+ for (uint256 i; i < accountingContexts.length;) {
211
211
  // slither-disable-next-line reentrancy-events
212
212
  emit SetAccountingContext({projectId: projectId, context: accountingContexts[i], caller: _msgSender()});
213
+ unchecked {
214
+ ++i;
215
+ }
213
216
  }
214
217
  }
215
218
 
@@ -582,11 +585,15 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
582
585
  // Get a reference to the beneficiary's balance before the payment.
583
586
  uint256 beneficiaryBalanceBefore = TOKENS.totalBalanceOf({holder: beneficiary, projectId: projectId});
584
587
 
588
+ // Accept the funds.
589
+ uint256 acceptedAmount =
590
+ _acceptFundsFor({projectId: projectId, token: token, amount: amount, metadata: metadata});
591
+
585
592
  // Pay the project.
586
593
  _pay({
587
594
  projectId: projectId,
588
595
  token: token,
589
- amount: _acceptFundsFor(projectId, token, amount, metadata),
596
+ amount: acceptedAmount,
590
597
  payer: _msgSender(),
591
598
  beneficiary: beneficiary,
592
599
  memo: memo,
@@ -627,7 +634,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
627
634
 
628
635
  // Process each fee. Re-read the index and array length from storage each iteration to account for reentrant
629
636
  // calls that may have already advanced the index or cleaned up the array.
630
- for (uint256 i; i < count; i++) {
637
+ for (uint256 i; i < count;) {
631
638
  // Read the current index from storage (not a cached value) to prevent reentrancy from
632
639
  // causing double-processing.
633
640
  uint256 currentIndex = _nextHeldFeeIndexOf[projectId][token];
@@ -662,6 +669,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
662
669
  feeTerminal: feeTerminal,
663
670
  wasHeld: true
664
671
  });
672
+ unchecked {
673
+ ++i;
674
+ }
665
675
  }
666
676
 
667
677
  // If all held fees have been processed, reset the array and index entirely to bound storage growth.
@@ -850,8 +860,11 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
850
860
  heldFees = new JBFee[](count);
851
861
 
852
862
  // Copy the fees into the array.
853
- for (uint256 i; i < count; i++) {
863
+ for (uint256 i; i < count;) {
854
864
  heldFees[i] = _heldFeesOf[projectId][token][startIndex + i];
865
+ unchecked {
866
+ ++i;
867
+ }
855
868
  }
856
869
  }
857
870
 
@@ -1134,13 +1147,16 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1134
1147
  // Keep a reference to the cash out tax rate being used.
1135
1148
  uint256 cashOutTaxRate;
1136
1149
 
1150
+ // Cache whether the beneficiary is feeless.
1151
+ bool beneficiaryIsFeeless = _isFeeless(beneficiary);
1152
+
1137
1153
  // Record the cash out.
1138
1154
  (ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications) = STORE.recordCashOutFor({
1139
1155
  holder: holder,
1140
1156
  projectId: projectId,
1141
1157
  cashOutCount: cashOutCount,
1142
1158
  tokenToReclaim: tokenToReclaim,
1143
- beneficiaryIsFeeless: _isFeeless(beneficiary),
1159
+ beneficiaryIsFeeless: beneficiaryIsFeeless,
1144
1160
  metadata: metadata
1145
1161
  });
1146
1162
 
@@ -1156,7 +1172,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1156
1172
  // Send the reclaimed funds to the beneficiary.
1157
1173
  if (reclaimAmount != 0) {
1158
1174
  // Determine if a fee should be taken. Fees are not taken if the beneficiary is feeless.
1159
- if (!_isFeeless(beneficiary)) {
1175
+ if (!beneficiaryIsFeeless) {
1160
1176
  if (cashOutTaxRate != 0) {
1161
1177
  // Non-zero tax: fees apply to the full reclaim amount.
1162
1178
  amountEligibleForFees += reclaimAmount;
@@ -1404,12 +1420,17 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1404
1420
  });
1405
1421
 
1406
1422
  // slither-disable-next-line calls-loop
1407
- for (uint256 i; i < specifications.length; i++) {
1423
+ for (uint256 i; i < specifications.length;) {
1408
1424
  // Set the specification being iterated on.
1409
1425
  JBCashOutHookSpecification memory specification = specifications[i];
1410
1426
 
1411
1427
  // A noop specification is informational only and doesn't trigger the hook.
1412
- if (specification.noop) continue;
1428
+ if (specification.noop) {
1429
+ unchecked {
1430
+ ++i;
1431
+ }
1432
+ continue;
1433
+ }
1413
1434
 
1414
1435
  // Get the fee for the specified amount.
1415
1436
  uint256 specificationAmountFee = _isFeeless(address(specification.hook))
@@ -1454,6 +1475,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1454
1475
  fee: specificationAmountFee,
1455
1476
  caller: _msgSender()
1456
1477
  });
1478
+ unchecked {
1479
+ ++i;
1480
+ }
1457
1481
  }
1458
1482
  }
1459
1483
 
@@ -1494,12 +1518,17 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1494
1518
 
1495
1519
  // Fulfill each specification through their pay hooks.
1496
1520
  // slither-disable-next-line calls-loop
1497
- for (uint256 i; i < specifications.length; i++) {
1521
+ for (uint256 i; i < specifications.length;) {
1498
1522
  // Set the specification being iterated on.
1499
1523
  JBPayHookSpecification memory specification = specifications[i];
1500
1524
 
1501
1525
  // A noop specification is informational only and doesn't trigger the hook.
1502
- if (specification.noop) continue;
1526
+ if (specification.noop) {
1527
+ unchecked {
1528
+ ++i;
1529
+ }
1530
+ continue;
1531
+ }
1503
1532
 
1504
1533
  // Pass the correct token `forwardedAmount` to the hook.
1505
1534
  context.forwardedAmount = JBTokenAmount({
@@ -1532,6 +1561,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1532
1561
  specificationAmount: specification.amount,
1533
1562
  caller: _msgSender()
1534
1563
  });
1564
+ unchecked {
1565
+ ++i;
1566
+ }
1535
1567
  }
1536
1568
  }
1537
1569
 
@@ -1674,6 +1706,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1674
1706
  }
1675
1707
 
1676
1708
  /// @notice Returns held fees to the project who paid them based on the specified amount.
1709
+ /// @dev Fee rounding during partial replenishment can zero out dust-level fee entries (< 40 wei at 2.5% fee).
1710
+ /// This is accepted behavior — dust fees are economically insignificant.
1677
1711
  /// @param projectId The project held fees are being returned to.
1678
1712
  /// @param token The token that the held fees are in.
1679
1713
  /// @param amount The amount to base the calculation on, as a fixed point number with the same number of decimals
@@ -1700,7 +1734,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1700
1734
  uint256 newStartIndex = startIndex;
1701
1735
 
1702
1736
  // Process each fee.
1703
- for (uint256 i; i < count; i++) {
1737
+ for (uint256 i; i < count;) {
1704
1738
  // Save the fee being iterated on.
1705
1739
  JBFee memory heldFee = _heldFeesOf[projectId][token][startIndex + i];
1706
1740
 
@@ -1734,6 +1768,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1734
1768
  leftoverAmount = 0;
1735
1769
  }
1736
1770
  }
1771
+ unchecked {
1772
+ ++i;
1773
+ }
1737
1774
  }
1738
1775
 
1739
1776
  // Update the next held fee index.
@@ -1766,6 +1803,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1766
1803
  internal
1767
1804
  returns (uint256 amountPaidOut)
1768
1805
  {
1806
+ // Cache the message sender.
1807
+ address sender = _msgSender();
1808
+
1769
1809
  // Keep a reference to the ruleset.
1770
1810
  JBRuleset memory ruleset;
1771
1811
 
@@ -1799,7 +1839,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1799
1839
  token: token,
1800
1840
  rulesetId: ruleset.id,
1801
1841
  amount: amountPaidOut,
1802
- caller: _msgSender()
1842
+ caller: sender
1803
1843
  });
1804
1844
 
1805
1845
  // Send any leftover funds to the project owner and update the fee tracking accordingly.
@@ -1826,7 +1866,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1826
1866
  amount: leftoverPayoutAmount - fee,
1827
1867
  fee: fee,
1828
1868
  reason: reason,
1829
- caller: _msgSender()
1869
+ caller: sender
1830
1870
  });
1831
1871
 
1832
1872
  // Add balance back to the project.
@@ -1852,7 +1892,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1852
1892
  amountPaidOut: amountPaidOut,
1853
1893
  fee: feeTaken,
1854
1894
  netLeftoverPayoutAmount: leftoverPayoutAmount,
1855
- caller: _msgSender()
1895
+ caller: sender
1856
1896
  });
1857
1897
  }
1858
1898
 
@@ -160,7 +160,7 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
160
160
 
161
161
  // Returns true for empty permission arrays by design (vacuous truth). An empty set of
162
162
  // required permissions is trivially satisfied. Callers should validate non-empty permission arrays if needed.
163
- for (uint256 i; i < permissionIds.length; i++) {
163
+ for (uint256 i; i < permissionIds.length;) {
164
164
  // Set the permission being iterated on.
165
165
  uint256 permissionId = permissionIds[i];
166
166
 
@@ -176,6 +176,9 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
176
176
  ) {
177
177
  return false;
178
178
  }
179
+ unchecked {
180
+ ++i;
181
+ }
179
182
  }
180
183
  return true;
181
184
  }
@@ -210,29 +213,23 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
210
213
  // Indexes above 255 don't exist
211
214
  if (permissionId > 255) revert JBPermissions_PermissionIdOutOfBounds(permissionId);
212
215
 
216
+ // Cache both permission slots upfront to avoid redundant storage reads.
217
+ uint256 projectPermissions = permissionsOf[operator][account][projectId];
218
+ uint256 wildcardPermissions =
219
+ includeWildcardProjectId ? permissionsOf[operator][account][WILDCARD_PROJECT_ID] : 0;
220
+
213
221
  // If the ROOT permission is set and should be included, return true.
214
222
  if (
215
223
  includeRoot
216
- && (_includesPermission({
217
- permissions: permissionsOf[operator][account][projectId], permissionId: JBPermissionIds.ROOT
218
- })
219
- || (includeWildcardProjectId
220
- && _includesPermission({
221
- permissions: permissionsOf[operator][account][WILDCARD_PROJECT_ID],
222
- permissionId: JBPermissionIds.ROOT
223
- })))
224
+ && (_includesPermission({permissions: projectPermissions, permissionId: JBPermissionIds.ROOT})
225
+ || _includesPermission({permissions: wildcardPermissions, permissionId: JBPermissionIds.ROOT}))
224
226
  ) {
225
227
  return true;
226
228
  }
227
229
 
228
230
  // Otherwise return the t/f flag of the specified id.
229
- return _includesPermission({
230
- permissions: permissionsOf[operator][account][projectId], permissionId: permissionId
231
- })
232
- || (includeWildcardProjectId
233
- && _includesPermission({
234
- permissions: permissionsOf[operator][account][WILDCARD_PROJECT_ID], permissionId: permissionId
235
- }));
231
+ return _includesPermission({permissions: projectPermissions, permissionId: permissionId})
232
+ || _includesPermission({permissions: wildcardPermissions, permissionId: permissionId});
236
233
  }
237
234
 
238
235
  //*********************************************************************//
@@ -251,13 +248,16 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
251
248
  /// @param permissionIds The IDs of the permissions to pack.
252
249
  /// @return packed The packed value.
253
250
  function _packedPermissions(uint8[] calldata permissionIds) internal pure returns (uint256 packed) {
254
- for (uint256 i; i < permissionIds.length; i++) {
251
+ for (uint256 i; i < permissionIds.length;) {
255
252
  // Set the permission being iterated on.
256
253
  uint256 permissionId = permissionIds[i];
257
254
 
258
255
  // Turn on the bit at the ID.
259
256
  // forge-lint: disable-next-line(incorrect-shift)
260
257
  packed |= 1 << permissionId;
258
+ unchecked {
259
+ ++i;
260
+ }
261
261
  }
262
262
  }
263
263
  }