@bananapus/core-v6 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADMINISTRATION.md +43 -13
- package/ARCHITECTURE.md +62 -137
- package/AUDIT_INSTRUCTIONS.md +149 -428
- package/CHANGELOG.md +73 -0
- package/README.md +90 -201
- package/RISKS.md +27 -12
- package/SKILLS.md +31 -441
- package/STYLE_GUIDE.md +52 -19
- package/USER_JOURNEYS.md +76 -627
- package/package.json +1 -2
- package/references/entrypoints.md +160 -0
- package/references/types-errors-events.md +297 -0
- package/script/Deploy.s.sol +7 -2
- package/script/DeployPeriphery.s.sol +51 -4
- package/src/JBController.sol +45 -17
- package/src/JBDirectory.sol +26 -13
- package/src/JBFundAccessLimits.sol +28 -7
- package/src/JBMultiTerminal.sol +180 -86
- package/src/JBPermissions.sol +17 -17
- package/src/JBRulesets.sol +82 -23
- package/src/JBSplits.sol +31 -12
- package/src/JBTerminalStore.sol +137 -53
- package/src/JBTokens.sol +5 -2
- package/src/abstract/JBControlled.sol +10 -3
- package/src/abstract/JBPermissioned.sol +1 -1
- package/src/interfaces/IJBRulesetDataHook.sol +5 -4
- package/src/libraries/JBCashOuts.sol +1 -1
- package/src/libraries/JBConstants.sol +1 -1
- package/src/libraries/JBCurrencyIds.sol +1 -1
- package/src/libraries/JBFees.sol +1 -1
- package/src/libraries/JBFixedPointNumber.sol +1 -1
- package/src/libraries/JBMetadataResolver.sol +5 -2
- package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
- package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
- package/src/libraries/JBSplitGroupIds.sol +1 -1
- package/src/libraries/JBSurplus.sol +5 -2
- package/src/structs/JBSplit.sol +4 -1
- package/test/TestForwardedTokenConsumption.sol +419 -0
- package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
- package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
- package/CHANGE_LOG.md +0 -479
package/src/JBTerminalStore.sol
CHANGED
|
@@ -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;
|
|
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
|
|
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;
|
|
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 >
|
|
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
|
-
|
|
342
|
-
|
|
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
|
-
//
|
|
389
|
-
|
|
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
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
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,
|
|
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
|
|
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 >
|
|
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
|
|
905
|
-
// It can set
|
|
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
|
-
//
|
|
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 =
|
|
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,
|
|
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;
|
|
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,
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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 (
|
|
258
|
-
revert JBTokens_OverflowAlert(
|
|
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
|
|
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
|
-
|
|
45
|
-
|
|
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
|
}
|
|
@@ -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
|
|
21
|
-
///
|
|
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
|
|
30
|
-
uint256
|
|
30
|
+
uint256 effectiveCashOutCount,
|
|
31
|
+
uint256 effectiveTotalSupply,
|
|
31
32
|
JBCashOutHookSpecification[] memory hookSpecifications
|
|
32
33
|
);
|
|
33
34
|
|
package/src/libraries/JBFees.sol
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity
|
|
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;
|
|
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;
|
|
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.
|