@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 +1 -1
- package/src/JBController.sol +34 -14
- package/src/JBDirectory.sol +25 -13
- package/src/JBFundAccessLimits.sol +28 -7
- package/src/JBMultiTerminal.sol +54 -14
- package/src/JBPermissions.sol +17 -17
- package/src/JBRulesets.sol +95 -64
- package/src/JBSplits.sol +31 -12
- package/src/JBTerminalStore.sol +115 -42
- package/src/JBTokens.sol +5 -2
- package/src/abstract/JBControlled.sol +4 -3
- package/src/libraries/JBMetadataResolver.sol +4 -1
- package/src/libraries/JBPayoutSplitGroupLib.sol +4 -1
- package/src/libraries/JBSurplus.sol +4 -1
- package/test/TestForwardedTokenConsumption.sol +1 -0
- package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +0 -1
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +0 -1
package/package.json
CHANGED
package/src/JBController.sol
CHANGED
|
@@ -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:
|
|
532
|
-
|| _hasDataHookMintPermissionFor(projectId, ruleset,
|
|
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() && !
|
|
539
|
-
&&
|
|
540
|
-
|
|
541
|
-
|
|
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:
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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 :
|
|
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:
|
|
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:
|
|
1135
|
+
caller: messageSender
|
|
1119
1136
|
});
|
|
1137
|
+
unchecked {
|
|
1138
|
+
++i;
|
|
1139
|
+
}
|
|
1120
1140
|
}
|
|
1121
1141
|
}
|
|
1122
1142
|
|
package/src/JBDirectory.sol
CHANGED
|
@@ -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(
|
|
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(
|
|
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;
|
|
239
|
-
for (uint256 j = i + 1; j < terminals.length;
|
|
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[]
|
|
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;
|
|
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[]
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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
|
}
|
package/src/JBMultiTerminal.sol
CHANGED
|
@@ -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;
|
|
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:
|
|
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;
|
|
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;
|
|
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:
|
|
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 (!
|
|
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;
|
|
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)
|
|
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;
|
|
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)
|
|
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;
|
|
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:
|
|
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:
|
|
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:
|
|
1895
|
+
caller: sender
|
|
1856
1896
|
});
|
|
1857
1897
|
}
|
|
1858
1898
|
|
package/src/JBPermissions.sol
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
}
|