@bananapus/core-v6 0.0.16 → 0.0.18
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 +1 -1
- package/ARCHITECTURE.md +2 -1
- package/AUDIT_INSTRUCTIONS.md +342 -0
- package/CHANGE_LOG.md +400 -0
- package/README.md +4 -4
- package/RISKS.md +171 -50
- package/SKILLS.md +9 -6
- package/USER_JOURNEYS.md +622 -0
- package/package.json +2 -2
- package/script/DeployPeriphery.s.sol +7 -1
- package/src/JBController.sol +5 -0
- package/src/JBDeadline.sol +3 -0
- package/src/JBDirectory.sol +2 -1
- package/src/JBMultiTerminal.sol +50 -9
- package/src/JBPermissions.sol +2 -0
- package/src/JBPrices.sol +8 -2
- package/src/JBRulesets.sol +3 -0
- package/src/JBSplits.sol +9 -5
- package/src/JBTerminalStore.sol +54 -47
- package/src/JBTokens.sol +3 -0
- package/src/interfaces/IJBTerminalStore.sol +3 -0
- package/src/libraries/JBFees.sol +2 -0
- package/src/libraries/JBMetadataResolver.sol +17 -4
- package/src/structs/JBBeforeCashOutRecordedContext.sol +4 -0
- package/test/TestAuditResponseDesignProofs.sol +434 -0
- package/test/TestDataHookFuzzing.sol +520 -0
- package/test/TestFeeFreeCashOutBypass.sol +617 -0
- package/test/TestL2SequencerPriceFeed.sol +292 -0
- package/test/TestMetadataOffsetOverflow.sol +179 -0
- package/test/TestMultiTerminalSurplus.sol +348 -0
- package/test/TestPermit2DataHook.t.sol +360 -0
- package/test/TestRulesetQueueing.sol +1 -2
- package/test/TestRulesetWeightCaching.sol +122 -124
- package/test/WeirdTokenTests.t.sol +37 -0
- package/test/regression/HoldFeesCashOutReserved.t.sol +415 -0
- package/test/regression/WeightCacheBoundary.t.sol +291 -0
- package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +2 -2
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +18 -17
- package/test/units/static/JBMultiTerminal/TestPay.sol +6 -4
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +206 -18
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +280 -0
- package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +55 -12
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +72 -0
- package/docs/book.css +0 -13
- package/docs/book.toml +0 -12
- package/docs/solidity.min.js +0 -74
- package/docs/src/README.md +0 -703
- package/docs/src/SUMMARY.md +0 -94
- package/docs/src/src/JBChainlinkV3PriceFeed.sol/contract.JBChainlinkV3PriceFeed.md +0 -83
- package/docs/src/src/JBChainlinkV3SequencerPriceFeed.sol/contract.JBChainlinkV3SequencerPriceFeed.md +0 -88
- package/docs/src/src/JBController.sol/contract.JBController.md +0 -1121
- package/docs/src/src/JBDeadline.sol/contract.JBDeadline.md +0 -84
- package/docs/src/src/JBDirectory.sol/contract.JBDirectory.md +0 -294
- package/docs/src/src/JBERC20.sol/contract.JBERC20.md +0 -190
- package/docs/src/src/JBFeelessAddresses.sol/contract.JBFeelessAddresses.md +0 -80
- package/docs/src/src/JBFundAccessLimits.sol/contract.JBFundAccessLimits.md +0 -253
- package/docs/src/src/JBMultiTerminal.sol/contract.JBMultiTerminal.md +0 -1472
- package/docs/src/src/JBPermissions.sol/contract.JBPermissions.md +0 -199
- package/docs/src/src/JBPrices.sol/contract.JBPrices.md +0 -154
- package/docs/src/src/JBProjects.sol/contract.JBProjects.md +0 -131
- package/docs/src/src/JBRulesets.sol/contract.JBRulesets.md +0 -677
- package/docs/src/src/JBSplits.sol/contract.JBSplits.md +0 -237
- package/docs/src/src/JBTerminalStore.sol/contract.JBTerminalStore.md +0 -591
- package/docs/src/src/JBTokens.sol/contract.JBTokens.md +0 -353
- package/docs/src/src/README.md +0 -25
- package/docs/src/src/abstract/JBControlled.sol/abstract.JBControlled.md +0 -64
- package/docs/src/src/abstract/JBPermissioned.sol/abstract.JBPermissioned.md +0 -84
- package/docs/src/src/abstract/README.md +0 -5
- package/docs/src/src/enums/JBApprovalStatus.sol/enum.JBApprovalStatus.md +0 -17
- package/docs/src/src/enums/README.md +0 -4
- package/docs/src/src/interfaces/IJBCashOutHook.sol/interface.IJBCashOutHook.md +0 -29
- package/docs/src/src/interfaces/IJBCashOutTerminal.sol/interface.IJBCashOutTerminal.md +0 -57
- package/docs/src/src/interfaces/IJBControlled.sol/interface.IJBControlled.md +0 -12
- package/docs/src/src/interfaces/IJBController.sol/interface.IJBController.md +0 -334
- package/docs/src/src/interfaces/IJBDirectory.sol/interface.IJBDirectory.md +0 -108
- package/docs/src/src/interfaces/IJBDirectoryAccessControl.sol/interface.IJBDirectoryAccessControl.md +0 -19
- package/docs/src/src/interfaces/IJBFeeTerminal.sol/interface.IJBFeeTerminal.md +0 -91
- package/docs/src/src/interfaces/IJBFeelessAddresses.sol/interface.IJBFeelessAddresses.md +0 -26
- package/docs/src/src/interfaces/IJBFundAccessLimits.sol/interface.IJBFundAccessLimits.md +0 -88
- package/docs/src/src/interfaces/IJBMigratable.sol/interface.IJBMigratable.md +0 -29
- package/docs/src/src/interfaces/IJBMultiTerminal.sol/interface.IJBMultiTerminal.md +0 -50
- package/docs/src/src/interfaces/IJBPayHook.sol/interface.IJBPayHook.md +0 -28
- package/docs/src/src/interfaces/IJBPayoutTerminal.sol/interface.IJBPayoutTerminal.md +0 -105
- package/docs/src/src/interfaces/IJBPermissioned.sol/interface.IJBPermissioned.md +0 -12
- package/docs/src/src/interfaces/IJBPermissions.sol/interface.IJBPermissions.md +0 -74
- package/docs/src/src/interfaces/IJBPermitTerminal.sol/interface.IJBPermitTerminal.md +0 -15
- package/docs/src/src/interfaces/IJBPriceFeed.sol/interface.IJBPriceFeed.md +0 -12
- package/docs/src/src/interfaces/IJBPrices.sol/interface.IJBPrices.md +0 -74
- package/docs/src/src/interfaces/IJBProjectUriRegistry.sol/interface.IJBProjectUriRegistry.md +0 -19
- package/docs/src/src/interfaces/IJBProjects.sol/interface.IJBProjects.md +0 -49
- package/docs/src/src/interfaces/IJBRulesetApprovalHook.sol/interface.IJBRulesetApprovalHook.md +0 -35
- package/docs/src/src/interfaces/IJBRulesetDataHook.sol/interface.IJBRulesetDataHook.md +0 -97
- package/docs/src/src/interfaces/IJBRulesets.sol/interface.IJBRulesets.md +0 -165
- package/docs/src/src/interfaces/IJBSplitHook.sol/interface.IJBSplitHook.md +0 -31
- package/docs/src/src/interfaces/IJBSplits.sol/interface.IJBSplits.md +0 -35
- package/docs/src/src/interfaces/IJBTerminal.sol/interface.IJBTerminal.md +0 -141
- package/docs/src/src/interfaces/IJBTerminalStore.sol/interface.IJBTerminalStore.md +0 -198
- package/docs/src/src/interfaces/IJBToken.sol/interface.IJBToken.md +0 -54
- package/docs/src/src/interfaces/IJBTokenUriResolver.sol/interface.IJBTokenUriResolver.md +0 -12
- package/docs/src/src/interfaces/IJBTokens.sol/interface.IJBTokens.md +0 -151
- package/docs/src/src/interfaces/README.md +0 -33
- package/docs/src/src/libraries/JBCashOuts.sol/library.JBCashOuts.md +0 -40
- package/docs/src/src/libraries/JBConstants.sol/library.JBConstants.md +0 -52
- package/docs/src/src/libraries/JBCurrencyIds.sol/library.JBCurrencyIds.md +0 -19
- package/docs/src/src/libraries/JBFees.sol/library.JBFees.md +0 -52
- package/docs/src/src/libraries/JBFixedPointNumber.sol/library.JBFixedPointNumber.md +0 -12
- package/docs/src/src/libraries/JBMetadataResolver.sol/library.JBMetadataResolver.md +0 -242
- package/docs/src/src/libraries/JBRulesetMetadataResolver.sol/library.JBRulesetMetadataResolver.md +0 -180
- package/docs/src/src/libraries/JBSplitGroupIds.sol/library.JBSplitGroupIds.md +0 -14
- package/docs/src/src/libraries/JBSurplus.sol/library.JBSurplus.md +0 -44
- package/docs/src/src/libraries/README.md +0 -12
- package/docs/src/src/periphery/JBDeadline1Day.sol/contract.JBDeadline1Day.md +0 -15
- package/docs/src/src/periphery/JBDeadline3Days.sol/contract.JBDeadline3Days.md +0 -15
- package/docs/src/src/periphery/JBDeadline3Hours.sol/contract.JBDeadline3Hours.md +0 -15
- package/docs/src/src/periphery/JBDeadline7Days.sol/contract.JBDeadline7Days.md +0 -15
- package/docs/src/src/periphery/JBMatchingPriceFeed.sol/contract.JBMatchingPriceFeed.md +0 -22
- package/docs/src/src/periphery/README.md +0 -8
- package/docs/src/src/structs/JBAccountingContext.sol/struct.JBAccountingContext.md +0 -20
- package/docs/src/src/structs/JBAfterCashOutRecordedContext.sol/struct.JBAfterCashOutRecordedContext.md +0 -43
- package/docs/src/src/structs/JBAfterPayRecordedContext.sol/struct.JBAfterPayRecordedContext.md +0 -42
- package/docs/src/src/structs/JBBeforeCashOutRecordedContext.sol/struct.JBBeforeCashOutRecordedContext.md +0 -45
- package/docs/src/src/structs/JBBeforePayRecordedContext.sol/struct.JBBeforePayRecordedContext.md +0 -41
- package/docs/src/src/structs/JBCashOutHookSpecification.sol/struct.JBCashOutHookSpecification.md +0 -22
- package/docs/src/src/structs/JBCurrencyAmount.sol/struct.JBCurrencyAmount.md +0 -17
- package/docs/src/src/structs/JBFee.sol/struct.JBFee.md +0 -20
- package/docs/src/src/structs/JBFundAccessLimitGroup.sol/struct.JBFundAccessLimitGroup.md +0 -39
- package/docs/src/src/structs/JBPayHookSpecification.sol/struct.JBPayHookSpecification.md +0 -22
- package/docs/src/src/structs/JBPermissionsData.sol/struct.JBPermissionsData.md +0 -21
- package/docs/src/src/structs/JBRuleset.sol/struct.JBRuleset.md +0 -55
- package/docs/src/src/structs/JBRulesetConfig.sol/struct.JBRulesetConfig.md +0 -51
- package/docs/src/src/structs/JBRulesetMetadata.sol/struct.JBRulesetMetadata.md +0 -79
- package/docs/src/src/structs/JBRulesetWeightCache.sol/struct.JBRulesetWeightCache.md +0 -16
- package/docs/src/src/structs/JBRulesetWithMetadata.sol/struct.JBRulesetWithMetadata.md +0 -16
- package/docs/src/src/structs/JBSingleAllowance.sol/struct.JBSingleAllowance.md +0 -26
- package/docs/src/src/structs/JBSplit.sol/struct.JBSplit.md +0 -49
- package/docs/src/src/structs/JBSplitGroup.sol/struct.JBSplitGroup.md +0 -17
- package/docs/src/src/structs/JBSplitHookContext.sol/struct.JBSplitHookContext.md +0 -29
- package/docs/src/src/structs/JBTerminalConfig.sol/struct.JBTerminalConfig.md +0 -16
- package/docs/src/src/structs/JBTokenAmount.sol/struct.JBTokenAmount.md +0 -23
- package/docs/src/src/structs/README.md +0 -25
package/RISKS.md
CHANGED
|
@@ -1,68 +1,189 @@
|
|
|
1
|
-
# nana-core-v6
|
|
1
|
+
# nana-core-v6 -- Active Risk Vectors
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Known security properties, trust assumptions, active vulnerability surfaces, and operational risks. Intended audience: experienced Solidity auditors looking for where to focus.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 1. Trust Assumptions
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
2. **Project Owner** — The ERC-721 holder can queue new rulesets, set terminals, configure splits, and delegate permissions. A malicious or compromised owner can fundamentally change project economics.
|
|
9
|
-
3. **Data Hooks** — If a ruleset specifies a data hook, that hook has absolute control over token minting weights and cash out parameters. A malicious data hook can drain the entire project treasury.
|
|
10
|
-
4. **Approval Hooks** — Can approve or reject ruleset transitions. A reverting approval hook doesn't freeze the project (try-catch fallback), but a malicious one could allow unexpected transitions.
|
|
11
|
-
5. **Price Feeds** — Surplus calculations depend on Chainlink price feeds. Stale or manipulated feeds affect cash out values and payout calculations. Staleness causes reverts (DoS), not fund loss.
|
|
12
|
-
6. **Fee Project (#1)** — 2.5% fees go to project #1. If project #1's terminal is misconfigured, fees are returned to the originating project's balance (not lost).
|
|
7
|
+
What must be true for the system to remain safe:
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
- **Hooks do not exploit reentrancy.** No `ReentrancyGuard` anywhere in core. All safety relies on checks-effects-interactions ordering and the `JBTerminalStore_InadequateTerminalStoreBalance` backstop. If a hook finds a code path where state is read before a prior write has settled, value extraction may be possible.
|
|
10
|
+
- **Data hooks are honest.** A data hook has absolute control over payment weight, cash out tax rate, `totalSupply`, `cashOutCount`, and fund-forwarding amounts. A malicious data hook can bypass the bonding curve entirely (e.g., set `totalSupply = surplus` to get 1:1 redemptions) or divert 100% of incoming payments to external hooks. The protocol enforces `sum(hook.amount) <= payment.value` and `reclaimAmount + sum(hook.amount) <= project balance`, but within those bounds the hook is omnipotent.
|
|
11
|
+
- **Price feeds do not lie.** Surplus calculations, currency conversions for payouts, and surplus allowance all depend on `JBPrices`. A manipulated or stale feed causes incorrect surplus values. Chainlink feeds have staleness thresholds and sequencer checks, but project-specific feeds registered via `allowAddPriceFeed` have no such guarantee -- a project owner can register a feed that returns any value.
|
|
12
|
+
- **ERC-20 tokens behave standardly.** `_acceptFundsFor` uses a balance-before/after pattern, which handles fee-on-transfer tokens. However, rebasing tokens that change balances between transactions will cause `balanceOf` in `JBTerminalStore` to diverge from actual terminal holdings. Missing-return-value tokens are handled by `SafeERC20`.
|
|
13
|
+
- **Trusted forwarder is not compromised.** The ERC-2771 forwarder is immutable. If compromised, it can spoof `_msgSender()` for all permission-gated functions across `JBController`, `JBMultiTerminal`, `JBProjects`, `JBPrices`, and `JBPermissions`.
|
|
14
|
+
- **Project #1 terminal remains functional.** If the fee beneficiary project's terminal reverts, `_processFee` catches the error and returns the fee amount to the originating project's balance. This is safe but means fees are silently forgiven during outages.
|
|
15
|
+
- **`OMNICHAIN_RULESET_OPERATOR` is trusted.** This immutable address bypasses owner permission checks for `launchRulesetsFor`, `queueRulesetsOf`, and `setTerminalsOf`. A compromised operator can queue arbitrary rulesets for any project.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
- **Token holders** — Can only cash out proportional to the bonding curve
|
|
18
|
-
- **Permit2** — Optional; projects work without it
|
|
17
|
+
## 2. Economic Risks
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
### Bonding Curve
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
- **Zero cash out guard.** `cashOutFrom` returns 0 when `cashOutCount == 0` (line 31 early return). Auditors should verify no code path bypasses this guard or reaches the `cashOutCount >= totalSupply` branch (line 37) with both values at 0.
|
|
22
|
+
- **Pending reserved tokens inflate `totalSupply`.** `totalTokenSupplyWithReservedTokensOf()` adds `pendingReservedTokenBalanceOf` to `totalSupply`, reducing per-token cash out value. A project owner who delays calling `sendReservedTokensToSplitsOf()` can suppress cash out values. Auditors should model the magnitude of this effect for projects with large pending reserves.
|
|
23
|
+
- **`mulDiv` rounding.** The bonding curve's subadditivity property (`cashOutFrom(a) + cashOutFrom(b) <= cashOutFrom(a+b)`) can be violated by <0.01% due to floor rounding. Economically insignificant per operation but could accumulate across many small cash outs.
|
|
24
|
+
- **Binary search in `minCashOutCountFor`.** The inverse cash out function uses binary search over `[1, totalSupply]`. For large supplies (>2^128), this is ~128 iterations of `mulDiv` calls. Verify gas cost remains bounded.
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|------|-------------|------------|
|
|
26
|
-
| Data hook omnipotence | Data hooks override bonding curve parameters | Only use audited, trusted data hooks |
|
|
27
|
-
| Last-holder advantage | Last token holder redeems remaining surplus at 1:1 | Bonding curve math; inherent to the design |
|
|
28
|
-
| Pending reserved inflation | Pending reserved tokens dilute cash out values | Call `sendReservedTokensToSplitsOf` regularly |
|
|
29
|
-
| No reentrancy guard | Protocol relies on CEI ordering, not mutex | State updates before all external calls |
|
|
30
|
-
| Weight cache requirement | Projects with >20k cycles need progressive cache updates | Anyone can call `updateRulesetWeightCache` |
|
|
26
|
+
### Fee Arithmetic
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
- **Forward vs. backward fee asymmetry.** `feeAmountFrom` (forward) uses `mulDiv(amount, 25, 1000)`. `feeAmountResultingIn` (backward) uses `mulDiv(amount, 1000, 975) - amount`. These are algebraically equivalent but rounding differs. In `_returnHeldFees`, both are used on the same held fee entry (forward to compute `feeAmount`, backward when partially returning). Verify the interplay never undercharges.
|
|
29
|
+
- **Held fee amount mutation.** `_returnHeldFees` mutates `heldFee.amount` in-place via unchecked subtraction (line 1583). If the accounting is off by even 1 wei in the wrong direction, this underflows and corrupts the held fee entry.
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|------|-------------|------------|
|
|
36
|
-
| Price feed DoS | Stale/reverting price feed blocks multi-currency operations | Monitor feed health; single-currency projects unaffected |
|
|
37
|
-
| Split gas exhaustion | Very large split arrays (100+) may exceed block gas | Keep split count reasonable (<50) |
|
|
38
|
-
| Held fee growth | Held fees array grows without cleanup | `_nextHeldFeeIndexOf` pointer skips processed entries |
|
|
31
|
+
### First Cycle Behavior
|
|
39
32
|
|
|
40
|
-
|
|
33
|
+
- **`currentOf()` returns the stored ruleset directly in the first cycle.** The first cycle (`cycleNumber == 1`) uses the original weight with no decay applied. Weight decay via `weightCutPercent` only takes effect from the second cycle onward. This is by design and verified by test (`TestAuditResponseDesignProofs.test_currentOf_firstCycle_returnsOriginalWeight`).
|
|
41
34
|
|
|
42
|
-
|
|
35
|
+
### Weight Decay
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
| `_cashOutTokensOf` | Store balance deducted, tokens burned BEFORE transfer | LOW |
|
|
47
|
-
| `_pay` | Store balance added, tokens minted BEFORE pay hooks | LOW |
|
|
48
|
-
| `executePayout` | Payout limit recorded BEFORE split hook calls | LOW |
|
|
49
|
-
| `processHeldFeesOf` | Index updated BEFORE fee processing | LOW |
|
|
50
|
-
| `_sendReservedTokensToSplitsOf` | Pending balance zeroed BEFORE minting | LOW |
|
|
37
|
+
- **Weight cache starvation as DoS.** Projects with short duration and nonzero `weightCutPercent` that run >20,000 cycles without a cache update will revert on `currentOf()` with `WeightCacheRequired`. This blocks all operations (pay, cash out, payouts). Anyone can call `updateRulesetWeightCache()` to fix it, but an attacker could create projects designed to hit this.
|
|
38
|
+
- **Weight truncation.** Derived weight is cast to `uint112` in `_simulateCycledRulesetBasedOn`. If the derived weight exceeds `type(uint112).max` (unlikely but theoretically possible if cache state is corrupted), silent truncation occurs.
|
|
51
39
|
|
|
52
|
-
|
|
40
|
+
### Surplus Manipulation
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
- **Cross-terminal surplus aggregation.** When `useTotalSurplusForCashOuts` is enabled, `recordCashOutFor` aggregates surplus across all terminals via `JBSurplus.currentSurplusOf()`, which calls `terminal.currentSurplusOf()` on each. If a malicious terminal is added to the project's directory, it could report inflated surplus. Defense: `InadequateTerminalStoreBalance` revert prevents extracting more than the actual terminal balance.
|
|
43
|
+
- **Price feed inconsistency across terminals.** Different tokens in different terminals are converted to a common currency via `JBPrices`. If price feeds between terminals are stale or inconsistent, aggregated surplus can be inflated/deflated, affecting cash out reclaim amounts.
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
- ROOT operators cannot grant ROOT to other addresses
|
|
58
|
-
- Permission 0 is reserved and cannot be set
|
|
59
|
-
- All permission checks support ERC-2771 meta-transactions
|
|
45
|
+
## 3. Reentrancy Surface
|
|
60
46
|
|
|
61
|
-
|
|
47
|
+
No `ReentrancyGuard` is used. The system relies on state ordering and the `InadequateTerminalStoreBalance` backstop.
|
|
62
48
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
49
|
+
### External Call Map
|
|
50
|
+
|
|
51
|
+
| Function | State Changes Before External Call | External Calls | Risk |
|
|
52
|
+
|----------|-----------------------------------|----------------|------|
|
|
53
|
+
| `_pay` | `STORE.recordPaymentFrom` (balance incremented), `controller.mintTokensOf` (tokens minted) | Pay hooks via `_fulfillPayHookSpecificationsFor` | LOW -- full state settlement before hooks |
|
|
54
|
+
| `_cashOutTokensOf` | `STORE.recordCashOutFor` (balance decremented), `controller.burnTokensOf` (tokens burned), `_transferFrom` (beneficiary paid) | Cash out hooks via `_fulfillCashOutHookSpecificationsFor`, then `_takeFeeFrom` | MEDIUM -- beneficiary receives funds before hooks execute; hooks run before fees are taken |
|
|
55
|
+
| `executePayout` | `STORE.recordPayoutFor` already consumed payout limit | `split.hook.processSplitWith`, `terminal.pay/addToBalance` | MEDIUM -- split hook receives funds and can re-enter; payout limit already consumed prevents double-payout |
|
|
56
|
+
| `processHeldFeesOf` | `delete _heldFeesOf[...][currentIndex]`, `_nextHeldFeeIndexOf` incremented | `_processFee` -> `this.executeProcessFee` -> `terminal.pay` | LOW -- index advanced before external call; re-reads from storage each iteration |
|
|
57
|
+
| `_sendReservedTokensToSplitsOf` | `pendingReservedTokenBalanceOf` zeroed, tokens minted to controller | Split hooks, terminal payments | LOW -- pending balance cleared before minting prevents double-distribution |
|
|
58
|
+
| `_useAllowanceOf` | `STORE.recordUsedAllowanceOf` (allowance consumed, balance decremented) | `_takeFeeFrom` (fee payment/holding), `_transferFrom` (beneficiary) | LOW -- allowance consumed before calls |
|
|
59
|
+
| `migrateBalanceOf` | `STORE.recordTerminalMigration` (balance zeroed) | `to.addToBalanceOf` | LOW -- balance zeroed before transfer |
|
|
60
|
+
|
|
61
|
+
### Cross-Function Reentrancy to Explore
|
|
62
|
+
|
|
63
|
+
- **Pay hook -> `cashOutTokensOf`**: After `_pay` mints tokens, a pay hook could call `cashOutTokensOf`. The cash out sees post-payment balance and post-mint supply. Not profitable after fees in tested scenarios, but verify with data hooks that modify weights.
|
|
64
|
+
- **Cash out hook -> `pay`**: During `_cashOutTokensOf`, after tokens are burned and beneficiary is paid, a cash out hook could call `pay()` adding to the balance. Fees haven't been taken yet at this point. Verify the fee calculation on `amountEligibleForFees` isn't affected.
|
|
65
|
+
- **Split hook -> `pay` on same project**: During `sendPayoutsOf`, a split hook receives funds and calls `pay()` on the same project. Payout limit is consumed, but the payment increases balance and mints tokens. The funds came from the project's own balance, so no value creation -- but verify the accounting.
|
|
66
|
+
- **Fee processing -> any re-entry**: `_processFee` uses `this.executeProcessFee` (external call via try-catch). Inside, it calls `terminal.pay()` on project #1. If project #1 has a pay hook that calls back, the fee amount is already deducted.
|
|
67
|
+
|
|
68
|
+
### Key Backstop
|
|
69
|
+
|
|
70
|
+
`JBTerminalStore_InadequateTerminalStoreBalance` revert prevents extracting more than the recorded balance from any terminal regardless of reentrancy state. This is the final defense for all value extraction paths. Auditors should verify this check cannot be bypassed by manipulating the recorded balance (e.g., via `recordAddedBalanceFor`, which has no access control -- balance is keyed by `msg.sender`, so only a terminal can inflate its own balance).
|
|
71
|
+
|
|
72
|
+
## 4. Access Control
|
|
73
|
+
|
|
74
|
+
### Permission System
|
|
75
|
+
|
|
76
|
+
- **ROOT (ID 1) grants all permissions.** Including permissions not yet defined. Future permission IDs automatically fall under ROOT.
|
|
77
|
+
- **ROOT cannot be set for wildcard `projectId = 0`.** The actual enforcement in `setPermissionsFor` is: if the caller is not the account itself, they must have ROOT for the target project, AND they cannot set ROOT for others, AND they cannot set any permissions on the wildcard project. ROOT holders for a specific project can set non-ROOT permissions for operators on that project. Auditors should verify the exact boundary -- particularly whether a ROOT operator on project X can escalate to ROOT on project Y through any indirect path.
|
|
78
|
+
- **Empty permission arrays pass `hasPermissions`.** By design (vacuous truth). Any caller that expects "the operator has at least one of these permissions" must validate the array is non-empty.
|
|
79
|
+
- **`OMNICHAIN_RULESET_OPERATOR` bypass.** This immutable address can `launchRulesetsFor`, `queueRulesetsOf`, and set terminals for any project without owner permission. The trust assumption is that this operator only queues rulesets that the omnichain deployer's logic permits. If this address is an EOA or an upgradeable contract, it is a single point of failure for all projects.
|
|
80
|
+
|
|
81
|
+
### Splits GroupId Namespace
|
|
82
|
+
|
|
83
|
+
- **GroupId namespace overlap between terminals and token contracts is prevented.** Terminals use `uint160(tokenAddress)` as the `groupId` for payout split groups -- these have zero upper 96 bits. The self-auth path in `setSplitGroupsOf` now requires the upper 96 bits of the `groupId` to be non-zero (in addition to the lower 160 bits matching `msg.sender`). This means bare-address groupIds (upper 96 bits = 0) are protocol-reserved and always require controller authorization. Without this restriction, an accepted token contract could call `setSplitGroupsOf` to overwrite the terminal's payout splits for its own address. The 721 hook is unaffected since it uses `hookAddress | tierId << 160` (non-zero upper bits).
|
|
84
|
+
|
|
85
|
+
### Migration
|
|
86
|
+
|
|
87
|
+
- **Controller migration** requires `allowSetController` in the current ruleset. During migration, `JBController.migrate()` reverts if there are pending reserved tokens. An attacker cannot front-run migration to inflate pending reserves (they'd need mint permission), but a project with organic pending reserves must distribute them first.
|
|
88
|
+
- **Terminal migration** requires `allowTerminalMigration` in the current ruleset. Held fees are intentionally NOT migrated -- they belong to project #1. Verify that a project owner cannot use migration to escape held fee obligations.
|
|
89
|
+
- **Directory updates** (`setTerminalsOf`, `setControllerOf`) are gated by `IJBDirectoryAccessControl` checks that read from the current ruleset's metadata flags. If the current ruleset allows these changes, anyone with the appropriate permission can redirect all of a project's fund flows.
|
|
90
|
+
|
|
91
|
+
### Ruleset Queuing
|
|
92
|
+
|
|
93
|
+
- Only the project's controller can call `RULESETS.queueFor()` (enforced by `onlyControllerOf` modifier).
|
|
94
|
+
- The controller allows queuing by the project owner, anyone with `QUEUE_RULESETS` permission, or the `OMNICHAIN_RULESET_OPERATOR`.
|
|
95
|
+
- For `duration = 0` projects, a queued ruleset takes effect immediately. This means an owner can atomically change all project economics (weight, tax rate, splits, payout limits) in the same transaction as other operations.
|
|
96
|
+
|
|
97
|
+
## 5. DoS Vectors
|
|
98
|
+
|
|
99
|
+
### Unbounded Arrays
|
|
100
|
+
|
|
101
|
+
| Array | Growth Mechanism | Cleanup | Risk |
|
|
102
|
+
|-------|-----------------|---------|------|
|
|
103
|
+
| `_heldFeesOf[projectId][token]` | Each held-fee payout appends | `_nextHeldFeeIndexOf` pointer skips processed; full delete when all processed | MODERATE -- if held fees accumulate faster than the 28-day unlock window, the array grows unboundedly. `processHeldFeesOf` takes a `count` param, so partial processing is possible. |
|
|
104
|
+
| `splits[]` | Set by project owner per ruleset | Replaced wholesale | MODERATE -- no explicit cap. At 100+ splits, `_sendPayoutsToSplitGroupOf` gas exceeds 10M. Percentage constraint limits useful splits to ~300-500 but doesn't prevent a malicious owner from setting more. |
|
|
105
|
+
| `_accountingContextsOf[projectId]` | `addAccountingContextsFor` (append-only) | Never shrinks | LOW -- duplicate prevention limits growth; realistic max ~100 tokens. But since it's append-only, a project that accepts many tokens over time cannot remove old ones. |
|
|
106
|
+
| Payout limits / surplus allowances | Set per ruleset | Replaced per ruleset | LOW -- currency ordering constraint limits ~30-50. |
|
|
107
|
+
| `_terminalsOf[projectId]` | `setTerminalsOf` (replaced wholesale) | Replaced | LOW -- realistic max 5-10. |
|
|
108
|
+
|
|
109
|
+
### Price Feed Reverts
|
|
110
|
+
|
|
111
|
+
- If a Chainlink feed is stale beyond its threshold, `JBChainlinkV3PriceFeed` reverts. This blocks all multi-currency operations for projects using that feed: `pay`, `cashOutTokensOf`, `sendPayoutsOf`, `useAllowanceOf`.
|
|
112
|
+
- L2 sequencer downtime triggers `JBChainlinkV3SequencerPriceFeed` to revert during downtime + grace period.
|
|
113
|
+
- Single-currency projects (where `amount.currency == ruleset.baseCurrency()`) are unaffected.
|
|
114
|
+
- Price feeds are immutable once set in `JBPrices` -- a broken feed cannot be replaced.
|
|
115
|
+
|
|
116
|
+
### Approval Hook Griefing
|
|
117
|
+
|
|
118
|
+
- A reverting approval hook is caught by try-catch and treated as `Failed`. This causes fallback to `basedOnId` chain.
|
|
119
|
+
- A gas-consuming approval hook (e.g., infinite loop) can DoS `currentOf()` via gas exhaustion. The try-catch does not limit gas. This is accepted risk since the project owner chose their own approval hook, but it means a malicious approval hook can permanently freeze its project's operations.
|
|
120
|
+
- Approval hook rejection at a ruleset boundary triggers complex fallback behavior: the protocol simulates cycling from the last approved ruleset. Verify this simulation always produces economically correct results, especially when multiple rulesets are queued and rejected in sequence.
|
|
121
|
+
|
|
122
|
+
### Other DoS Surfaces
|
|
123
|
+
|
|
124
|
+
- `sendPayoutsOf` is callable by anyone (unless `ownerMustSendPayouts` is set). A split recipient that always reverts will cause that split's payout to fail, but the try-catch returns the amount to the project balance. Payout limit is still consumed. The project owner must wait until the next cycle.
|
|
125
|
+
- `addAccountingContextsFor` is gated by `allowAddAccountingContext` in the ruleset, but the contexts array is append-only and never shrinks. Over many rulesets, this could grow large enough to cause gas issues in functions that iterate over all contexts (e.g., `currentSurplusOf` when no explicit contexts are passed).
|
|
126
|
+
|
|
127
|
+
## 6. Integration Risks
|
|
128
|
+
|
|
129
|
+
### Non-Standard ERC-20s
|
|
130
|
+
|
|
131
|
+
- **Fee-on-transfer tokens**: Handled by `_acceptFundsFor` using balance-before/after pattern. The actual received amount is used, not the passed `amount`. However, `_transferFrom` for outbound transfers uses the nominal amount. If the token charges fees on transfer-out, the terminal's actual balance decreases more than `balanceOf` in the store records. Over time, `terminal.balance(token) < sum(store.balanceOf(projectId, terminal, token))`, breaking the balance conservation invariant.
|
|
132
|
+
- **Rebasing tokens**: Tokens that change balances (e.g., stETH, AMPL) will cause `JBTerminalStore.balanceOf` to diverge from actual terminal holdings. Positive rebases create untracked surplus; negative rebases can cause `InadequateTerminalStoreBalance` reverts on withdrawals.
|
|
133
|
+
- **Tokens with blocklists** (e.g., USDC, USDT): If a split beneficiary or cash out beneficiary is blocklisted, the transfer reverts. For split payouts, try-catch returns the amount to the project. For cash out beneficiaries, the entire `cashOutTokensOf` call reverts.
|
|
134
|
+
- **Low-decimal tokens** (e.g., USDC with 6 decimals): Weight and token counts use 18 decimals internally. The fixed-point conversion in `recordPaymentFrom` uses `mulDiv(amount.value, weight, weightRatio)`. With large weight values and small decimal tokens, precision loss may be significant.
|
|
135
|
+
|
|
136
|
+
### Permit2 Interactions
|
|
137
|
+
|
|
138
|
+
- `_acceptFundsFor` tries direct ERC-20 `transferFrom` first (if allowance is sufficient), then falls back to Permit2. The Permit2 `permit` call is wrapped in try-catch -- failure emits an event but doesn't revert the payment.
|
|
139
|
+
- `_transferFrom` for outbound transfers also falls back to Permit2 if direct allowance is insufficient. This means outbound transfers (to beneficiaries, split hooks) may unexpectedly use Permit2 state.
|
|
140
|
+
- The `uint160` cast on line 1897 of `JBMultiTerminal.sol` limits Permit2 transfers to `type(uint160).max`. Amounts above this revert with `OverflowAlert`.
|
|
141
|
+
|
|
142
|
+
### Cross-Terminal Surplus Aggregation
|
|
143
|
+
|
|
144
|
+
- `JBSurplus.currentSurplusOf` calls `terminal.currentSurplusOf()` on each terminal. These are external view calls with no gas limit. A malicious or gas-expensive terminal can cause this aggregation to revert, blocking cash outs for any project that has `useTotalSurplusForCashOuts` enabled and uses that terminal.
|
|
145
|
+
- The surplus calculation converts each terminal's balance to a common currency via price feeds. Rounding accumulates across terminals. With N terminals and M tokens each, there are N*M price conversions, each with up to 1 wei of rounding error.
|
|
146
|
+
|
|
147
|
+
### `recordAddedBalanceFor` Access Control
|
|
148
|
+
|
|
149
|
+
- `JBTerminalStore.recordAddedBalanceFor` has **no access control**. Any address can call it. The balance is keyed by `msg.sender` (the terminal address), so only a terminal can inflate its own recorded balance. This is safe as long as all terminals correctly track their actual holdings. A buggy or malicious terminal implementation could call `recordAddedBalanceFor` without actually receiving tokens, inflating the recorded balance above actual holdings.
|
|
150
|
+
|
|
151
|
+
## 7. Invariants to Verify
|
|
152
|
+
|
|
153
|
+
These should hold at all times and are the most productive targets for formal verification or invariant testing:
|
|
154
|
+
|
|
155
|
+
### Balance Conservation
|
|
156
|
+
- `terminal.balance(token) >= sum(store.balanceOf(projectId, terminal, token))` for all projects sharing a terminal. Fee amounts held but not yet processed are included in the terminal's actual balance but not in any project's store balance. Violation indicates a bug in fee handling or reentrancy.
|
|
157
|
+
|
|
158
|
+
### Fund Conservation
|
|
159
|
+
- Total inflows to a project (payments + `addToBalance`) >= total outflows (payouts + cash outs + surplus allowance usage + fees). Rounding should favor the protocol (fees round up, reclaims round down).
|
|
160
|
+
|
|
161
|
+
### Fee Monotonicity
|
|
162
|
+
- Project #1 balance only increases over time (fees flow in, never out via protocol mechanics). Exception: project #1 itself can pay out or cash out.
|
|
163
|
+
|
|
164
|
+
### Token Supply Consistency
|
|
165
|
+
- `TOKENS.totalSupplyOf(projectId) == creditSupply + erc20.totalSupply()` at all times.
|
|
166
|
+
- `totalTokenSupplyWithReservedTokensOf(projectId) == TOKENS.totalSupplyOf(projectId) + pendingReservedTokenBalanceOf[projectId]`.
|
|
167
|
+
|
|
168
|
+
### Payout Limit Enforcement
|
|
169
|
+
- `usedPayoutLimitOf[terminal][projectId][token][cycleNumber][currency] <= payoutLimitOf(...)` after every `recordPayoutFor`. Verify this holds even when the same project pays out from multiple terminals in the same cycle.
|
|
170
|
+
|
|
171
|
+
### Surplus Allowance Enforcement
|
|
172
|
+
- `usedSurplusAllowanceOf[terminal][projectId][token][rulesetId][currency] <= surplusAllowanceOf(...)` after every `recordUsedAllowanceOf`.
|
|
173
|
+
|
|
174
|
+
### Cash Out Bound
|
|
175
|
+
- `reclaimAmount + sum(hookSpecification.amounts) <= balanceOf[terminal][projectId][token]` after every `recordCashOutFor`. This is the `InadequateTerminalStoreBalance` check. Verify it is never circumvented.
|
|
176
|
+
|
|
177
|
+
### Ruleset Existence
|
|
178
|
+
- After `launchProjectFor()`, `RULESETS.currentOf(projectId)` always returns a valid ruleset (non-zero `cycleNumber`). A project in a state where `currentOf` returns an empty ruleset cannot accept payments (`RulesetNotFound` revert), but verify this cannot happen accidentally.
|
|
179
|
+
|
|
180
|
+
### No Flash-Loan Profit
|
|
181
|
+
- `pay() + cashOutTokensOf()` in the same transaction should never be profitable after fees. The 2.5% fee should make single-block round-trips unprofitable. Verify this holds when data hooks modify weights or cash out parameters.
|
|
182
|
+
|
|
183
|
+
### Same-Terminal Fee Exemption
|
|
184
|
+
|
|
185
|
+
- **Payouts between projects on the same terminal are fee-exempt by design.** When `executePayout` sends funds to a split's project that uses the same `JBMultiTerminal`, no fee is charged — the payout is routed via `addToBalanceOf` (internal accounting) rather than requiring an external transfer. Fees only apply when funds leave the terminal (sent to an EOA beneficiary or a different terminal). This is intentional: fees protect against fund egress, not intra-terminal accounting moves. Verified by test (`TestAuditResponseDesignProofs.test_sameTerminal_payoutNoFee`).
|
|
186
|
+
- **Fee-free surplus tracking prevents round-trip fee bypass.** When a project receives a fee-free intra-terminal payout, `_feeFreeSurplusOf[projectId][token]` is incremented by the payout amount. During cashout with `cashOutTaxRate == 0`, the 2.5% fee is applied only up to this accumulated surplus — then decremented. Once depleted, subsequent cashouts are fee-free again. This scopes the fee precisely to the fee-free inflow: a 1 wei griefing payout only costs the victim fees on 1 wei, not their entire balance. Without this, an attacker could route payouts through a pass-through project with `cashOutTaxRate=0` and cash out fee-free. When `cashOutTaxRate != 0`, the fee applies to the full reclaim amount regardless of surplus. Verified by 7 tests in `TestFeeFreeCashOutBypass.sol`.
|
|
187
|
+
|
|
188
|
+
### Held Fee Integrity
|
|
189
|
+
- `sum(heldFee.amount for active entries) + sum(processed fees) == total fees ever taken with shouldHoldFees=true`. Active entries are those from `_nextHeldFeeIndexOf` to end of array. Verify `_returnHeldFees`' in-place mutation of `heldFee.amount` preserves this invariant.
|
package/SKILLS.md
CHANGED
|
@@ -76,7 +76,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
76
76
|
|----------|--------------|
|
|
77
77
|
| `recordPaymentFrom(address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` | Records a payment. Applies data hook if enabled. Returns ruleset, token count, hook specifications. |
|
|
78
78
|
| `recordPayoutFor(uint256 projectId, JBAccountingContext accountingContext, uint256 amount, uint256 currency)` | Records a payout. Enforces payout limits. Returns ruleset and amount paid out. |
|
|
79
|
-
| `recordCashOutFor(address holder, uint256 projectId, uint256 cashOutCount, JBAccountingContext accountingContext, JBAccountingContext[] balanceAccountingContexts, bytes metadata)` | Records a cash out. Computes reclaim via bonding curve. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
|
|
79
|
+
| `recordCashOutFor(address holder, uint256 projectId, uint256 cashOutCount, JBAccountingContext accountingContext, JBAccountingContext[] balanceAccountingContexts, bool beneficiaryIsFeeless, bytes metadata)` | Records a cash out. Computes reclaim via bonding curve. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
|
|
80
80
|
| `recordUsedAllowanceOf(uint256 projectId, JBAccountingContext accountingContext, uint256 amount, uint256 currency)` | Records surplus allowance usage. Enforces allowance limits. Returns ruleset and used amount. |
|
|
81
81
|
| `recordAddedBalanceFor(uint256 projectId, address token, uint256 amount)` | Records funds added to a project's balance. |
|
|
82
82
|
| `recordTerminalMigration(uint256 projectId, address token)` | Records a terminal migration, returning the full balance. |
|
|
@@ -99,8 +99,8 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
99
99
|
| Function | What it does |
|
|
100
100
|
|----------|--------------|
|
|
101
101
|
| `setPermissionsFor(address account, JBPermissionsData permissionsData)` | Grants or revokes operator permissions. ROOT operators can set non-ROOT permissions for others. |
|
|
102
|
-
| `hasPermission(address operator, address account, uint256 projectId, uint256 permissionId)` | Checks if an operator has a specific permission. |
|
|
103
|
-
| `hasPermissions(address operator, address account, uint256 projectId, uint256[] permissionIds)` | Checks if an operator has all specified permissions. |
|
|
102
|
+
| `hasPermission(address operator, address account, uint256 projectId, uint256 permissionId, bool includeRoot, bool includeWildcardProjectId)` | Checks if an operator has a specific permission. |
|
|
103
|
+
| `hasPermissions(address operator, address account, uint256 projectId, uint256[] permissionIds, bool includeRoot, bool includeWildcardProjectId)` | Checks if an operator has all specified permissions. |
|
|
104
104
|
|
|
105
105
|
### JBDirectory
|
|
106
106
|
|
|
@@ -138,6 +138,8 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
138
138
|
|----------|--------------|
|
|
139
139
|
| `splitsOf(uint256 projectId, uint256 rulesetId, uint256 groupId)` | Returns splits for a project/ruleset/group. Falls back to ruleset ID 0 if none set. |
|
|
140
140
|
|
|
141
|
+
**Self-auth for `setSplitGroupsOf`**: A contract can set its own split groups without controller authorization if the lower 160 bits of the `groupId` match `msg.sender` AND the upper 96 bits are non-zero. GroupIds with zero upper 96 bits (bare addresses like `uint256(uint160(token))`) are protocol-reserved for terminal payout groups and always require controller auth.
|
|
142
|
+
|
|
141
143
|
### Other
|
|
142
144
|
|
|
143
145
|
| Function | What it does |
|
|
@@ -172,7 +174,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
172
174
|
| Struct | Key Fields | Used In |
|
|
173
175
|
|--------|------------|---------|
|
|
174
176
|
| `JBBeforePayRecordedContext` | `terminal`, `payer`, `amount (JBTokenAmount)`, `projectId`, `rulesetId`, `beneficiary`, `weight`, `reservedPercent`, `metadata` | `IJBRulesetDataHook.beforePayRecordedWith()` input |
|
|
175
|
-
| `JBBeforeCashOutRecordedContext` | `terminal`, `holder`, `projectId`, `rulesetId`, `cashOutCount`, `totalSupply`, `surplus (JBTokenAmount)`, `useTotalSurplus`, `cashOutTaxRate`, `metadata` | `IJBRulesetDataHook.beforeCashOutRecordedWith()` input |
|
|
177
|
+
| `JBBeforeCashOutRecordedContext` | `terminal`, `holder`, `projectId`, `rulesetId`, `cashOutCount`, `totalSupply`, `surplus (JBTokenAmount)`, `useTotalSurplus`, `cashOutTaxRate`, `beneficiaryIsFeeless`, `metadata` | `IJBRulesetDataHook.beforeCashOutRecordedWith()` input |
|
|
176
178
|
| `JBAfterPayRecordedContext` | `payer`, `projectId`, `rulesetId`, `amount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `weight`, `newlyIssuedTokenCount`, `beneficiary`, `hookMetadata`, `payerMetadata` | `IJBPayHook.afterPayRecordedWith()` input |
|
|
177
179
|
| `JBAfterCashOutRecordedContext` | `holder`, `projectId`, `rulesetId`, `cashOutCount`, `reclaimedAmount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `cashOutTaxRate`, `beneficiary`, `hookMetadata`, `cashOutMetadata` | `IJBCashOutHook.afterCashOutRecordedWith()` input |
|
|
178
180
|
| `JBPayHookSpecification` | `hook (IJBPayHook)`, `amount`, `metadata` | Returned by data hook; specifies which pay hooks to call and how much to forward |
|
|
@@ -210,8 +212,8 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
210
212
|
| `weight = 0` | `JBRuleset` / `JBRulesetConfig` | No token issuance for payments. |
|
|
211
213
|
| `weight = 1` | `JBRuleset` / `JBRulesetConfig` | Inherit decayed weight from previous ruleset (sentinel). |
|
|
212
214
|
| `duration = 0` | `JBRuleset` / `JBRulesetConfig` | Ruleset never expires; must be explicitly replaced by a new queued ruleset (takes effect immediately). |
|
|
213
|
-
| `projectId = 0` | `JBPermissionsData` | Wildcard: permission applies to ALL projects. Cannot be combined with ROOT (
|
|
214
|
-
| `permissionId =
|
|
215
|
+
| `projectId = 0` | `JBPermissionsData` | Wildcard: permission applies to ALL projects. Cannot be combined with ROOT (1). |
|
|
216
|
+
| `permissionId = 1` | `JBPermissions` | ROOT: grants all permissions for the scoped project. |
|
|
215
217
|
| `rulesetId = 0` | `JBSplits.splitsOf()` | Fallback split group used when no splits are set for a specific ruleset. |
|
|
216
218
|
| `projectId = 0` | `JBPrices.addPriceFeedFor()` | Sets a protocol-wide default price feed (owner-only). |
|
|
217
219
|
|
|
@@ -233,6 +235,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
233
235
|
- `JBERC20` is cloned via `Clones.clone()` -- its constructor sets invalid name/symbol; real values set in `initialize()`
|
|
234
236
|
- Fee is 2.5% (`FEE = 25` out of `MAX_FEE = 1000`)
|
|
235
237
|
- Project #1 is the fee beneficiary project (receives all protocol fees)
|
|
238
|
+
- **Fee-free cashout exemption is scoped to fee-free intra-terminal payout amounts.** `_feeFreeSurplusOf[projectId][token]` accumulates the value of fee-free payouts. During cashout with `cashOutTaxRate=0`, the 2.5% fee applies only up to this surplus, then depletes. Once consumed, subsequent cashouts are fee-free again. This prevents a round-trip fee bypass (intra-terminal payout → zero-tax cashout) while scoping fees precisely to the fee-free inflow.
|
|
236
239
|
- `JBProjects` constructor optionally mints project #1 to `feeProjectOwner` -- if `address(0)`, no fee project is created
|
|
237
240
|
- `JBMultiTerminal` derives `DIRECTORY` and `RULESETS` from the provided `store` in its constructor -- not passed directly
|
|
238
241
|
- `JBPrices.pricePerUnitOf()` checks project-specific feed, then inverse, then falls back to `DEFAULT_PROJECT_ID = 0`
|