@bananapus/core-v6 0.0.41 → 0.0.43
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/CHANGELOG.md +1 -1
- package/README.md +3 -3
- package/package.json +1 -1
- package/references/types-errors-events.md +0 -1
- package/src/JBChainlinkV3PriceFeed.sol +14 -6
- package/src/JBChainlinkV3SequencerPriceFeed.sol +5 -6
- package/src/JBController.sol +61 -50
- package/src/JBDirectory.sol +4 -7
- package/src/JBERC20.sol +8 -7
- package/src/JBFundAccessLimits.sol +12 -4
- package/src/JBMultiTerminal.sol +18 -51
- package/src/JBPermissions.sol +6 -3
- package/src/JBPrices.sol +18 -12
- package/src/JBRulesets.sol +4 -25
- package/src/JBSplits.sol +13 -5
- package/src/JBTerminalStore.sol +36 -25
- package/src/JBTokens.sol +11 -13
- package/src/abstract/JBControlled.sol +1 -2
- package/src/interfaces/IJBController.sol +0 -6
- package/src/libraries/JBCashOuts.sol +6 -2
- package/src/libraries/JBCurrencyIds.sol +3 -0
- package/src/libraries/JBFees.sol +10 -52
- package/src/libraries/JBMetadataResolver.sol +20 -12
- package/src/libraries/JBPayoutSplitGroupLib.sol +8 -3
- package/src/libraries/JBSplitGroupIds.sol +1 -0
- package/src/periphery/JBDeadline1Day.sol +1 -0
- package/src/periphery/JBDeadline3Days.sol +1 -0
- package/src/periphery/JBDeadline3Hours.sol +1 -0
- package/src/periphery/JBDeadline7Days.sol +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -19,7 +19,7 @@ This file describes the verified change from `nana-core-v5` to the current `nana
|
|
|
19
19
|
- Token metadata is more editable than in v5. The controller now exposes a dedicated token-metadata update path.
|
|
20
20
|
- Approval-hook handling is safer. The v6 codebase and tests are built around preventing a bad approval hook from freezing project behavior.
|
|
21
21
|
- Fee accounting is tighter than in v5, especially around fee-free surplus behavior and cross-flow bookkeeping.
|
|
22
|
-
- The repo carries much broader test coverage than the v5 tree, including dedicated
|
|
22
|
+
- The repo carries much broader test coverage than the v5 tree, including dedicated review, invariant, fork, and formal-style suites.
|
|
23
23
|
- The implementation baseline moved from the v5 `0.8.23` world to `0.8.28`.
|
|
24
24
|
|
|
25
25
|
## Verified deltas
|
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ When a flow is unclear, read the contract that owns the state before the contrac
|
|
|
89
89
|
2. `test/TestTerminalPreviewParity.sol`
|
|
90
90
|
3. `test/invariants/TerminalStoreInvariant.t.sol`
|
|
91
91
|
4. `test/invariants/RulesetsInvariant.t.sol`
|
|
92
|
-
5. `test/
|
|
92
|
+
5. `test/regression/CrossTerminalSurplusSpoof.t.sol`
|
|
93
93
|
|
|
94
94
|
## Install
|
|
95
95
|
|
|
@@ -123,7 +123,7 @@ This repo contains the main core deployments and periphery deployment helpers. M
|
|
|
123
123
|
src/
|
|
124
124
|
core contracts, periphery helpers, interfaces, libraries, enums, structs, and abstract bases
|
|
125
125
|
test/
|
|
126
|
-
unit, integration, fork, invariant,
|
|
126
|
+
unit, integration, fork, invariant, review, formal, and regression coverage
|
|
127
127
|
script/
|
|
128
128
|
Deploy.s.sol
|
|
129
129
|
DeployPeriphery.s.sol
|
|
@@ -135,7 +135,7 @@ script/
|
|
|
135
135
|
- Hooks can materially change payment and cash-out behavior.
|
|
136
136
|
- Permissions are flexible, which makes broad or wildcard grants risky.
|
|
137
137
|
- Multi-terminal and multi-token accounting is powerful, but it is easy to misuse if an integration assumes a single-terminal model.
|
|
138
|
-
- Fee, surplus, and reclaim logic stay high-priority
|
|
138
|
+
- Fee, surplus, and reclaim logic stay high-priority review areas.
|
|
139
139
|
|
|
140
140
|
The easiest way to misread V6 is to treat core like a simple crowdfunding terminal. It is closer to a configurable accounting and settlement layer.
|
|
141
141
|
|
package/package.json
CHANGED
|
@@ -182,7 +182,6 @@ Errors an agent is most likely to encounter. All are custom errors (revert with
|
|
|
182
182
|
| `JBSplits_TotalPercentExceeds100` | `JBSplits` | Split percentages sum exceeds `SPLITS_TOTAL_PERCENT`. |
|
|
183
183
|
| `JBPrices_PriceFeedAlreadyExists` | `JBPrices` | Feed already set for that currency pair (immutable). |
|
|
184
184
|
| `JBPrices_PriceFeedNotFound` | `JBPrices` | No feed found for the requested currency pair. |
|
|
185
|
-
| `JBPermissions_CantSetRootPermissionForWildcardProject` | `JBPermissions` | Tried to grant ROOT with `projectId = 0` (wildcard). |
|
|
186
185
|
| `JBRulesets_InvalidWeight` | `JBRulesets` | Weight exceeds `uint112.max`. |
|
|
187
186
|
| `JBRulesets_InvalidWeightCutPercent` | `JBRulesets` | `weightCutPercent` exceeds `MAX_WEIGHT_CUT_PERCENT`. |
|
|
188
187
|
| `JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering` | `JBFundAccessLimits` | Payout limit currencies not in strictly increasing order. |
|
|
@@ -17,7 +17,7 @@ contract JBChainlinkV3PriceFeed is IJBPriceFeed {
|
|
|
17
17
|
// --------------------------- custom errors ------------------------- //
|
|
18
18
|
//*********************************************************************//
|
|
19
19
|
|
|
20
|
-
error JBChainlinkV3PriceFeed_IncompleteRound();
|
|
20
|
+
error JBChainlinkV3PriceFeed_IncompleteRound(uint80 roundId, uint80 answeredInRound, uint256 updatedAt);
|
|
21
21
|
error JBChainlinkV3PriceFeed_NegativePrice(int256 price);
|
|
22
22
|
error JBChainlinkV3PriceFeed_StalePrice(uint256 timestamp, uint256 threshold, uint256 updatedAt);
|
|
23
23
|
|
|
@@ -51,20 +51,28 @@ contract JBChainlinkV3PriceFeed is IJBPriceFeed {
|
|
|
51
51
|
/// @return The current unit price from the feed, as a fixed point number with the specified number of decimals.
|
|
52
52
|
function currentUnitPrice(uint256 decimals) public view virtual override returns (uint256) {
|
|
53
53
|
// Get the latest round information from the feed.
|
|
54
|
-
// slither-disable-next-line unused-return
|
|
55
54
|
(uint80 roundId, int256 price,, uint256 updatedAt, uint80 answeredInRound) = FEED.latestRoundData();
|
|
56
55
|
|
|
57
56
|
// Make sure the round is finished (check before stale price to avoid false stale on incomplete rounds).
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
if (updatedAt == 0) {
|
|
58
|
+
revert JBChainlinkV3PriceFeed_IncompleteRound({
|
|
59
|
+
roundId: roundId, answeredInRound: answeredInRound, updatedAt: updatedAt
|
|
60
|
+
});
|
|
61
|
+
}
|
|
60
62
|
|
|
61
63
|
// Make sure the answer was provided in the current round.
|
|
62
|
-
if (answeredInRound < roundId)
|
|
64
|
+
if (answeredInRound < roundId) {
|
|
65
|
+
revert JBChainlinkV3PriceFeed_IncompleteRound({
|
|
66
|
+
roundId: roundId, answeredInRound: answeredInRound, updatedAt: updatedAt
|
|
67
|
+
});
|
|
68
|
+
}
|
|
63
69
|
|
|
64
70
|
// Make sure the price's update threshold is met.
|
|
65
71
|
// forge-lint: disable-next-line(block-timestamp)
|
|
66
72
|
if (block.timestamp > THRESHOLD + updatedAt) {
|
|
67
|
-
revert JBChainlinkV3PriceFeed_StalePrice(
|
|
73
|
+
revert JBChainlinkV3PriceFeed_StalePrice({
|
|
74
|
+
timestamp: block.timestamp, threshold: THRESHOLD, updatedAt: updatedAt
|
|
75
|
+
});
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
// Make sure the price is positive.
|
|
@@ -14,7 +14,7 @@ contract JBChainlinkV3SequencerPriceFeed is JBChainlinkV3PriceFeed {
|
|
|
14
14
|
// --------------------------- custom errors ------------------------- //
|
|
15
15
|
//*********************************************************************//
|
|
16
16
|
|
|
17
|
-
error JBChainlinkV3SequencerPriceFeed_InvalidRound();
|
|
17
|
+
error JBChainlinkV3SequencerPriceFeed_InvalidRound(uint256 startedAt);
|
|
18
18
|
error JBChainlinkV3SequencerPriceFeed_SequencerDownOrRestarting(
|
|
19
19
|
uint256 timestamp, uint256 gracePeriodTime, uint256 startedAt
|
|
20
20
|
);
|
|
@@ -58,18 +58,17 @@ contract JBChainlinkV3SequencerPriceFeed is JBChainlinkV3PriceFeed {
|
|
|
58
58
|
/// @return The current unit price from the feed, as a fixed point number with the specified number of decimals.
|
|
59
59
|
function currentUnitPrice(uint256 decimals) public view override returns (uint256) {
|
|
60
60
|
// Fetch sequencer status.
|
|
61
|
-
// slither-disable-next-line unused-return
|
|
62
61
|
(, int256 answer, uint256 startedAt,,) = SEQUENCER_FEED.latestRoundData();
|
|
63
62
|
|
|
64
63
|
// Check if round is valid to prevent an edge-case where Arbitrum uptime contract is not init.
|
|
65
|
-
if (startedAt == 0) revert JBChainlinkV3SequencerPriceFeed_InvalidRound();
|
|
64
|
+
if (startedAt == 0) revert JBChainlinkV3SequencerPriceFeed_InvalidRound({startedAt: startedAt});
|
|
66
65
|
|
|
67
66
|
// Revert if sequencer has too recently restarted or is currently down.
|
|
68
67
|
// forge-lint: disable-next-line(block-timestamp)
|
|
69
68
|
if (block.timestamp <= GRACE_PERIOD_TIME + startedAt || answer != 0) {
|
|
70
|
-
revert JBChainlinkV3SequencerPriceFeed_SequencerDownOrRestarting(
|
|
71
|
-
block.timestamp, GRACE_PERIOD_TIME, startedAt
|
|
72
|
-
);
|
|
69
|
+
revert JBChainlinkV3SequencerPriceFeed_SequencerDownOrRestarting({
|
|
70
|
+
timestamp: block.timestamp, gracePeriodTime: GRACE_PERIOD_TIME, startedAt: startedAt
|
|
71
|
+
});
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
return super.currentUnitPrice(decimals);
|
package/src/JBController.sol
CHANGED
|
@@ -60,19 +60,19 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
60
60
|
//*********************************************************************//
|
|
61
61
|
|
|
62
62
|
error JBController_AddingPriceFeedNotAllowed(uint256 projectId);
|
|
63
|
-
error JBController_CreditTransfersPaused();
|
|
63
|
+
error JBController_CreditTransfersPaused(uint256 projectId, uint256 rulesetId);
|
|
64
64
|
error JBController_InvalidCashOutTaxRate(uint256 rate, uint256 limit);
|
|
65
65
|
error JBController_InvalidReservedPercent(uint256 percent, uint256 limit);
|
|
66
66
|
error JBController_MintNotAllowedAndNotTerminalOrHook(address caller);
|
|
67
|
-
error JBController_NoReservedTokens();
|
|
67
|
+
error JBController_NoReservedTokens(uint256 projectId);
|
|
68
68
|
error JBController_OnlyDirectory(address sender, IJBDirectory directory);
|
|
69
69
|
error JBController_PendingReservedTokens(uint256 pendingReservedTokenBalance);
|
|
70
70
|
error JBController_RulesetsAlreadyLaunched(uint256 projectId);
|
|
71
|
-
error JBController_RulesetsArrayEmpty();
|
|
71
|
+
error JBController_RulesetsArrayEmpty(uint256 projectId, uint256 rulesetConfigurationCount);
|
|
72
72
|
error JBController_RulesetSetTokenNotAllowed(uint256 projectId);
|
|
73
|
-
error JBController_TerminalTokensNotTransferred();
|
|
74
|
-
error JBController_ZeroTokensToBurn();
|
|
75
|
-
error JBController_ZeroTokensToMint();
|
|
73
|
+
error JBController_TerminalTokensNotTransferred(address terminal, address token, uint256 allowance);
|
|
74
|
+
error JBController_ZeroTokensToBurn(uint256 projectId, address holder);
|
|
75
|
+
error JBController_ZeroTokensToMint(uint256 projectId, address beneficiary);
|
|
76
76
|
|
|
77
77
|
//*********************************************************************//
|
|
78
78
|
// --------------- public immutable stored properties ---------------- //
|
|
@@ -163,7 +163,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
163
163
|
RULESETS = rulesets;
|
|
164
164
|
SPLITS = splits;
|
|
165
165
|
TOKENS = tokens;
|
|
166
|
-
// slither-disable-next-line missing-zero-check
|
|
167
166
|
OMNICHAIN_RULESET_OPERATOR = omnichainRulesetOperator;
|
|
168
167
|
}
|
|
169
168
|
|
|
@@ -235,7 +234,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
235
234
|
from.supportsInterface(type(IJBController).interfaceId)
|
|
236
235
|
&& IJBController(address(from)).pendingReservedTokenBalanceOf(projectId) > 0
|
|
237
236
|
) {
|
|
238
|
-
// slither-disable-next-line unused-return
|
|
239
237
|
IJBController(address(from)).sendReservedTokensToSplitsOf(projectId);
|
|
240
238
|
}
|
|
241
239
|
}
|
|
@@ -261,11 +259,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
261
259
|
account: holder,
|
|
262
260
|
projectId: projectId,
|
|
263
261
|
permissionId: JBPermissionIds.BURN_TOKENS,
|
|
264
|
-
alsoGrantAccessIf: _isTerminalOf(projectId, _msgSender())
|
|
262
|
+
alsoGrantAccessIf: _isTerminalOf({projectId: projectId, terminal: _msgSender()})
|
|
265
263
|
});
|
|
266
264
|
|
|
267
265
|
// There must be tokens to burn.
|
|
268
|
-
if (tokenCount == 0) revert JBController_ZeroTokensToBurn();
|
|
266
|
+
if (tokenCount == 0) revert JBController_ZeroTokensToBurn({projectId: projectId, holder: holder});
|
|
269
267
|
|
|
270
268
|
emit BurnTokens({
|
|
271
269
|
holder: holder, projectId: projectId, tokenCount: tokenCount, memo: memo, caller: _msgSender()
|
|
@@ -359,7 +357,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
359
357
|
// Approve the tokens being paid.
|
|
360
358
|
IERC20(address(token)).forceApprove({spender: address(terminal), value: splitTokenCount});
|
|
361
359
|
|
|
362
|
-
// slither-disable-next-line unused-return
|
|
363
360
|
terminal.pay({
|
|
364
361
|
projectId: projectId,
|
|
365
362
|
token: address(token),
|
|
@@ -371,8 +368,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
371
368
|
});
|
|
372
369
|
|
|
373
370
|
// Make sure that the terminal received the tokens.
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
uint256 allowance = IERC20(address(token)).allowance({owner: address(this), spender: address(terminal)});
|
|
372
|
+
if (allowance != 0) {
|
|
373
|
+
revert JBController_TerminalTokensNotTransferred({
|
|
374
|
+
terminal: address(terminal), token: address(token), allowance: allowance
|
|
375
|
+
});
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
|
|
@@ -400,7 +400,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
400
400
|
returns (uint256 projectId)
|
|
401
401
|
{
|
|
402
402
|
// Mint the project ERC-721 into the owner's wallet.
|
|
403
|
-
// slither-disable-next-line reentrancy-benign
|
|
404
403
|
projectId = PROJECTS.createFor(owner);
|
|
405
404
|
|
|
406
405
|
// If provided, set the project's metadata URI.
|
|
@@ -415,7 +414,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
415
414
|
_configureTerminals({projectId: projectId, terminalConfigurations: terminalConfigurations});
|
|
416
415
|
|
|
417
416
|
// Queue the rulesets.
|
|
418
|
-
// slither-disable-next-line reentrancy-events
|
|
419
417
|
uint256 rulesetId = _queueRulesets({projectId: projectId, rulesetConfigurations: rulesetConfigurations});
|
|
420
418
|
|
|
421
419
|
emit LaunchProject({
|
|
@@ -445,7 +443,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
445
443
|
returns (uint256 rulesetId)
|
|
446
444
|
{
|
|
447
445
|
// Make sure there are rulesets being queued.
|
|
448
|
-
if (rulesetConfigurations.length == 0)
|
|
446
|
+
if (rulesetConfigurations.length == 0) {
|
|
447
|
+
revert JBController_RulesetsArrayEmpty({
|
|
448
|
+
projectId: projectId, rulesetConfigurationCount: rulesetConfigurations.length
|
|
449
|
+
});
|
|
450
|
+
}
|
|
449
451
|
|
|
450
452
|
// Keep a reference to the sender.
|
|
451
453
|
address sender = _msgSender();
|
|
@@ -477,7 +479,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
477
479
|
|
|
478
480
|
// If the project has already had rulesets, use `queueRulesetsOf(...)` instead.
|
|
479
481
|
if (RULESETS.latestRulesetIdOf(projectId) > 0) {
|
|
480
|
-
revert JBController_RulesetsAlreadyLaunched(projectId);
|
|
482
|
+
revert JBController_RulesetsAlreadyLaunched({projectId: projectId});
|
|
481
483
|
}
|
|
482
484
|
|
|
483
485
|
// If provided, set the project's metadata URI.
|
|
@@ -492,7 +494,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
492
494
|
_configureTerminals({projectId: projectId, terminalConfigurations: terminalConfigurations});
|
|
493
495
|
|
|
494
496
|
// Queue the first ruleset.
|
|
495
|
-
// slither-disable-next-line reentrancy-events
|
|
496
497
|
rulesetId = _queueRulesets({projectId: projectId, rulesetConfigurations: rulesetConfigurations});
|
|
497
498
|
|
|
498
499
|
emit LaunchRulesets({
|
|
@@ -539,7 +540,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
539
540
|
returns (uint256 beneficiaryTokenCount)
|
|
540
541
|
{
|
|
541
542
|
// There should be tokens to mint.
|
|
542
|
-
if (tokenCount == 0) revert JBController_ZeroTokensToMint();
|
|
543
|
+
if (tokenCount == 0) revert JBController_ZeroTokensToMint({projectId: projectId, beneficiary: beneficiary});
|
|
543
544
|
|
|
544
545
|
// Keep a reference to the reserved percent.
|
|
545
546
|
uint256 reservedPercent;
|
|
@@ -549,11 +550,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
549
550
|
|
|
550
551
|
// Cache common values used in both permission checks.
|
|
551
552
|
address sender = _msgSender();
|
|
552
|
-
bool senderIsTerminal = _isTerminalOf(projectId, sender);
|
|
553
|
+
bool senderIsTerminal = _isTerminalOf({projectId: projectId, terminal: sender});
|
|
553
554
|
bool senderIsTerminalOrDataHook = senderIsTerminal || sender == ruleset.dataHook();
|
|
554
555
|
// Only query the data hook if the sender isn't already a terminal or the data hook itself.
|
|
555
|
-
bool senderHasDataHookMintPermission =
|
|
556
|
-
|
|
556
|
+
bool senderHasDataHookMintPermission = !senderIsTerminalOrDataHook
|
|
557
|
+
&& _hasDataHookMintPermissionFor({projectId: projectId, ruleset: ruleset, addr: sender});
|
|
557
558
|
|
|
558
559
|
// Minting is restricted to: the project's owner, addresses with permission to `MINT_TOKENS`, the project's
|
|
559
560
|
// terminals, and the project's data hook.
|
|
@@ -570,7 +571,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
570
571
|
ruleset.id != 0 && !ruleset.allowOwnerMinting() && !senderIsTerminalOrDataHook
|
|
571
572
|
&& !senderHasDataHookMintPermission
|
|
572
573
|
) {
|
|
573
|
-
revert JBController_MintNotAllowedAndNotTerminalOrHook(sender);
|
|
574
|
+
revert JBController_MintNotAllowedAndNotTerminalOrHook({caller: sender});
|
|
574
575
|
}
|
|
575
576
|
|
|
576
577
|
// Determine the reserved percent to use.
|
|
@@ -581,7 +582,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
581
582
|
|
|
582
583
|
if (beneficiaryTokenCount != 0) {
|
|
583
584
|
// Mint the tokens.
|
|
584
|
-
// slither-disable-next-line reentrancy-benign,reentrancy-events,unused-return
|
|
585
585
|
TOKENS.mintFor({holder: beneficiary, projectId: projectId, count: beneficiaryTokenCount});
|
|
586
586
|
}
|
|
587
587
|
|
|
@@ -618,7 +618,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
618
618
|
returns (uint256 rulesetId)
|
|
619
619
|
{
|
|
620
620
|
// Make sure there are rulesets being queued.
|
|
621
|
-
if (rulesetConfigurations.length == 0)
|
|
621
|
+
if (rulesetConfigurations.length == 0) {
|
|
622
|
+
revert JBController_RulesetsArrayEmpty({
|
|
623
|
+
projectId: projectId, rulesetConfigurationCount: rulesetConfigurations.length
|
|
624
|
+
});
|
|
625
|
+
}
|
|
622
626
|
|
|
623
627
|
// Enforce permissions.
|
|
624
628
|
_requirePermissionAllowingOverrideFrom({
|
|
@@ -629,7 +633,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
629
633
|
});
|
|
630
634
|
|
|
631
635
|
// Queue the rulesets.
|
|
632
|
-
// slither-disable-next-line reentrancy-events
|
|
633
636
|
rulesetId = _queueRulesets({projectId: projectId, rulesetConfigurations: rulesetConfigurations});
|
|
634
637
|
|
|
635
638
|
emit QueueRulesets({rulesetId: rulesetId, projectId: projectId, memo: memo, caller: _msgSender()});
|
|
@@ -748,7 +751,9 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
748
751
|
JBRuleset memory ruleset = _currentRulesetOf(projectId);
|
|
749
752
|
|
|
750
753
|
// Credit transfers must not be paused.
|
|
751
|
-
if (ruleset.pauseCreditTransfers())
|
|
754
|
+
if (ruleset.pauseCreditTransfers()) {
|
|
755
|
+
revert JBController_CreditTransfersPaused({projectId: projectId, rulesetId: ruleset.id});
|
|
756
|
+
}
|
|
752
757
|
|
|
753
758
|
TOKENS.transferCreditsFrom({holder: holder, projectId: projectId, recipient: recipient, count: creditCount});
|
|
754
759
|
}
|
|
@@ -939,7 +944,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
939
944
|
JBTerminalConfig memory terminalConfig = terminalConfigurations[i];
|
|
940
945
|
|
|
941
946
|
// Add the accounting contexts for the specified tokens.
|
|
942
|
-
// slither-disable-next-line calls-loop
|
|
943
947
|
terminalConfig.terminal
|
|
944
948
|
.addAccountingContextsFor({
|
|
945
949
|
projectId: projectId, accountingContexts: terminalConfig.accountingContextsToAccept
|
|
@@ -975,20 +979,19 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
975
979
|
|
|
976
980
|
// Make sure its reserved percent is valid.
|
|
977
981
|
if (rulesetConfig.metadata.reservedPercent > JBConstants.MAX_RESERVED_PERCENT) {
|
|
978
|
-
revert JBController_InvalidReservedPercent(
|
|
979
|
-
rulesetConfig.metadata.reservedPercent, JBConstants.MAX_RESERVED_PERCENT
|
|
980
|
-
);
|
|
982
|
+
revert JBController_InvalidReservedPercent({
|
|
983
|
+
percent: rulesetConfig.metadata.reservedPercent, limit: JBConstants.MAX_RESERVED_PERCENT
|
|
984
|
+
});
|
|
981
985
|
}
|
|
982
986
|
|
|
983
987
|
// Make sure its cash out tax rate is valid.
|
|
984
988
|
if (rulesetConfig.metadata.cashOutTaxRate > JBConstants.MAX_CASH_OUT_TAX_RATE) {
|
|
985
|
-
revert JBController_InvalidCashOutTaxRate(
|
|
986
|
-
rulesetConfig.metadata.cashOutTaxRate, JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
987
|
-
);
|
|
989
|
+
revert JBController_InvalidCashOutTaxRate({
|
|
990
|
+
rate: rulesetConfig.metadata.cashOutTaxRate, limit: JBConstants.MAX_CASH_OUT_TAX_RATE
|
|
991
|
+
});
|
|
988
992
|
}
|
|
989
993
|
|
|
990
994
|
// Queue its ruleset.
|
|
991
|
-
// slither-disable-next-line calls-loop
|
|
992
995
|
JBRuleset memory ruleset = RULESETS.queueFor({
|
|
993
996
|
projectId: projectId,
|
|
994
997
|
duration: rulesetConfig.duration,
|
|
@@ -1000,13 +1003,11 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1000
1003
|
});
|
|
1001
1004
|
|
|
1002
1005
|
// Set its split groups.
|
|
1003
|
-
// slither-disable-next-line calls-loop
|
|
1004
1006
|
SPLITS.setSplitGroupsOf({
|
|
1005
1007
|
projectId: projectId, rulesetId: ruleset.id, splitGroups: rulesetConfig.splitGroups
|
|
1006
1008
|
});
|
|
1007
1009
|
|
|
1008
1010
|
// Set its fund access limits.
|
|
1009
|
-
// slither-disable-next-line calls-loop
|
|
1010
1011
|
FUND_ACCESS_LIMITS.setFundAccessLimitsFor({
|
|
1011
1012
|
projectId: projectId, rulesetId: ruleset.id, fundAccessLimitGroups: rulesetConfig.fundAccessLimitGroups
|
|
1012
1013
|
});
|
|
@@ -1069,13 +1070,19 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1069
1070
|
|
|
1070
1071
|
// If the split has a hook, call its `processSplitWith` function.
|
|
1071
1072
|
if (split.hook != IJBSplitHook(address(0))) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1073
|
+
if (token != IJBToken(address(0))) {
|
|
1074
|
+
// ERC-20: grant the hook an allowance and let it pull tokens via transferFrom.
|
|
1075
|
+
IERC20(address(token)).forceApprove({spender: address(split.hook), value: splitTokenCount});
|
|
1076
|
+
} else {
|
|
1077
|
+
// Credits: send directly — there is no allowance mechanism for credits.
|
|
1078
|
+
TOKENS.transferCreditsFrom({
|
|
1079
|
+
holder: address(this),
|
|
1080
|
+
projectId: projectId,
|
|
1081
|
+
recipient: address(split.hook),
|
|
1082
|
+
count: splitTokenCount
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1077
1085
|
|
|
1078
|
-
// slither-disable-next-line calls-loop,reentrancy-events
|
|
1079
1086
|
try split.hook
|
|
1080
1087
|
.processSplitWith(
|
|
1081
1088
|
JBSplitHookContext({
|
|
@@ -1088,9 +1095,18 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1088
1095
|
})
|
|
1089
1096
|
) {}
|
|
1090
1097
|
catch (bytes memory reason) {
|
|
1091
|
-
// If the hook reverts, the tokens already transferred to it stay with the hook.
|
|
1092
1098
|
emit SplitHookReverted({projectId: projectId, hook: address(split.hook), reason: reason});
|
|
1093
1099
|
}
|
|
1100
|
+
|
|
1101
|
+
// For ERC-20 tokens, burn any tokens the hook did not consume.
|
|
1102
|
+
if (token != IJBToken(address(0))) {
|
|
1103
|
+
uint256 remaining =
|
|
1104
|
+
IERC20(address(token)).allowance({owner: address(this), spender: address(split.hook)});
|
|
1105
|
+
if (remaining > 0) {
|
|
1106
|
+
IERC20(address(token)).forceApprove({spender: address(split.hook), value: 0});
|
|
1107
|
+
TOKENS.burnFrom({holder: address(this), projectId: projectId, count: remaining});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1094
1110
|
// If the split has a project ID, try to pay the project. If that fails, pay the beneficiary.
|
|
1095
1111
|
} else {
|
|
1096
1112
|
// Pay the project using the split's beneficiary if one was provided. Otherwise, use the message
|
|
@@ -1099,7 +1115,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1099
1115
|
|
|
1100
1116
|
if (split.projectId != 0) {
|
|
1101
1117
|
// Get a reference to the receiving project's primary payment terminal for the token.
|
|
1102
|
-
// slither-disable-next-line calls-loop
|
|
1103
1118
|
IJBTerminal terminal = token == IJBToken(address(0))
|
|
1104
1119
|
? IJBTerminal(address(0))
|
|
1105
1120
|
: DIRECTORY.primaryTerminalOf({projectId: split.projectId, token: address(token)});
|
|
@@ -1108,17 +1123,14 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1108
1123
|
// which accepts the token, send the tokens to the beneficiary.
|
|
1109
1124
|
if (address(token) == address(0) || address(terminal) == address(0)) {
|
|
1110
1125
|
// Mint the tokens to the beneficiary.
|
|
1111
|
-
// slither-disable-next-line reentrancy-events
|
|
1112
1126
|
_sendTokens({
|
|
1113
1127
|
projectId: projectId, tokenCount: splitTokenCount, recipient: beneficiary, token: token
|
|
1114
1128
|
});
|
|
1115
1129
|
} else {
|
|
1116
1130
|
// Use the `projectId` in the pay metadata.
|
|
1117
|
-
// slither-disable-next-line reentrancy-events
|
|
1118
1131
|
bytes memory metadata = bytes(abi.encodePacked(projectId));
|
|
1119
1132
|
|
|
1120
1133
|
// Try to fulfill the payment.
|
|
1121
|
-
// slither-disable-next-line calls-loop
|
|
1122
1134
|
try this.executePayReservedTokenToTerminal({
|
|
1123
1135
|
projectId: split.projectId,
|
|
1124
1136
|
terminal: terminal,
|
|
@@ -1142,7 +1154,6 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1142
1154
|
}
|
|
1143
1155
|
} else if (beneficiary == address(0xdead)) {
|
|
1144
1156
|
// If the split has no project ID, and the beneficiary is 0xdead, burn.
|
|
1145
|
-
// slither-disable-next-line calls-loop
|
|
1146
1157
|
TOKENS.burnFrom({holder: address(this), projectId: projectId, count: splitTokenCount});
|
|
1147
1158
|
} else {
|
|
1148
1159
|
// If the split has no project Id, send to beneficiary.
|
|
@@ -1180,7 +1191,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1180
1191
|
tokenCount = pendingReservedTokenBalanceOf[projectId];
|
|
1181
1192
|
|
|
1182
1193
|
// Revert if there are no pending reserved tokens
|
|
1183
|
-
if (tokenCount == 0) revert JBController_NoReservedTokens();
|
|
1194
|
+
if (tokenCount == 0) revert JBController_NoReservedTokens({projectId: projectId});
|
|
1184
1195
|
|
|
1185
1196
|
// Get the ruleset to read the reserved percent from.
|
|
1186
1197
|
JBRuleset memory ruleset = _currentRulesetOf(projectId);
|
package/src/JBDirectory.sol
CHANGED
|
@@ -121,7 +121,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
121
121
|
|
|
122
122
|
// If setting the controller is not allowed, revert.
|
|
123
123
|
if (!allowSetController) {
|
|
124
|
-
revert JBDirectory_SetControllerNotAllowed(projectId);
|
|
124
|
+
revert JBDirectory_SetControllerNotAllowed({projectId: projectId});
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// Prepare the new controller to receive the project.
|
|
@@ -140,10 +140,8 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Set the new controller after migration completes.
|
|
143
|
-
// slither-disable-next-line reentrancy-no-eth
|
|
144
143
|
controllerOf[projectId] = controller;
|
|
145
144
|
|
|
146
|
-
// slither-disable-next-line reentrancy-events
|
|
147
145
|
emit SetController({projectId: projectId, controller: controller, caller: msg.sender});
|
|
148
146
|
|
|
149
147
|
// Notify the new controller that migration is complete and it is now the active controller.
|
|
@@ -185,7 +183,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
185
183
|
|
|
186
184
|
// If the terminal doesn't accept the token, revert.
|
|
187
185
|
if (terminal.accountingContextForTokenOf({projectId: projectId, token: token}).token == address(0)) {
|
|
188
|
-
revert JBDirectory_TokenNotAccepted(projectId, token, terminal);
|
|
186
|
+
revert JBDirectory_TokenNotAccepted({projectId: projectId, token: token, terminal: terminal});
|
|
189
187
|
}
|
|
190
188
|
|
|
191
189
|
// If the terminal is not already in the project's terminal list, require ADD_TERMINALS permission.
|
|
@@ -229,7 +227,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
229
227
|
|
|
230
228
|
// If the caller is not the project's controller, the project's ruleset must allow setting terminals.
|
|
231
229
|
if (msg.sender != address(controller) && !allowSetTerminals) {
|
|
232
|
-
revert JBDirectory_SetTerminalsNotAllowed(projectId);
|
|
230
|
+
revert JBDirectory_SetTerminalsNotAllowed({projectId: projectId});
|
|
233
231
|
}
|
|
234
232
|
|
|
235
233
|
// Set the stored terminals for the project.
|
|
@@ -286,7 +284,6 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
286
284
|
IJBTerminal terminal = terminals[i];
|
|
287
285
|
|
|
288
286
|
// If the terminal accepts the specified token, return it.
|
|
289
|
-
// slither-disable-next-line calls-loop
|
|
290
287
|
if (terminal.accountingContextForTokenOf({projectId: projectId, token: token}).token != address(0)) {
|
|
291
288
|
return terminal;
|
|
292
289
|
}
|
|
@@ -355,7 +352,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
355
352
|
|
|
356
353
|
// The project's ruleset must allow setting terminals.
|
|
357
354
|
if (!allowSetTerminals) {
|
|
358
|
-
revert JBDirectory_SetTerminalsNotAllowed(projectId);
|
|
355
|
+
revert JBDirectory_SetTerminalsNotAllowed({projectId: projectId});
|
|
359
356
|
}
|
|
360
357
|
|
|
361
358
|
// Add the new terminal.
|
package/src/JBERC20.sol
CHANGED
|
@@ -25,8 +25,8 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
25
25
|
// --------------------------- custom errors ------------------------- //
|
|
26
26
|
//*********************************************************************//
|
|
27
27
|
|
|
28
|
-
error JBERC20_AlreadyInitialized();
|
|
29
|
-
error JBERC20_Unauthorized();
|
|
28
|
+
error JBERC20_AlreadyInitialized(uint256 currentNameLength, uint256 newNameLength);
|
|
29
|
+
error JBERC20_Unauthorized(address caller, address tokens);
|
|
30
30
|
|
|
31
31
|
//*********************************************************************//
|
|
32
32
|
// --------------- public immutable stored properties ---------------- //
|
|
@@ -48,11 +48,9 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
48
48
|
//*********************************************************************//
|
|
49
49
|
|
|
50
50
|
/// @notice The token's name.
|
|
51
|
-
// slither-disable-next-line shadowing-state
|
|
52
51
|
string private _name;
|
|
53
52
|
|
|
54
53
|
/// @notice The token's symbol.
|
|
55
|
-
// slither-disable-next-line shadowing-state
|
|
56
54
|
string private _symbol;
|
|
57
55
|
|
|
58
56
|
//*********************************************************************//
|
|
@@ -82,7 +80,7 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
82
80
|
/// @notice Only the JBTokens contract can call this function.
|
|
83
81
|
// forge-lint: disable-next-line(unwrapped-modifier-logic)
|
|
84
82
|
modifier onlyTokens() {
|
|
85
|
-
if (msg.sender != address(TOKENS)) revert JBERC20_Unauthorized();
|
|
83
|
+
if (msg.sender != address(TOKENS)) revert JBERC20_Unauthorized({caller: msg.sender, tokens: address(TOKENS)});
|
|
86
84
|
_;
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -132,7 +130,6 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
132
130
|
/// @return magicValue `0x1626ba7e` if the signature is valid, `0xffffffff` otherwise.
|
|
133
131
|
function isValidSignature(bytes32 hash, bytes memory signature) external view override returns (bytes4 magicValue) {
|
|
134
132
|
// Recover the signer from the signature. Return invalid if recovery fails.
|
|
135
|
-
// slither-disable-next-line unused-return
|
|
136
133
|
(address signer, ECDSA.RecoverError error,) = ECDSA.tryRecover(hash, signature);
|
|
137
134
|
if (error != ECDSA.RecoverError.NoError) return 0xffffffff;
|
|
138
135
|
|
|
@@ -201,7 +198,11 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
201
198
|
/// @param tokens The JBTokens contract that manages this token.
|
|
202
199
|
function initialize(string memory name_, string memory symbol_, address tokens) public override {
|
|
203
200
|
// Prevent re-initialization by reverting if a name is already set or if the provided name is empty.
|
|
204
|
-
if (bytes(_name).length != 0 || bytes(name_).length == 0)
|
|
201
|
+
if (bytes(_name).length != 0 || bytes(name_).length == 0) {
|
|
202
|
+
revert JBERC20_AlreadyInitialized({
|
|
203
|
+
currentNameLength: bytes(_name).length, newNameLength: bytes(name_).length
|
|
204
|
+
});
|
|
205
|
+
}
|
|
205
206
|
|
|
206
207
|
_name = name_;
|
|
207
208
|
_symbol = symbol_;
|
|
@@ -18,8 +18,12 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
18
18
|
// --------------------------- custom errors ------------------------- //
|
|
19
19
|
//*********************************************************************//
|
|
20
20
|
|
|
21
|
-
error JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering(
|
|
22
|
-
|
|
21
|
+
error JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering(
|
|
22
|
+
uint256 projectId, uint256 rulesetId, uint256 groupIndex, uint256 limitIndex
|
|
23
|
+
);
|
|
24
|
+
error JBFundAccessLimits_InvalidSurplusAllowanceCurrencyOrdering(
|
|
25
|
+
uint256 projectId, uint256 rulesetId, uint256 groupIndex, uint256 allowanceIndex
|
|
26
|
+
);
|
|
23
27
|
|
|
24
28
|
//*********************************************************************//
|
|
25
29
|
// --------------------- internal stored properties ------------------ //
|
|
@@ -102,7 +106,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
102
106
|
// Make sure the payout limits are passed in strictly increasing order (sorted by currency) to prevent
|
|
103
107
|
// duplicates.
|
|
104
108
|
if (j != 0 && payoutLimit.currency <= fundAccessLimitGroup.payoutLimits[j - 1].currency) {
|
|
105
|
-
revert JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering(
|
|
109
|
+
revert JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering({
|
|
110
|
+
projectId: projectId, rulesetId: rulesetId, groupIndex: i, limitIndex: j
|
|
111
|
+
});
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
// Set the payout limit if there is one.
|
|
@@ -127,7 +133,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
127
133
|
// Make sure the surplus allowances are passed in strictly increasing order (sorted by currency) to
|
|
128
134
|
// prevent duplicates.
|
|
129
135
|
if (j != 0 && surplusAllowance.currency <= fundAccessLimitGroup.surplusAllowances[j - 1].currency) {
|
|
130
|
-
revert JBFundAccessLimits_InvalidSurplusAllowanceCurrencyOrdering(
|
|
136
|
+
revert JBFundAccessLimits_InvalidSurplusAllowanceCurrencyOrdering({
|
|
137
|
+
projectId: projectId, rulesetId: rulesetId, groupIndex: i, allowanceIndex: j
|
|
138
|
+
});
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
// Set the surplus allowance if there is one.
|