@bananapus/core-v6 0.0.15 → 0.0.17
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 +5 -1
- package/ARCHITECTURE.md +2 -1
- package/AUDIT_INSTRUCTIONS.md +342 -0
- package/CHANGE_LOG.md +375 -0
- package/README.md +6 -6
- package/RISKS.md +171 -50
- package/SKILLS.md +11 -6
- package/STYLE_GUIDE.md +16 -2
- package/USER_JOURNEYS.md +622 -0
- package/package.json +2 -2
- package/script/Deploy.s.sol +22 -13
- package/script/DeployPeriphery.s.sol +76 -52
- package/script/helpers/CoreDeploymentLib.sol +83 -35
- package/src/JBChainlinkV3PriceFeed.sol +1 -0
- package/src/JBController.sol +23 -3
- package/src/JBDeadline.sol +3 -0
- package/src/JBDirectory.sol +2 -1
- package/src/JBERC20.sol +12 -3
- package/src/JBFundAccessLimits.sol +12 -2
- package/src/JBMultiTerminal.sol +53 -10
- package/src/JBPermissions.sol +3 -0
- package/src/JBPrices.sol +8 -2
- package/src/JBProjects.sol +1 -1
- package/src/JBRulesets.sol +14 -0
- package/src/JBSplits.sol +14 -5
- package/src/JBTerminalStore.sol +57 -47
- package/src/JBTokens.sol +43 -4
- package/src/interfaces/IJBController.sol +6 -0
- package/src/interfaces/IJBPermitTerminal.sol +1 -0
- package/src/interfaces/IJBTerminalStore.sol +3 -0
- package/src/interfaces/IJBToken.sol +5 -0
- package/src/interfaces/IJBTokens.sol +13 -0
- package/src/libraries/JBFees.sol +2 -0
- package/src/libraries/JBMetadataResolver.sol +24 -7
- package/src/libraries/JBRulesetMetadataResolver.sol +21 -21
- package/src/structs/JBAccountingContext.sol +1 -0
- package/src/structs/JBAfterCashOutRecordedContext.sol +1 -0
- package/src/structs/JBAfterPayRecordedContext.sol +1 -0
- package/src/structs/JBBeforeCashOutRecordedContext.sol +5 -0
- package/src/structs/JBBeforePayRecordedContext.sol +1 -0
- package/src/structs/JBCashOutHookSpecification.sol +1 -0
- package/src/structs/JBCurrencyAmount.sol +1 -0
- package/src/structs/JBFee.sol +1 -0
- package/src/structs/JBFundAccessLimitGroup.sol +1 -0
- package/src/structs/JBPayHookSpecification.sol +1 -0
- package/src/structs/JBPermissionsData.sol +1 -0
- package/src/structs/JBRuleset.sol +1 -0
- package/src/structs/JBRulesetConfig.sol +1 -0
- package/src/structs/JBRulesetMetadata.sol +1 -0
- package/src/structs/JBRulesetWeightCache.sol +1 -0
- package/src/structs/JBRulesetWithMetadata.sol +1 -0
- package/src/structs/JBSingleAllowance.sol +1 -0
- package/src/structs/JBSplit.sol +1 -0
- package/src/structs/JBSplitGroup.sol +1 -0
- package/src/structs/JBSplitHookContext.sol +1 -0
- package/src/structs/JBTerminalConfig.sol +1 -0
- package/src/structs/JBTokenAmount.sol +1 -0
- package/test/ComprehensiveInvariant.t.sol +15 -2
- package/test/CoreExploitTests.t.sol +34 -1
- package/test/EconomicSimulation.t.sol +10 -2
- package/test/EntryPointPermutations.t.sol +17 -3
- package/test/FlashLoanAttacks.t.sol +12 -1
- package/test/PermissionEscalation.t.sol +53 -10
- package/test/RulesetTransitions.t.sol +15 -1
- package/test/SplitLoopTests.t.sol +25 -2
- package/test/TestAccessToFunds.sol +17 -2
- package/test/TestAuditResponseDesignProofs.sol +434 -0
- package/test/TestCashOut.sol +15 -1
- package/test/TestCashOutCountFor.sol +1 -1
- package/test/TestCashOutHooks.sol +47 -25
- package/test/TestCashOutTimingEdge.sol +13 -1
- package/test/TestDataHookFuzzing.sol +520 -0
- package/test/TestDurationUnderflow.sol +13 -1
- package/test/TestFeeFreeCashOutBypass.sol +617 -0
- package/test/TestFeeProcessingFailure.sol +16 -1
- package/test/TestFees.sol +14 -1
- package/test/TestInterfaceSupport.sol +20 -1
- package/test/TestJBERC20Inheritance.sol +11 -1
- package/test/TestL2SequencerPriceFeed.sol +292 -0
- package/test/TestLaunchProject.sol +13 -1
- package/test/TestMetaTx.sol +15 -1
- package/test/TestMetadataOffsetOverflow.sol +179 -0
- package/test/TestMetadataParserLib.sol +37 -4
- package/test/TestMigrationHeldFees.sol +16 -1
- package/test/TestMintTokensOf.sol +14 -1
- package/test/TestMultiTerminalSurplus.sol +348 -0
- package/test/TestMultiTokenSurplus.sol +14 -1
- package/test/TestMultipleAccessLimits.sol +23 -1
- package/test/TestPayBurnRedeemFlow.sol +16 -1
- package/test/TestPayHooks.sol +33 -14
- package/test/TestPermissions.sol +20 -1
- package/test/TestPermissionsEdge.sol +5 -1
- package/test/TestPermit2DataHook.t.sol +360 -0
- package/test/TestPermit2Terminal.sol +36 -3
- package/test/TestRulesetQueueing.sol +23 -1
- package/test/TestRulesetQueuingStress.sol +20 -1
- package/test/TestRulesetWeightCaching.sol +127 -125
- package/test/TestSplits.sol +23 -1
- package/test/TestTerminalMigration.sol +11 -1
- package/test/TestTokenFlow.sol +18 -1
- package/test/TestWeightCacheStaleAfterRejection.sol +15 -1
- package/test/WeirdTokenTests.t.sol +54 -1
- package/test/fork/TestChainlinkPriceFeedFork.sol +6 -1
- package/test/formal/BondingCurveProperties.t.sol +8 -1
- package/test/formal/FeeProperties.t.sol +7 -1
- package/test/helpers/JBTest.sol +1 -1
- package/test/helpers/TestBaseWorkflow.sol +84 -1
- package/test/invariants/Phase3DeepInvariant.t.sol +13 -2
- package/test/invariants/RulesetsInvariant.t.sol +12 -2
- package/test/invariants/TerminalStoreInvariant.t.sol +11 -2
- package/test/invariants/TokensInvariant.t.sol +13 -2
- package/test/invariants/handlers/ComprehensiveHandler.sol +19 -1
- package/test/invariants/handlers/EconomicHandler.sol +31 -1
- package/test/invariants/handlers/Phase3Handler.sol +31 -1
- package/test/invariants/handlers/RulesetsHandler.sol +5 -1
- package/test/invariants/handlers/TerminalStoreHandler.sol +6 -1
- package/test/invariants/handlers/TokensHandler.sol +1 -1
- package/test/mock/MockERC20.sol +0 -2
- package/test/mock/MockMaliciousBeneficiary.sol +2 -1
- package/test/mock/MockMaliciousSplitHook.sol +2 -1
- package/test/mock/MockPriceFeed.sol +1 -1
- package/test/regression/HoldFeesCashOutReserved.t.sol +415 -0
- package/test/regression/WeightCacheBoundary.t.sol +291 -0
- package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +0 -1
- package/test/units/static/JBController/JBControllerSetup.sol +10 -1
- package/test/units/static/JBController/TestBurnTokensOf.sol +8 -1
- package/test/units/static/JBController/TestClaimTokensFor.sol +4 -1
- package/test/units/static/JBController/TestDeployErc20For.sol +7 -1
- package/test/units/static/JBController/TestLaunchProjectFor.sol +21 -1
- package/test/units/static/JBController/TestLaunchRulesetsFor.sol +21 -1
- package/test/units/static/JBController/TestMigrateController.sol +10 -1
- package/test/units/static/JBController/TestMintTokensOfUnits.sol +10 -1
- package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +4 -1
- package/test/units/static/JBController/TestReceiveMigrationFrom.sol +5 -1
- package/test/units/static/JBController/TestRulesetViews.sol +7 -1
- package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +21 -1
- package/test/units/static/JBController/TestSetSplitGroupsOf.sol +6 -1
- package/test/units/static/JBController/TestSetTokenFor.sol +13 -1
- package/test/units/static/JBController/TestSetUriOf.sol +5 -1
- package/test/units/static/JBController/TestTransferCreditsFrom.sol +11 -1
- package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +12 -1
- package/test/units/static/JBDirectory/JBDirectorySetup.sol +4 -1
- package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +5 -1
- package/test/units/static/JBDirectory/TestSetControllerOf.sol +11 -1
- package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +7 -1
- package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +11 -1
- package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +10 -1
- package/test/units/static/JBERC20/JBERC20Setup.sol +2 -1
- package/test/units/static/JBERC20/SigUtils.sol +2 -0
- package/test/units/static/JBERC20/TestInitialize.sol +1 -1
- package/test/units/static/JBERC20/TestName.sol +1 -1
- package/test/units/static/JBERC20/TestNonces.sol +3 -1
- package/test/units/static/JBERC20/TestSymbol.sol +1 -1
- package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +2 -1
- package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +2 -1
- package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +1 -1
- package/test/units/static/JBFees/TestFeesFuzz.sol +1 -1
- package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +0 -1
- package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +0 -1
- package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +3 -1
- package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +4 -1
- package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +4 -1
- package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +8 -1
- package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +8 -1
- package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +4 -1
- package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +7 -1
- package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +1 -1
- package/test/units/static/JBMetadataResolver/TestMetadataResolverEdgeCases.sol +2 -1
- package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +2 -1
- package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +12 -1
- package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +9 -1
- package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +18 -2
- package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +44 -9
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +48 -23
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +18 -2
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +13 -3
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +21 -4
- package/test/units/static/JBMultiTerminal/TestPay.sol +35 -7
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +206 -19
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +15 -1
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +297 -1
- package/test/units/static/JBPermissions/JBPermissionsSetup.sol +2 -1
- package/test/units/static/JBPermissions/TestHasPermission.sol +1 -1
- package/test/units/static/JBPermissions/TestHasPermissions.sol +1 -1
- package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +3 -1
- package/test/units/static/JBPrices/JBPricesSetup.sol +6 -1
- package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +6 -1
- package/test/units/static/JBPrices/TestPricePerUnitOf.sol +4 -1
- package/test/units/static/JBPrices/TestPrices.sol +4 -1
- package/test/units/static/JBProjects/JBProjectsSetup.sol +2 -1
- package/test/units/static/JBProjects/TestCreateFor.sol +3 -1
- package/test/units/static/JBProjects/TestInitialProject.sol +2 -1
- package/test/units/static/JBProjects/TestInterfaces.sol +0 -1
- package/test/units/static/JBProjects/TestSetResolver.sol +2 -1
- package/test/units/static/JBProjects/TestTokenUri.sol +3 -1
- package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +9 -1
- package/test/units/static/JBRulesets/JBRulesetsSetup.sol +3 -1
- package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +9 -1
- package/test/units/static/JBRulesets/TestCurrentOf.sol +10 -1
- package/test/units/static/JBRulesets/TestGetRulesetOf.sol +7 -1
- package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +9 -1
- package/test/units/static/JBRulesets/TestRulesets.sol +12 -1
- package/test/units/static/JBRulesets/TestRulesetsOf.sol +1 -1
- package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +10 -1
- package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +6 -1
- package/test/units/static/JBSplits/JBSplitsSetup.sol +3 -1
- package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +63 -13
- package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +8 -1
- package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +6 -1
- package/test/units/static/JBSplits/TestSplitsOf.sol +1 -1
- package/test/units/static/JBSplits/TestSplitsPacking.sol +5 -2
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +3 -1
- package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +5 -1
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +14 -1
- package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +14 -1
- package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +3 -1
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +92 -1
- package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +15 -1
- package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +13 -1
- package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +8 -1
- package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +16 -1
- package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +15 -1
- package/test/units/static/JBTokens/JBTokensSetup.sol +5 -1
- package/test/units/static/JBTokens/TestBurnFrom.sol +4 -1
- package/test/units/static/JBTokens/TestClaimTokensFor.sol +4 -1
- package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +4 -1
- package/test/units/static/JBTokens/TestMintFor.sol +4 -1
- package/test/units/static/JBTokens/TestSetTokenFor.sol +4 -1
- package/test/units/static/JBTokens/TestTotalBalanceOf.sol +1 -1
- package/test/units/static/JBTokens/TestTotalSupplyOf.sol +1 -1
- package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +3 -1
|
@@ -177,6 +177,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
177
177
|
|
|
178
178
|
// If the currencies match, return the value.
|
|
179
179
|
if (currency == packedPayoutLimitData >> 224) {
|
|
180
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
180
181
|
return uint256(uint224(packedPayoutLimitData));
|
|
181
182
|
}
|
|
182
183
|
}
|
|
@@ -217,8 +218,12 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
217
218
|
uint256 packedPayoutLimitData = packedPayoutLimitsData[i];
|
|
218
219
|
|
|
219
220
|
// The limit amount is in bits 0-223. The currency is in bits 224-255.
|
|
221
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
220
222
|
payoutLimits[i] = JBCurrencyAmount({
|
|
221
|
-
|
|
223
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
224
|
+
currency: uint32(packedPayoutLimitData >> 224),
|
|
225
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
226
|
+
amount: uint224(packedPayoutLimitData)
|
|
222
227
|
});
|
|
223
228
|
}
|
|
224
229
|
}
|
|
@@ -258,6 +263,7 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
258
263
|
|
|
259
264
|
// If the currencies match, return the value.
|
|
260
265
|
if (currency == packedSurplusAllowanceData >> 224) {
|
|
266
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
261
267
|
return uint256(uint224(packedSurplusAllowanceData));
|
|
262
268
|
}
|
|
263
269
|
}
|
|
@@ -300,8 +306,12 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
300
306
|
uint256 packedSurplusAllowanceData = packedSurplusAllowancesData[i];
|
|
301
307
|
|
|
302
308
|
// The limit is in bits 0-223. The currency is in bits 224-255.
|
|
309
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
303
310
|
surplusAllowances[i] = JBCurrencyAmount({
|
|
304
|
-
|
|
311
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
312
|
+
currency: uint32(packedSurplusAllowanceData >> 224),
|
|
313
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
314
|
+
amount: uint224(packedSurplusAllowanceData)
|
|
305
315
|
});
|
|
306
316
|
}
|
|
307
317
|
}
|
package/src/JBMultiTerminal.sol
CHANGED
|
@@ -136,6 +136,18 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
136
136
|
/// @custom:param projectId The ID of the project to get a list of accepted tokens for.
|
|
137
137
|
mapping(uint256 projectId => JBAccountingContext[]) internal _accountingContextsOf;
|
|
138
138
|
|
|
139
|
+
/// @notice The cumulative amount of fee-free intra-terminal payouts a project has received for a given token.
|
|
140
|
+
/// @dev Incremented each time a fee-free payout lands (same terminal, no fee charged). During cashout with
|
|
141
|
+
/// `cashOutTaxRate == 0`, fees are applied only up to this amount, then decremented. This prevents a round-trip
|
|
142
|
+
/// fee bypass (intra-terminal payout → zero-tax cashout) while scoping the fee precisely to the fee-free inflow
|
|
143
|
+
/// — legitimate cashouts beyond this amount remain fee-free.
|
|
144
|
+
/// @dev WARNING: This accumulator persists across rulesets and cannot be cleared. Once a fee-free payout
|
|
145
|
+
/// increments it, the balance remains until consumed by a zero-tax cashout. There is no admin function to reset
|
|
146
|
+
/// it. Projects switching from zero-tax to non-zero-tax rulesets will carry forward any unconsumed balance.
|
|
147
|
+
/// @custom:param projectId The ID of the project that received the payout.
|
|
148
|
+
/// @custom:param token The token that was received.
|
|
149
|
+
mapping(uint256 projectId => mapping(address token => uint256)) internal _feeFreeSurplusOf;
|
|
150
|
+
|
|
139
151
|
/// @notice Fees that are being held for each project.
|
|
140
152
|
/// @dev Projects can temporarily hold fees and unlock them later by adding funds to the project's balance.
|
|
141
153
|
/// @dev Held fees can be processed at any time by this terminal's owner.
|
|
@@ -416,12 +428,21 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
416
428
|
revert JBMultiTerminal_RecipientProjectTerminalNotFound(split.projectId, token);
|
|
417
429
|
}
|
|
418
430
|
|
|
431
|
+
// Fees apply to fund egress, not intra-terminal accounting. When both projects share this terminal,
|
|
432
|
+
// funds stay within the contract (addToBalance or pay) so no fee is charged. This is intentional:
|
|
433
|
+
// the fee model taxes value leaving the protocol ecosystem, not internal rebalancing.
|
|
419
434
|
// This payout is eligible for a fee if the funds are leaving this contract and the receiving terminal isn't
|
|
420
|
-
// a
|
|
435
|
+
// a feeless address.
|
|
421
436
|
if (terminal != this && !_isFeeless(address(terminal))) {
|
|
422
437
|
netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
|
|
423
438
|
}
|
|
424
439
|
|
|
440
|
+
// Track the fee-free payout amount. During cashout at zero tax rate, fees apply
|
|
441
|
+
// only up to this accumulated amount, preventing round-trip fee bypass.
|
|
442
|
+
if (terminal == this) {
|
|
443
|
+
_feeFreeSurplusOf[split.projectId][token] += netPayoutAmount;
|
|
444
|
+
}
|
|
445
|
+
|
|
425
446
|
// Send the `projectId` in the metadata as a referral.
|
|
426
447
|
bytes memory metadata = bytes(abi.encodePacked(projectId));
|
|
427
448
|
|
|
@@ -541,6 +562,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
541
562
|
revert JBMultiTerminal_TerminalTokensIncompatible({projectId: projectId, token: token, terminal: to});
|
|
542
563
|
}
|
|
543
564
|
|
|
565
|
+
// Terminal migration intentionally does not transfer held fees. Held fees belong to the
|
|
566
|
+
// fee beneficiary (project #1), not the migrating project. They unlock after 28 days regardless of terminal.
|
|
544
567
|
// Record the migration in the store.
|
|
545
568
|
// slither-disable-next-line reentrancy-events
|
|
546
569
|
balance = STORE.recordTerminalMigration({projectId: projectId, token: token});
|
|
@@ -625,6 +648,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
625
648
|
}
|
|
626
649
|
|
|
627
650
|
/// @notice Process any fees that are being held for the project.
|
|
651
|
+
/// @dev Reentrancy safety: the loop re-reads `_nextHeldFeeIndexOf` from storage each iteration and advances the
|
|
652
|
+
/// index before the external `_processFee` call, so a reentrant call cannot double-process the same fee entry.
|
|
628
653
|
/// @param projectId The ID of the project to process held fees for.
|
|
629
654
|
/// @param token The token to process held fees for.
|
|
630
655
|
/// @param count The number of fees to process.
|
|
@@ -1049,6 +1074,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1049
1074
|
accountingContext: accountingContext,
|
|
1050
1075
|
balanceAccountingContexts: balanceAccountingContexts,
|
|
1051
1076
|
cashOutCount: cashOutCount,
|
|
1077
|
+
beneficiaryIsFeeless: _isFeeless(beneficiary),
|
|
1052
1078
|
metadata: metadata
|
|
1053
1079
|
});
|
|
1054
1080
|
}
|
|
@@ -1064,12 +1090,22 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1064
1090
|
|
|
1065
1091
|
// Send the reclaimed funds to the beneficiary.
|
|
1066
1092
|
if (reclaimAmount != 0) {
|
|
1067
|
-
// Determine if a fee should be taken. Fees are not
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1093
|
+
// Determine if a fee should be taken. Fees are not taken if the beneficiary is feeless.
|
|
1094
|
+
if (!_isFeeless(beneficiary)) {
|
|
1095
|
+
if (cashOutTaxRate != 0) {
|
|
1096
|
+
// Non-zero tax: fees apply to the full reclaim amount.
|
|
1097
|
+
amountEligibleForFees += reclaimAmount;
|
|
1098
|
+
reclaimAmount -= JBFees.feeAmountFrom({amountBeforeFee: reclaimAmount, feePercent: FEE});
|
|
1099
|
+
} else {
|
|
1100
|
+
// Zero tax: fees apply only up to the fee-free surplus (round-trip prevention).
|
|
1101
|
+
uint256 feeFreeSurplus = _feeFreeSurplusOf[projectId][tokenToReclaim];
|
|
1102
|
+
if (feeFreeSurplus != 0) {
|
|
1103
|
+
uint256 feeableAmount = reclaimAmount < feeFreeSurplus ? reclaimAmount : feeFreeSurplus;
|
|
1104
|
+
_feeFreeSurplusOf[projectId][tokenToReclaim] = feeFreeSurplus - feeableAmount;
|
|
1105
|
+
amountEligibleForFees += feeableAmount;
|
|
1106
|
+
reclaimAmount -= JBFees.feeAmountFrom({amountBeforeFee: feeableAmount, feePercent: FEE});
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1073
1109
|
}
|
|
1074
1110
|
|
|
1075
1111
|
// Subtract the fee from the reclaim amount.
|
|
@@ -1623,7 +1659,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1623
1659
|
internal
|
|
1624
1660
|
returns (uint256)
|
|
1625
1661
|
{
|
|
1626
|
-
//
|
|
1662
|
+
// Failed split payouts consume the payout limit by design. The try-catch prevents a single
|
|
1663
|
+
// split from DoS-ing the entire payout. Failed splits' amounts are returned to the project balance via
|
|
1664
|
+
// `_recordAddedBalanceFor`. Payout limit consumption is correct because the project authorized the
|
|
1665
|
+
// distribution.
|
|
1627
1666
|
// slither-disable-next-line reentrancy-events
|
|
1628
1667
|
try this.executePayout({
|
|
1629
1668
|
split: split, projectId: projectId, token: token, amount: amount, originalMessageSender: _msgSender()
|
|
@@ -1697,7 +1736,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1697
1736
|
? 0
|
|
1698
1737
|
: JBFees.feeAmountFrom({amountBeforeFee: leftoverPayoutAmount, feePercent: FEE});
|
|
1699
1738
|
|
|
1700
|
-
//
|
|
1739
|
+
// Failed owner transfer consumes the payout limit by design. Same pattern as split payouts:
|
|
1740
|
+
// the try-catch prevents revert, failed amount is returned to project balance, and the owner can retry
|
|
1741
|
+
// via addToBalanceOf or in the next cycle.
|
|
1701
1742
|
try this.executeTransferTo({addr: projectOwner, token: token, amount: leftoverPayoutAmount - fee}) {
|
|
1702
1743
|
if (fee > 0) {
|
|
1703
1744
|
amountEligibleForFees += leftoverPayoutAmount;
|
|
@@ -1834,6 +1875,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1834
1875
|
JBFee({
|
|
1835
1876
|
amount: amount,
|
|
1836
1877
|
beneficiary: beneficiary,
|
|
1878
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1837
1879
|
unlockTimestamp: uint48(block.timestamp + _FEE_HOLDING_SECONDS)
|
|
1838
1880
|
})
|
|
1839
1881
|
);
|
|
@@ -1877,7 +1919,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1877
1919
|
}
|
|
1878
1920
|
|
|
1879
1921
|
// If there's sufficient approval, transfer normally.
|
|
1880
|
-
if (IERC20(token).allowance(address(from), address(this)) >= amount) {
|
|
1922
|
+
if (IERC20(token).allowance({owner: address(from), spender: address(this)}) >= amount) {
|
|
1881
1923
|
return IERC20(token).safeTransferFrom({from: from, to: to, value: amount});
|
|
1882
1924
|
}
|
|
1883
1925
|
|
|
@@ -1885,6 +1927,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1885
1927
|
if (amount > type(uint160).max) revert JBMultiTerminal_OverflowAlert(amount, type(uint160).max);
|
|
1886
1928
|
|
|
1887
1929
|
// Otherwise we attempt to use the PERMIT2 method.
|
|
1930
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1888
1931
|
PERMIT2.transferFrom({from: from, to: to, amount: uint160(amount), token: token});
|
|
1889
1932
|
}
|
|
1890
1933
|
|
package/src/JBPermissions.sol
CHANGED
|
@@ -154,6 +154,8 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
|
|
|
154
154
|
uint256 operatorAccountWildcardProjectPermissions =
|
|
155
155
|
includeWildcardProjectId ? permissionsOf[operator][account][WILDCARD_PROJECT_ID] : 0;
|
|
156
156
|
|
|
157
|
+
// Returns true for empty permission arrays by design (vacuous truth). An empty set of
|
|
158
|
+
// required permissions is trivially satisfied. Callers should validate non-empty permission arrays if needed.
|
|
157
159
|
for (uint256 i; i < permissionIds.length; i++) {
|
|
158
160
|
// Set the permission being iterated on.
|
|
159
161
|
uint256 permissionId = permissionIds[i];
|
|
@@ -250,6 +252,7 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
|
|
|
250
252
|
uint256 permissionId = permissionIds[i];
|
|
251
253
|
|
|
252
254
|
// Turn on the bit at the ID.
|
|
255
|
+
// forge-lint: disable-next-line(incorrect-shift)
|
|
253
256
|
packed |= 1 << permissionId;
|
|
254
257
|
}
|
|
255
258
|
}
|
package/src/JBPrices.sol
CHANGED
|
@@ -15,8 +15,10 @@ import {IJBProjects} from "./interfaces/IJBProjects.sol";
|
|
|
15
15
|
|
|
16
16
|
/// @notice Manages and normalizes price feeds. Price feeds are contracts which return the "pricing currency" cost of 1
|
|
17
17
|
/// "unit currency".
|
|
18
|
-
/// @dev Price feeds are immutable once set and cannot be replaced or removed.
|
|
19
|
-
/// a
|
|
18
|
+
/// @dev Price feeds are immutable once set and cannot be replaced or removed. This prevents oracle manipulation via
|
|
19
|
+
/// admin-key attacks, but means a misconfigured or failing feed will cause operations using that currency pair to
|
|
20
|
+
/// revert (DoS, not fund loss). Select feeds carefully — recovery requires deploying a new JBPrices contract and
|
|
21
|
+
/// migrating projects.
|
|
20
22
|
contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBPrices {
|
|
21
23
|
//*********************************************************************//
|
|
22
24
|
// --------------------------- custom errors ------------------------- //
|
|
@@ -134,6 +136,10 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
|
|
|
134
136
|
: priceFeedFor[projectId][unitCurrency][pricingCurrency]);
|
|
135
137
|
}
|
|
136
138
|
|
|
139
|
+
// Price feed immutability is by design to prevent admin-key attacks on price oracles.
|
|
140
|
+
// If a feed fails, operations using that currency pair revert (DoS but not fund loss). Projects can use
|
|
141
|
+
// alternative currency pairs. A default feed for a currency pair prevents per-project overrides to ensure
|
|
142
|
+
// price consistency; projects should use unused currency IDs for custom pricing.
|
|
137
143
|
// Store the feed.
|
|
138
144
|
priceFeedFor[projectId][pricingCurrency][unitCurrency] = feed;
|
|
139
145
|
|
package/src/JBProjects.sol
CHANGED
|
@@ -74,7 +74,7 @@ contract JBProjects is ERC721, ERC2771Context, Ownable, IJBProjects {
|
|
|
74
74
|
emit Create({projectId: projectId, owner: owner, caller: _msgSender()});
|
|
75
75
|
|
|
76
76
|
// Mint the project.
|
|
77
|
-
_safeMint(owner, projectId);
|
|
77
|
+
_safeMint({to: owner, tokenId: projectId});
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
//*********************************************************************//
|
package/src/JBRulesets.sol
CHANGED
|
@@ -247,6 +247,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
247
247
|
// Calculate the weight cut multiple.
|
|
248
248
|
uint168 weightCutMultiple;
|
|
249
249
|
unchecked {
|
|
250
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
250
251
|
weightCutMultiple = uint168(startDistance / targetRuleset.duration);
|
|
251
252
|
}
|
|
252
253
|
|
|
@@ -352,6 +353,9 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
352
353
|
|
|
353
354
|
/// @notice The ruleset that is currently active for the specified project.
|
|
354
355
|
/// @dev If a current ruleset of the project is not found, returns an empty ruleset with all properties set to 0.
|
|
356
|
+
/// @dev The first cycle returns the stored ruleset directly (cycleNumber=1, original weight). Subsequent cycles
|
|
357
|
+
/// simulate cycling with weight decay via `_simulateCycledRulesetBasedOn`. Payout limits reset each cycle because
|
|
358
|
+
/// the terminal store keys usage by rulesetId, and each cycle produces a new simulated rulesetId.
|
|
355
359
|
/// @param projectId The ID of the project to get the current ruleset of.
|
|
356
360
|
/// @return ruleset The project's current ruleset.
|
|
357
361
|
function currentOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
|
|
@@ -955,26 +959,34 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
955
959
|
// slither-disable-next-line incorrect-equality
|
|
956
960
|
if (rulesetId == 0) return ruleset;
|
|
957
961
|
|
|
962
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
958
963
|
ruleset.id = uint48(rulesetId);
|
|
959
964
|
|
|
960
965
|
uint256 packedIntrinsicProperties = _packedIntrinsicPropertiesOf[projectId][rulesetId];
|
|
961
966
|
|
|
962
967
|
// `weight` in bits 0-111 bits.
|
|
968
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
963
969
|
ruleset.weight = uint112(packedIntrinsicProperties);
|
|
964
970
|
// `basedOnId` in bits 112-159 bits.
|
|
971
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
965
972
|
ruleset.basedOnId = uint48(packedIntrinsicProperties >> 112);
|
|
966
973
|
// `start` in bits 160-207 bits.
|
|
974
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
967
975
|
ruleset.start = uint48(packedIntrinsicProperties >> 160);
|
|
968
976
|
// `cycleNumber` in bits 208-255 bits.
|
|
977
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
969
978
|
ruleset.cycleNumber = uint48(packedIntrinsicProperties >> 208);
|
|
970
979
|
|
|
971
980
|
uint256 packedUserProperties = _packedUserPropertiesOf[projectId][rulesetId];
|
|
972
981
|
|
|
973
982
|
// approval hook in bits 0-159 bits.
|
|
983
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
974
984
|
ruleset.approvalHook = IJBRulesetApprovalHook(address(uint160(packedUserProperties)));
|
|
975
985
|
// `duration` in bits 160-191 bits.
|
|
986
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
976
987
|
ruleset.duration = uint32(packedUserProperties >> 160);
|
|
977
988
|
// weight cut percent in bits 192-223 bits.
|
|
989
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
978
990
|
ruleset.weightCutPercent = uint32(packedUserProperties >> 192);
|
|
979
991
|
|
|
980
992
|
ruleset.metadata = _metadataOf[projectId][rulesetId];
|
|
@@ -1021,9 +1033,11 @@ contract JBRulesets is JBControlled, IJBRulesets {
|
|
|
1021
1033
|
});
|
|
1022
1034
|
|
|
1023
1035
|
return JBRuleset({
|
|
1036
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1024
1037
|
cycleNumber: uint48(rulesetCycleNumber),
|
|
1025
1038
|
id: baseRuleset.id,
|
|
1026
1039
|
basedOnId: baseRuleset.basedOnId,
|
|
1040
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1027
1041
|
start: uint48(start),
|
|
1028
1042
|
duration: baseRuleset.duration,
|
|
1029
1043
|
weight: uint112(
|
package/src/JBSplits.sol
CHANGED
|
@@ -77,8 +77,9 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
77
77
|
|
|
78
78
|
/// @notice Sets a project's split groups.
|
|
79
79
|
/// @dev Only a project's controller can set its splits, unless the first 160 bits of the group's ID match
|
|
80
|
-
/// `msg.sender
|
|
81
|
-
///
|
|
80
|
+
/// `msg.sender` AND the upper 96 bits are non-zero, in which case the caller can set its own splits.
|
|
81
|
+
/// GroupIds with zero upper 96 bits (i.e. bare addresses) are reserved for protocol use (e.g. terminal
|
|
82
|
+
/// payout groups keyed by token address) and always require controller authorization.
|
|
82
83
|
/// @dev The new split groups must include any currently set splits that are locked.
|
|
83
84
|
/// @param projectId The ID of the project to set the split groups of.
|
|
84
85
|
/// @param rulesetId The ID of the ruleset the split groups should be active in. Send
|
|
@@ -101,9 +102,12 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
101
102
|
// Get a reference to the grouped split being iterated on.
|
|
102
103
|
JBSplitGroup memory splitGroup = splitGroups[i];
|
|
103
104
|
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
105
|
+
// Self-auth: lower 160 bits must match msg.sender AND upper 96 bits must be non-zero.
|
|
106
|
+
// GroupIds with zero upper bits are reserved for protocol use (e.g. terminal payout groups)
|
|
107
|
+
// and always require controller authorization to prevent token contracts from hijacking payouts.
|
|
108
|
+
bool isSelfManaged = splitGroup.groupId >> 160 != 0 && address(uint160(splitGroup.groupId)) == msg.sender;
|
|
109
|
+
|
|
110
|
+
if (!isSelfManaged && !controllerChecked) {
|
|
107
111
|
_onlyControllerOf(projectId);
|
|
108
112
|
controllerChecked = true;
|
|
109
113
|
}
|
|
@@ -271,10 +275,13 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
271
275
|
JBSplit memory split;
|
|
272
276
|
|
|
273
277
|
// `percent` in bits 0-31.
|
|
278
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
274
279
|
split.percent = uint32(packedSplitPart1);
|
|
275
280
|
// `projectId` in bits 32-95.
|
|
281
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
276
282
|
split.projectId = uint64(packedSplitPart1 >> 32);
|
|
277
283
|
// `beneficiary` in bits 96-255.
|
|
284
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
278
285
|
split.beneficiary = payable(address(uint160(packedSplitPart1 >> 96)));
|
|
279
286
|
|
|
280
287
|
// Get a reference to the second part of the split's packed data.
|
|
@@ -285,8 +292,10 @@ contract JBSplits is JBControlled, IJBSplits {
|
|
|
285
292
|
// `preferAddToBalance` in bit 0.
|
|
286
293
|
split.preferAddToBalance = packedSplitPart2 & 1 == 1;
|
|
287
294
|
// `lockedUntil` in bits 1-48.
|
|
295
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
288
296
|
split.lockedUntil = uint48(packedSplitPart2 >> 1);
|
|
289
297
|
// `hook` in bits 49-208.
|
|
298
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
290
299
|
split.hook = IJBSplitHook(address(uint160(packedSplitPart2 >> 49)));
|
|
291
300
|
}
|
|
292
301
|
|
package/src/JBTerminalStore.sol
CHANGED
|
@@ -156,6 +156,8 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
156
156
|
/// @param accountingContext The accounting context of the token being reclaimed by the cash out.
|
|
157
157
|
/// @param balanceAccountingContexts The accounting contexts of the tokens whose balances should contribute to the
|
|
158
158
|
/// surplus being reclaimed from.
|
|
159
|
+
/// @param beneficiaryIsFeeless Whether the cash out's beneficiary is a feeless address. Passed through to data
|
|
160
|
+
/// hooks so they can skip their own fees when value stays in the protocol (e.g. project-to-project routing).
|
|
159
161
|
/// @param metadata Bytes to send to the data hook, if the project's current ruleset specifies one.
|
|
160
162
|
/// @return ruleset The ruleset during the cash out was made during, as a `JBRuleset` struct. This ruleset will
|
|
161
163
|
/// have a cash out tax rate provided by the cash out hook if applicable.
|
|
@@ -170,6 +172,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
170
172
|
uint256 cashOutCount,
|
|
171
173
|
JBAccountingContext calldata accountingContext,
|
|
172
174
|
JBAccountingContext[] calldata balanceAccountingContexts,
|
|
175
|
+
bool beneficiaryIsFeeless,
|
|
173
176
|
bytes memory metadata
|
|
174
177
|
)
|
|
175
178
|
external
|
|
@@ -184,10 +187,9 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
184
187
|
// Get a reference to the project's current ruleset.
|
|
185
188
|
ruleset = RULESETS.currentOf(projectId);
|
|
186
189
|
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
uint256 currentSurplus = ruleset.useTotalSurplusForCashOuts()
|
|
190
|
+
// Store the current surplus in `reclaimAmount` temporarily to avoid allocating a separate local variable
|
|
191
|
+
// (saves one stack slot, which is needed to fit the 7th parameter without hitting stack-too-deep).
|
|
192
|
+
reclaimAmount = ruleset.useTotalSurplusForCashOuts()
|
|
191
193
|
? JBSurplus.currentSurplusOf({
|
|
192
194
|
projectId: projectId,
|
|
193
195
|
terminals: DIRECTORY.terminalsOf(projectId),
|
|
@@ -204,54 +206,59 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
204
206
|
targetCurrency: accountingContext.currency
|
|
205
207
|
});
|
|
206
208
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
209
|
+
// Scoped to keep `totalSupply` and `context` off the outer stack.
|
|
210
|
+
{
|
|
211
|
+
// Get the total number of outstanding project tokens.
|
|
212
|
+
uint256 totalSupply = IJBController(address(DIRECTORY.controllerOf(projectId)))
|
|
213
|
+
.totalTokenSupplyWithReservedTokensOf(projectId);
|
|
214
|
+
|
|
215
|
+
// Can't cash out more tokens than are in the supply.
|
|
216
|
+
if (cashOutCount > totalSupply) revert JBTerminalStore_InsufficientTokens(cashOutCount, totalSupply);
|
|
217
|
+
|
|
218
|
+
// SECURITY NOTE: The data hook has absolute control over cash-out economics.
|
|
219
|
+
// It can set totalSupply, cashOutCount, and cashOutTaxRate to arbitrary values,
|
|
220
|
+
// completely overriding the terminal's bonding curve math. For example, setting
|
|
221
|
+
// totalSupply = surplus makes reclaimAmount = cashOutCount, bypassing the curve.
|
|
222
|
+
// Project owners MUST audit their data hooks with the same rigor as the terminal.
|
|
223
|
+
|
|
224
|
+
// If the ruleset has a data hook which is enabled for cash outs, use it to derive a claim amount and memo.
|
|
225
|
+
if (ruleset.useDataHookForCashOut() && ruleset.dataHook() != address(0)) {
|
|
226
|
+
// Build the cash out context field-by-field to avoid stack-too-deep
|
|
227
|
+
// (the struct has 11 fields — a struct literal would require all values on the stack at once).
|
|
228
|
+
JBBeforeCashOutRecordedContext memory context;
|
|
229
|
+
context.terminal = msg.sender;
|
|
230
|
+
context.holder = holder;
|
|
231
|
+
context.projectId = projectId;
|
|
232
|
+
context.rulesetId = ruleset.id;
|
|
233
|
+
context.cashOutCount = cashOutCount;
|
|
234
|
+
context.totalSupply = totalSupply;
|
|
235
|
+
context.surplus = JBTokenAmount({
|
|
231
236
|
token: accountingContext.token,
|
|
232
|
-
value:
|
|
237
|
+
value: reclaimAmount, // reclaimAmount temporarily holds the current surplus.
|
|
233
238
|
decimals: accountingContext.decimals,
|
|
234
239
|
currency: accountingContext.currency
|
|
235
|
-
})
|
|
236
|
-
useTotalSurplus
|
|
237
|
-
cashOutTaxRate
|
|
238
|
-
|
|
239
|
-
|
|
240
|
+
});
|
|
241
|
+
context.useTotalSurplus = ruleset.useTotalSurplusForCashOuts();
|
|
242
|
+
context.cashOutTaxRate = ruleset.cashOutTaxRate();
|
|
243
|
+
context.beneficiaryIsFeeless = beneficiaryIsFeeless;
|
|
244
|
+
context.metadata = metadata;
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
(cashOutTaxRate, cashOutCount, totalSupply, hookSpecifications) =
|
|
247
|
+
IJBRulesetDataHook(ruleset.dataHook()).beforeCashOutRecordedWith(context);
|
|
248
|
+
} else {
|
|
249
|
+
cashOutTaxRate = ruleset.cashOutTaxRate();
|
|
250
|
+
}
|
|
246
251
|
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
reclaimAmount
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
// Calculate the reclaim amount. `reclaimAmount` currently holds the surplus — overwrite it with the
|
|
253
|
+
// result.
|
|
254
|
+
if (reclaimAmount != 0) {
|
|
255
|
+
reclaimAmount = JBCashOuts.cashOutFrom({
|
|
256
|
+
surplus: reclaimAmount,
|
|
257
|
+
cashOutCount: cashOutCount,
|
|
258
|
+
totalSupply: totalSupply,
|
|
259
|
+
cashOutTaxRate: cashOutTaxRate
|
|
260
|
+
});
|
|
261
|
+
}
|
|
255
262
|
}
|
|
256
263
|
|
|
257
264
|
// Keep a reference to the amount that should be added to the project's balance.
|
|
@@ -868,6 +875,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
868
875
|
terminal
|
|
869
876
|
][projectId][accountingContext.token][ruleset.cycleNumber][payoutLimit.currency];
|
|
870
877
|
if (remaining > type(uint224).max) revert JBTerminalStore_Uint224Overflow(remaining);
|
|
878
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
871
879
|
payoutLimit.amount = uint224(remaining);
|
|
872
880
|
}
|
|
873
881
|
|
|
@@ -877,6 +885,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
877
885
|
value: payoutLimit.amount, decimals: accountingContext.decimals, targetDecimals: targetDecimals
|
|
878
886
|
});
|
|
879
887
|
if (adjusted > type(uint224).max) revert JBTerminalStore_Uint224Overflow(adjusted);
|
|
888
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
880
889
|
payoutLimit.amount = uint224(adjusted);
|
|
881
890
|
}
|
|
882
891
|
|
|
@@ -894,6 +903,7 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
894
903
|
})
|
|
895
904
|
);
|
|
896
905
|
if (converted > type(uint224).max) revert JBTerminalStore_Uint224Overflow(converted);
|
|
906
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
897
907
|
payoutLimit.amount = uint224(converted);
|
|
898
908
|
}
|
|
899
909
|
|
package/src/JBTokens.sol
CHANGED
|
@@ -128,7 +128,7 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
// Burn the tokens.
|
|
131
|
-
if (tokensToBurn > 0) token.burn(holder, tokensToBurn);
|
|
131
|
+
if (tokensToBurn > 0) token.burn({account: holder, amount: tokensToBurn});
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/// @notice Redeem credits to claim tokens into a holder's wallet.
|
|
@@ -177,7 +177,7 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
// Mint the equivalent amount of the project's token for the holder.
|
|
180
|
-
token.mint(beneficiary, count);
|
|
180
|
+
token.mint({account: beneficiary, amount: count});
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/// @notice Deploys an ERC-20 token for a project. It will be used when claiming tokens.
|
|
@@ -211,7 +211,11 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
211
211
|
|
|
212
212
|
token = salt == bytes32(0)
|
|
213
213
|
? IJBToken(Clones.clone(address(TOKEN)))
|
|
214
|
-
: IJBToken(
|
|
214
|
+
: IJBToken(
|
|
215
|
+
Clones.cloneDeterministic({
|
|
216
|
+
implementation: address(TOKEN), salt: keccak256(abi.encode(msg.sender, salt))
|
|
217
|
+
})
|
|
218
|
+
);
|
|
215
219
|
|
|
216
220
|
// Store the token contract.
|
|
217
221
|
tokenOf[projectId] = token;
|
|
@@ -257,7 +261,7 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
257
261
|
if (tokensWereClaimed) {
|
|
258
262
|
// If tokens should be claimed, mint tokens into the holder's wallet.
|
|
259
263
|
// slither-disable-next-line reentrancy-events
|
|
260
|
-
token.mint(holder, count);
|
|
264
|
+
token.mint({account: holder, amount: count});
|
|
261
265
|
} else {
|
|
262
266
|
// Otherwise, add the tokens to their credits and the credit supply.
|
|
263
267
|
creditBalanceOf[holder][projectId] += count;
|
|
@@ -271,6 +275,9 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
271
275
|
|
|
272
276
|
/// @notice Set a project's token if not already set.
|
|
273
277
|
/// @dev Only a project's controller can set its token.
|
|
278
|
+
/// @dev If the provided ERC-20 has a pre-existing supply (minted outside this contract), that supply will be
|
|
279
|
+
/// included in `totalSupplyOf` and will dilute cash-out calculations for all token holders. Project owners are
|
|
280
|
+
/// responsible for ensuring the token's supply is appropriate before calling this function.
|
|
274
281
|
/// @param projectId The ID of the project to set the token of.
|
|
275
282
|
/// @param token The new token's address.
|
|
276
283
|
function setTokenFor(uint256 projectId, IJBToken token) external override onlyControllerOf(projectId) {
|
|
@@ -298,6 +305,38 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
298
305
|
emit SetToken({projectId: projectId, token: token, caller: msg.sender});
|
|
299
306
|
}
|
|
300
307
|
|
|
308
|
+
/// @notice Sets the name and symbol of a project's token.
|
|
309
|
+
/// @dev Only a project's controller can set the token's name and symbol.
|
|
310
|
+
/// @param projectId The ID of the project whose token is being updated.
|
|
311
|
+
/// @param name The new name.
|
|
312
|
+
/// @param symbol The new symbol.
|
|
313
|
+
function setTokenMetadataFor(
|
|
314
|
+
uint256 projectId,
|
|
315
|
+
string calldata name,
|
|
316
|
+
string calldata symbol
|
|
317
|
+
)
|
|
318
|
+
external
|
|
319
|
+
override
|
|
320
|
+
onlyControllerOf(projectId)
|
|
321
|
+
{
|
|
322
|
+
// Get a reference to the project's current token.
|
|
323
|
+
IJBToken token = tokenOf[projectId];
|
|
324
|
+
|
|
325
|
+
// The project must have a token contract attached.
|
|
326
|
+
if (token == IJBToken(address(0))) revert JBTokens_TokenNotFound();
|
|
327
|
+
|
|
328
|
+
// There must be a name.
|
|
329
|
+
if (bytes(name).length == 0) revert JBTokens_EmptyName();
|
|
330
|
+
|
|
331
|
+
// There must be a symbol.
|
|
332
|
+
if (bytes(symbol).length == 0) revert JBTokens_EmptySymbol();
|
|
333
|
+
|
|
334
|
+
emit SetTokenMetadata({projectId: projectId, name: name, symbol: symbol, caller: msg.sender});
|
|
335
|
+
|
|
336
|
+
// Set the name and symbol.
|
|
337
|
+
token.setMetadata({name: name, symbol: symbol});
|
|
338
|
+
}
|
|
339
|
+
|
|
301
340
|
/// @notice Allows a holder to transfer credits to another account.
|
|
302
341
|
/// @dev Only a project's controller can transfer credits for that project.
|
|
303
342
|
/// @param holder The address to transfer credits from.
|
|
@@ -345,6 +345,12 @@ interface IJBController is IERC165, IJBProjectUriRegistry, IJBDirectoryAccessCon
|
|
|
345
345
|
/// @param token The new token's address.
|
|
346
346
|
function setTokenFor(uint256 projectId, IJBToken token) external;
|
|
347
347
|
|
|
348
|
+
/// @notice Sets the name and symbol of a project's token.
|
|
349
|
+
/// @param projectId The ID of the project whose token is being updated.
|
|
350
|
+
/// @param name The new name.
|
|
351
|
+
/// @param symbol The new symbol.
|
|
352
|
+
function setTokenMetadataOf(uint256 projectId, string calldata name, string calldata symbol) external;
|
|
353
|
+
|
|
348
354
|
/// @notice Transfers credits from one address to another.
|
|
349
355
|
/// @param holder The address to transfer credits from.
|
|
350
356
|
/// @param projectId The ID of the project whose credits are being transferred.
|
|
@@ -14,5 +14,6 @@ interface IJBPermitTerminal is IJBTerminal {
|
|
|
14
14
|
event Permit2AllowanceFailed(address indexed token, address indexed owner, bytes reason);
|
|
15
15
|
|
|
16
16
|
/// @notice The Permit2 contract used for token approvals.
|
|
17
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
17
18
|
function PERMIT2() external returns (IPermit2);
|
|
18
19
|
}
|