@bananapus/core-v6 0.0.29 → 0.0.31

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.
Files changed (44) hide show
  1. package/ADMINISTRATION.md +43 -13
  2. package/ARCHITECTURE.md +62 -137
  3. package/AUDIT_INSTRUCTIONS.md +149 -428
  4. package/CHANGELOG.md +73 -0
  5. package/README.md +90 -201
  6. package/RISKS.md +27 -12
  7. package/SKILLS.md +31 -441
  8. package/STYLE_GUIDE.md +52 -19
  9. package/USER_JOURNEYS.md +76 -627
  10. package/package.json +2 -3
  11. package/references/entrypoints.md +160 -0
  12. package/references/types-errors-events.md +297 -0
  13. package/script/Deploy.s.sol +7 -2
  14. package/script/DeployPeriphery.s.sol +51 -4
  15. package/src/JBController.sol +11 -3
  16. package/src/JBDirectory.sol +1 -0
  17. package/src/JBMultiTerminal.sol +126 -72
  18. package/src/JBRulesets.sol +2 -1
  19. package/src/JBTerminalStore.sol +22 -11
  20. package/src/abstract/JBControlled.sol +7 -1
  21. package/src/abstract/JBPermissioned.sol +1 -1
  22. package/src/interfaces/IJBRulesetDataHook.sol +5 -4
  23. package/src/libraries/JBCashOuts.sol +1 -1
  24. package/src/libraries/JBConstants.sol +1 -1
  25. package/src/libraries/JBCurrencyIds.sol +1 -1
  26. package/src/libraries/JBFees.sol +1 -1
  27. package/src/libraries/JBFixedPointNumber.sol +1 -1
  28. package/src/libraries/JBMetadataResolver.sol +1 -1
  29. package/src/libraries/JBPayoutSplitGroupLib.sol +3 -1
  30. package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
  31. package/src/libraries/JBSplitGroupIds.sol +1 -1
  32. package/src/libraries/JBSurplus.sol +1 -1
  33. package/src/structs/JBSplit.sol +4 -1
  34. package/test/TestForwardedTokenConsumption.sol +418 -0
  35. package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
  36. package/test/units/static/JBController/TestPreviewMintOf.sol +5 -3
  37. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -11
  38. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
  39. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
  40. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
  41. package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
  42. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
  43. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
  44. package/CHANGE_LOG.md +0 -479
package/CHANGELOG.md ADDED
@@ -0,0 +1,73 @@
1
+ # Changelog
2
+
3
+ ## Scope
4
+
5
+ This file describes the verified change from `nana-core-v5` to the current `nana-core-v6` repo.
6
+
7
+ ## Current v6 surface
8
+
9
+ - `JBController`
10
+ - `JBMultiTerminal`
11
+ - `JBTerminalStore`
12
+ - `JBRulesets`
13
+ - `JBTokens`
14
+ - the shared core interfaces, structs, and libraries under `src/`
15
+
16
+ ## Summary
17
+
18
+ - v6 adds explicit preview APIs for pay and cash-out flows. Integrations can simulate more of the terminal path directly from the core contracts.
19
+ - Token metadata is more editable than in v5. The controller now exposes a dedicated token-metadata update path.
20
+ - Approval-hook handling is safer. The v6 codebase and tests are built around preventing a bad approval hook from freezing project behavior.
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 audit, invariant, fork, and formal-style suites.
23
+ - The implementation baseline moved from the v5 `0.8.23` world to `0.8.28`.
24
+
25
+ ## Verified deltas
26
+
27
+ - `IJBTerminal.previewPayFor(...)` is new.
28
+ - `IJBCashOutTerminal.previewCashOutFrom(...)` and `IJBTerminalStore.previewCashOutFrom(...)` are new preview surfaces.
29
+ - `IJBController.setTokenMetadataOf(uint256,string,string)` is new.
30
+ - `IJBController.addPriceFeed(...)` became `addPriceFeedFor(...)`.
31
+ - `IJBTerminal.currentSurplusOf(...)` now takes `address[] calldata tokens` instead of the old accounting-context array input.
32
+ - The interface surface adds explicit hook-spec return types to preview flows, which changes what off-chain callers can and should decode.
33
+
34
+ ## Breaking ABI changes
35
+
36
+ - `IJBController.addPriceFeed(...)` was renamed to `addPriceFeedFor(...)`.
37
+ - `IJBController.setTokenMetadataOf(...)` is new and sits on the core controller surface.
38
+ - `IJBController.previewMintOf(...)` is new.
39
+ - `IJBTerminal.previewPayFor(...)` is new.
40
+ - `IJBCashOutTerminal.previewCashOutFrom(...)` is new.
41
+ - `IJBTerminalStore.previewPayFrom(...)` and `previewCashOutFrom(...)` are new.
42
+ - `IJBTerminal.currentSurplusOf(...)` changed parameter shape.
43
+
44
+ ## Indexer impact
45
+
46
+ - `SplitHookReverted` is a new controller event.
47
+ - Preview functions do not emit events, but they change what frontends and simulation services can derive without sending transactions.
48
+ - If your indexer inferred token metadata immutability from v5, that assumption is no longer safe once `setTokenMetadataOf(...)` is in use.
49
+
50
+ ## Migration notes
51
+
52
+ - Rebuild against the v6 interfaces. This repo is too central for hand-maintained ABI diffs to be trustworthy.
53
+ - Review any integration that assumed old ruleset-cache behavior, old preview availability, or old token-metadata immutability.
54
+ - If your system relied on subtle fee-free cash-out behavior from v5, re-validate it against the v6 accounting model.
55
+
56
+ ## ABI appendix
57
+
58
+ - Added functions
59
+ - `IJBTerminal.previewPayFor(...)`
60
+ - `IJBCashOutTerminal.previewCashOutFrom(...)`
61
+ - `IJBTerminalStore.previewPayFrom(...)`
62
+ - `IJBTerminalStore.previewCashOutFrom(...)`
63
+ - `IJBController.previewMintOf(...)`
64
+ - `IJBController.setTokenMetadataOf(...)`
65
+ - Renamed functions
66
+ - `IJBController.addPriceFeed(...)` -> `addPriceFeedFor(...)`
67
+ - Changed function shapes
68
+ - `IJBTerminal.currentSurplusOf(...)`
69
+ - Added events
70
+ - `SplitHookReverted`
71
+ - Added migration-sensitive capabilities
72
+ - mutable token metadata
73
+ - preview-oriented hook-spec decoding
package/README.md CHANGED
@@ -1,238 +1,127 @@
1
- # Juicebox Core
1
+ # Juicebox Core V6
2
2
 
3
- The core protocol contracts for Juicebox V6 on EVM. A flexible toolkit for launching and managing a treasury-backed token on Ethereum and L2s.
3
+ `@bananapus/core-v6` is the core protocol package for Juicebox on EVM chains. It defines projects, rulesets, terminals, permissions, token issuance, cash outs, splits, price feeds, and the accounting surfaces that the rest of the V6 ecosystem builds on.
4
4
 
5
- For full documentation, see [docs.juicebox.money](https://docs.juicebox.money/). If you have questions, reach out on [Discord](https://discord.com/invite/ErQYmth4dS).
5
+ Docs: <https://docs.juicebox.money>
6
+ Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
6
7
 
7
- ## Conceptual Overview
8
+ ## Overview
8
9
 
9
- Juicebox projects have two main entry points:
10
+ If a V6 package moves value, mints tokens, checks permissions, or reasons about project configuration, it almost certainly depends on this repo.
10
11
 
11
- - **Terminals** handle inflows and outflows of funds -- payments, cash outs, payouts, and surplus allowance usage. Each project can use multiple terminals, and a single terminal can serve many projects. `JBMultiTerminal` is the standard implementation.
12
- - **Controllers** manage rulesets and tokens. `JBController` is the standard implementation that coordinates ruleset queuing, token minting/burning, splits, and fund access limits.
12
+ The core package provides:
13
13
 
14
- `JBDirectory` maps each project to its controller and terminals.
14
+ - project ownership and metadata through `JBProjects`
15
+ - ruleset lifecycle management through `JBRulesets`
16
+ - issuance, queueing, token setup, and splits through `JBController`
17
+ - multi-token terminal accounting through `JBMultiTerminal` and `JBTerminalStore`
18
+ - operator permissions through `JBPermissions`
19
+ - on-chain price-feed routing through `JBPrices`
15
20
 
16
- ### Rulesets
21
+ Use this repo when you need the canonical protocol invariant. Do not duplicate its logic in downstream packages unless the repo is explicitly intended to wrap or extend the core surface.
17
22
 
18
- A project's behavior is governed by a queue of **rulesets**. Each ruleset defines the rules that apply for a specific duration: payment weight (tokens minted per unit paid), cash out tax rate, reserved percent, payout limits, approval hooks, and more. When a ruleset ends, the next one in the queue takes effect. If the queue is empty, the current ruleset keeps cycling with weight decay applied each cycle. Rulesets give project creators the ability to evolve their project's rules while offering supporters contractual guarantees about the future.
23
+ If you only read one repo before auditing the rest of the ecosystem, read this one.
19
24
 
20
- Key ruleset behaviors:
21
- - **Weight** determines token issuance per unit paid. A weight of 1 means "inherit decayed weight from the previous ruleset". A weight of 0 means "no issuance".
22
- - **Weight decay** is controlled by `weightCutPercent`. Each cycle, the weight is reduced by this percent (9-decimal precision out of `1_000_000_000`).
23
- - **Duration of 0** means the ruleset never expires and must be explicitly replaced by a new queued ruleset (which takes effect immediately).
24
- - **Approval hooks** can gate whether queued rulesets take effect. For example, `JBDeadline` requires rulesets to be queued a minimum number of seconds before the current ruleset ends.
25
+ ## Mental Model
25
26
 
26
- ### Fund Distribution
27
+ The core protocol is easiest to reason about in four layers:
27
28
 
28
- Funds can be accessed through **payouts** (distributed to splits within payout limits, resetting each ruleset cycle) or **surplus allowance** (discretionary withdrawal of surplus funds, does not reset each cycle). Funds beyond payout limits are surplus -- available for cash outs if the project's cash out tax rate allows it.
29
+ 1. identity and configuration: `JBProjects`, `JBDirectory`, `JBRulesets`
30
+ 2. execution: `JBController` and `JBMultiTerminal`
31
+ 3. accounting: `JBTerminalStore`
32
+ 4. permissions and external context: `JBPermissions`, `JBPrices`, feeless-address and deadline helpers
29
33
 
30
- - **Payout limits** are denominated in configurable currencies and can be set per terminal/token. Multiple limits in different currencies can be active simultaneously.
31
- - **Surplus allowances** allow project owners to withdraw surplus funds up to a configured amount, also denominated in configurable currencies.
34
+ Most integrations touch only layer 2. Most economically important bugs leak through layer 3.
32
35
 
33
- ### Payments, Tokens, and Cash Outs
36
+ The shortest path through the repo is:
34
37
 
35
- Payments mint credits (or ERC-20 tokens if an ERC-20 has been deployed for the project) for the payer based on the current ruleset's weight. The number of tokens minted can be influenced by a data hook.
38
+ 1. `JBController` for project launch and ruleset configuration
39
+ 2. `JBMultiTerminal` for execution entrypoints
40
+ 3. `JBTerminalStore` for economic truth
41
+ 4. `JBDirectory` and `JBPermissions` for routing and authority
36
42
 
37
- Credits and tokens can be **cashed out** to reclaim surplus funds along a bonding curve determined by the cash out tax rate:
38
- - A **0% tax rate** gives proportional (1:1) redemption of surplus.
39
- - A **100% tax rate** means nothing can be reclaimed (all surplus is locked).
40
- - Tax rates between 0% and 100% create a bonding curve that incentivizes holding -- later cashers-out get a better rate per token.
43
+ ## Read These Files First
41
44
 
42
- ### Preview APIs
45
+ 1. `src/JBController.sol`
46
+ 2. `src/JBMultiTerminal.sol`
47
+ 3. `src/JBTerminalStore.sol`
48
+ 4. `src/JBDirectory.sol`
49
+ 5. `src/JBRulesets.sol`
50
+ 6. `src/JBPermissions.sol`
43
51
 
44
- Every core user action -- paying, cashing out, and minting -- has a corresponding **preview** function that simulates the operation on-chain without modifying state. These are essential for building UIs with accurate slippage estimates and for integrators who need to know exact outcomes before committing transactions.
52
+ ## Key Contracts
45
53
 
46
- - `JBMultiTerminal.previewPayFor(projectId, token, amount, beneficiary, metadata)` -- Returns the ruleset, beneficiary token count, reserved token count, and hook specifications that a payment would produce. Includes data hook effects.
47
- - `JBMultiTerminal.previewCashOutFrom(holder, projectId, cashOutCount, tokenToReclaim, beneficiary, metadata)` -- Returns the ruleset, reclaim amount, cash out tax rate, and hook specifications that a cash out would produce. Includes data hook effects.
48
- - `JBController.previewMintOf(projectId, tokenCount, useReservedPercent)` -- Returns the beneficiary and reserved token counts that a mint would produce under the current ruleset.
54
+ | Contract | Role |
55
+ | --- | --- |
56
+ | `JBController` | Project launch, ruleset queueing, token configuration, and split management. |
57
+ | `JBMultiTerminal` | Main payment, payout, allowance, and cash-out terminal surface. |
58
+ | `JBTerminalStore` | Shared accounting store for balances, surplus, fees, and reclaim calculations. |
59
+ | `JBDirectory` | Project-to-controller and project-to-terminal routing registry. |
60
+ | `JBProjects` | ERC-721 project registry and ownership surface. |
61
+ | `JBPermissions` | Packed operator permissions registry. |
62
+ | `JBPrices` | Price feed routing used by terminals and integrations. |
49
63
 
50
- All preview functions are `view` -- they read current state (including calling data hooks) but never write. They mirror the exact computation paths of their non-preview counterparts, so the returned values match what the real operation would produce at the same block.
64
+ ## Integration Traps
51
65
 
52
- ### Reserved Tokens
66
+ - `JBMultiTerminal` is not a single-token terminal. Integrations that assume one token, one balance, or one primary path usually misread the accounting model.
67
+ - data hooks and cash-out hooks are not cosmetic. They can change effective issuance, reclaim value, and side effects on the path.
68
+ - permission checks are not always against the project owner. Some flows are scoped to the token holder instead.
69
+ - previews and execution are intentionally close, but integrators should still treat them as distinct surfaces when hooks or dynamic routing are involved.
53
70
 
54
- Each ruleset can define a `reservedPercent` (0-10,000 basis points). When tokens are minted from payments, this percentage is set aside. Reserved tokens accumulate in `pendingReservedTokenBalanceOf` and are distributed to the reserved token split group when `sendReservedTokensToSplitsOf` is called.
71
+ ## Where State Lives
55
72
 
56
- ### Permissions
73
+ - project identity and ownership live in `JBProjects`
74
+ - controller and terminal routing live in `JBDirectory`
75
+ - ruleset history and activation live in `JBRulesets`
76
+ - balances, surplus, fees, and reclaim accounting live in `JBTerminalStore`
77
+ - operator authority lives in `JBPermissions`
57
78
 
58
- `JBPermissions` lets addresses delegate specific capabilities to operators, scoped by project ID. Each permission ID grants access to specific functions. See [`JBPermissionIds`](https://github.com/Bananapus/nana-permission-ids-v6/blob/main/src/JBPermissionIds.sol) for the full list.
79
+ When in doubt, read the state-owning contract before the contract that merely forwards into it.
59
80
 
60
- - Permission ID `1` is `ROOT` and grants all permissions for the scoped project.
61
- - Project ID `0` is a wildcard, granting permissions across all projects (cannot be combined with `ROOT` for safety).
62
- - ROOT operators can set non-ROOT permissions for other operators, but cannot grant ROOT or set wildcard-project permissions.
63
-
64
- ### Hooks
65
-
66
- Hooks are customizable contracts that plug into protocol flows:
67
-
68
- - **Approval hooks** -- Gate whether the next queued ruleset can take effect (e.g., `JBDeadline` enforces a minimum queue time).
69
- - **Data hooks** -- Override payment/cash-out weight, cash out tax rate, token counts, and specify pay/cash-out hooks to call. Data hooks can also grant `hasMintPermissionFor` to allow addresses to mint tokens on demand.
70
- - **Pay hooks** -- Custom logic triggered after a payment is recorded (e.g., `JB721TiersHook` mints NFTs). Receive tokens and `JBAfterPayRecordedContext`.
71
- - **Cash out hooks** -- Custom logic triggered after a cash out is recorded. Receive tokens and `JBAfterCashOutRecordedContext`.
72
- - **Split hooks** -- Custom logic triggered when a payout or reserved token distribution is routed to a split. Receive tokens and `JBSplitHookContext`.
73
-
74
- ### Fees
75
-
76
- `JBMultiTerminal` charges a 2.5% fee (`FEE = 25` out of `MAX_FEE = 1000`) on:
77
- - Payouts to external addresses (not to other Juicebox projects on the same terminal).
78
- - Surplus allowance usage.
79
- - Cash outs when the cash out tax rate is above 0%. When the cash out tax rate is 0%, fees apply only up to the project's accumulated fee-free intra-terminal payout surplus (`_feeFreeSurplusOf`) — once that surplus is consumed, subsequent cashouts are fee-free.
80
-
81
- `_feeFreeSurplusOf` lifecycle: (a) incremented on fee-free intra-terminal payouts, (b) capped at remaining balance after any outflow (payouts, `useAllowanceOf`, non-zero-tax or feeless cashouts) — non-fee-free funds leave first, (c) consumed (decremented by feeable amount) during zero-tax cashouts, and (d) cleared to zero on terminal migration via `migrateBalanceOf`.
82
-
83
- Fees are paid to **project #1** (the fee beneficiary project, minted in the `JBProjects` constructor). Addresses on the `JBFeelessAddresses` allowlist are exempt from fees.
84
-
85
- When a ruleset has `holdFees` enabled, fees are held for 28 days before being processed. During this period, if funds are returned to the project via `addToBalanceOf`, held fees can be unlocked and returned.
86
-
87
- ### Meta-Transactions
88
-
89
- `JBController`, `JBMultiTerminal`, `JBProjects`, `JBPrices`, and `JBPermissions` support ERC-2771 meta-transactions through a trusted forwarder. This allows gasless interactions where a relayer submits transactions on behalf of users.
90
-
91
- ### Permit2
92
-
93
- `JBMultiTerminal` integrates with Uniswap's [Permit2](https://github.com/Uniswap/permit2) for gas-efficient ERC-20 token approvals. Payers can include a `JBSingleAllowance` in the payment metadata to authorize token transfers without a separate approval transaction.
94
-
95
- ### Controller Migration
96
-
97
- Projects can migrate between controllers using the `IJBMigratable` interface. The migration lifecycle calls `beforeReceiveMigrationFrom` on the new controller, then `migrate` on the old controller (while the directory still points to it), then updates the directory, and finally calls `afterReceiveMigrationFrom`. Terminal migration is also supported via `migrateBalanceOf`.
98
-
99
- ## Architecture
100
-
101
- Juicebox V6 separates concerns across specialized contracts that coordinate through a central directory. Projects are represented as ERC-721 NFTs. Each project configures rulesets that dictate how payments, payouts, cash outs, and token minting behave over time.
102
-
103
- All contracts use Solidity `0.8.28`.
81
+ ## Install
104
82
 
105
- ```mermaid
106
- graph TD;
107
- User([User / Frontend])
108
- User -->|pay / cashOut / sendPayoutsOf| Terminal[JBMultiTerminal]
109
- User -->|launchProjectFor / queueRulesetsOf / mintTokensOf| Controller[JBController]
110
- Controller -->|reads & writes| Rulesets[JBRulesets]
111
- Controller -->|reads & writes| Tokens[JBTokens]
112
- Controller -->|reads & writes| Splits[JBSplits]
113
- Controller -->|reads & writes| FAL[JBFundAccessLimits]
114
- Controller -->|adds feeds to| Prices[JBPrices]
115
- Terminal -->|records inflows & outflows| Store[JBTerminalStore]
116
- Store -->|reads rulesets from| Rulesets
117
- Store -->|reads limits from| FAL
118
- Store -->|reads prices from| Prices
119
- Terminal -->|distributes payouts via| Splits
120
- Directory[JBDirectory] -->|maps projects to| Controller
121
- Directory -->|maps projects to| Terminal
122
- Controller -->|registers in| Directory
123
- Terminal -->|looks up controllers via| Directory
124
- Projects[JBProjects] -->|ERC-721 ownership| Directory
125
- Permissions[JBPermissions] -->|authorizes calls on| Controller
126
- Permissions -->|authorizes calls on| Terminal
83
+ ```bash
84
+ npm install @bananapus/core-v6
127
85
  ```
128
86
 
129
- ### Core Contracts
130
-
131
- | Contract | Description |
132
- |----------|-------------|
133
- | `JBProjects` | ERC-721 registry of projects. Minting an NFT creates a project. Optionally mints project #1 to a fee beneficiary owner. |
134
- | `JBPermissions` | Bitmap-based permission system. Accounts grant operators specific permissions scoped to project IDs. Supports ROOT (1) for all-permissions and wildcard project ID (0). |
135
- | `JBDirectory` | Maps each project to its controller and terminals. Entry point for looking up where to interact with a project. Manages an allowlist of addresses permitted to set a project's first controller. |
136
- | `JBController` | Coordinates rulesets, tokens, splits, and fund access limits. Entry point for launching projects, queuing rulesets, minting/burning tokens, deploying ERC-20s, updating token metadata, sending reserved tokens, setting project URIs, adding price feeds, transferring credits, and previewing mint outcomes via `previewMintOf`. |
137
- | `JBMultiTerminal` | Accepts payments (native ETH and ERC-20s), processes cash outs, distributes payouts, manages surplus allowances, and handles fees. Provides `previewPayFor` and `previewCashOutFrom` for simulating operations. Integrates with Permit2 for ERC-20 approvals. |
138
- | `JBTerminalStore` | Bookkeeping engine for all terminal inflows and outflows. Tracks balances, enforces payout limits and surplus allowances, computes cash out reclaim amounts via a bonding curve, and integrates with data hooks. |
139
- | `JBRulesets` | Stores and manages project rulesets. Handles queuing, cycling, weight decay, approval hook validation, and weight caching for long-running projects. |
140
- | `JBTokens` | Manages dual-balance token accounting (credits + ERC-20). Credits are minted by default; once an ERC-20 is deployed or set, credits can be claimed as tokens. Credits are burned before ERC-20 tokens. |
141
- | `JBSplits` | Stores split configurations per project, ruleset, and group. Splits route percentages of payouts or reserved tokens to beneficiaries, projects, or hooks. Packed storage for gas efficiency. Falls back to ruleset ID 0 if no splits are set for a specific ruleset. |
142
- | `JBFundAccessLimits` | Stores payout limits and surplus allowances per project, ruleset, terminal, and token. Limits are denominated in configurable currencies and must be set in strictly increasing currency order to prevent duplicates. |
143
- | `JBPrices` | Price feed registry. Maps currency pairs to `IJBPriceFeed` implementations, with per-project overrides and protocol-wide defaults. Feeds are immutable once set. Inverse prices are auto-calculated. |
144
-
145
- ### Token and Price Feed Contracts
146
-
147
- | Contract | Description |
148
- |----------|-------------|
149
- | `JBERC20` | Cloneable ERC-20 with ERC20Votes and ERC20Permit. Deployed by `JBTokens` via `Clones.clone()`. Owned by `JBTokens`. Name and symbol can be updated by the project owner via `JBController.setTokenMetadataOf`. |
150
- | `JBChainlinkV3PriceFeed` | `IJBPriceFeed` backed by a Chainlink `AggregatorV3Interface` with staleness threshold. Rejects negative/zero prices, incomplete rounds (`updatedAt == 0`), and stale answers carried from previous rounds (`answeredInRound < roundId`). |
151
- | `JBChainlinkV3SequencerPriceFeed` | Extends `JBChainlinkV3PriceFeed` with L2 sequencer uptime validation and grace period for Optimism/Arbitrum. |
152
- | `JBMatchingPriceFeed` | Returns 1:1 price (e.g., ETH/NATIVE_TOKEN on applicable chains). Lives in `src/periphery/`. |
87
+ ## Development
153
88
 
154
- ### Utility Contracts
155
-
156
- | Contract | Description |
157
- |----------|-------------|
158
- | `JBFeelessAddresses` | Owner-managed allowlist of addresses exempt from terminal fees. Supports `IERC165`. |
159
- | `JBDeadline` | Approval hook that rejects rulesets queued too close to the current ruleset's end. Ships as `JBDeadline3Hours`, `JBDeadline1Day`, `JBDeadline3Days`, `JBDeadline7Days`. |
160
-
161
- ### Abstract Contracts
162
-
163
- | Contract | Description |
164
- |----------|-------------|
165
- | `JBControlled` | Provides `onlyControllerOf(projectId)` modifier. Used by `JBRulesets`, `JBTokens`, `JBSplits`, `JBFundAccessLimits`, and `JBPrices`. |
166
- | `JBPermissioned` | Provides `_requirePermissionFrom` and `_requirePermissionAllowingOverrideFrom` helpers. Used by `JBController`, `JBMultiTerminal`, `JBDirectory`, and `JBPrices`. |
167
-
168
- ### Libraries
169
-
170
- | Library | Description |
171
- |---------|-------------|
172
- | `JBConstants` | Protocol-wide constants: `NATIVE_TOKEN` address, max percentages, max fee. |
173
- | `JBCurrencyIds` | Currency identifiers (`ETH = 1`, `USD = 2`). |
174
- | `JBSplitGroupIds` | Group identifiers (`RESERVED_TOKENS = 1`). |
175
- | `JBCashOuts` | Bonding curve math for computing cash out reclaim amounts. Includes `minCashOutCountFor` inverse via binary search. |
176
- | `JBSurplus` | Calculates a project's surplus across all terminals. |
177
- | `JBFees` | Fee calculation helpers. `feeAmountFrom` (forward) and `feeAmountResultingIn` (backward). |
178
- | `JBFixedPointNumber` | Decimal adjustment between fixed-point number precisions. |
179
- | `JBMetadataResolver` | Packs and unpacks variable-length `{id: data}` metadata entries with a lookup table. Used by pay/cash-out hooks. |
180
- | `JBPayoutSplitGroupLib` | External library for payout split-group distribution, called via `DELEGATECALL` from `JBMultiTerminal` to reduce terminal bytecode. Distributes payouts to splits with try-catch per split. |
181
- | `JBRulesetMetadataResolver` | Packs and unpacks the `uint256 metadata` field on `JBRuleset` into `JBRulesetMetadata`. Bit layout: version (4 bits), reservedPercent (16), cashOutTaxRate (16), baseCurrency (32), 14 boolean flags (1 bit each), dataHook address (160), metadata (14). |
182
-
183
- ### Hook Interfaces
184
-
185
- | Interface | Description |
186
- |-----------|-------------|
187
- | `IJBRulesetApprovalHook` | Determines whether the next queued ruleset is approved or rejected. Must implement `approvalStatusOf` and `DURATION`. |
188
- | `IJBRulesetDataHook` | Overrides payment/cash-out parameters. Implements `beforePayRecordedWith`, `beforeCashOutRecordedWith`, and `hasMintPermissionFor`. |
189
- | `IJBPayHook` | Called after a payment is recorded. Implements `afterPayRecordedWith`. |
190
- | `IJBCashOutHook` | Called after a cash out is recorded. Implements `afterCashOutRecordedWith`. |
191
- | `IJBSplitHook` | Called when processing a split. Implements `processSplitWith`. |
192
-
193
- ## Risks
194
-
195
- This section summarizes known risks and design trade-offs. None of these are unmitigated vulnerabilities -- they are deliberate design decisions with well-understood boundaries.
196
-
197
- ### Reentrancy
198
-
199
- The protocol does not use an explicit `ReentrancyGuard`. Instead, it relies on **state ordering**: all storage writes (balance updates, token mints/burns, payout limit usage) are completed before any external calls (hooks, split payouts, fee processing). This means re-entering a function sees already-updated state, preventing double-spends and double-payouts. The `try-catch` pattern around external calls ensures that hook failures do not leave the protocol in an inconsistent state -- failed calls return funds to the project balance. This includes reserved token split hooks: `processSplitWith` is wrapped in try-catch, and a reverting hook emits `SplitHookReverted` instead of blocking distribution.
200
-
201
- ### Unbounded Arrays
202
-
203
- Several data structures grow without explicit caps:
204
-
205
- - **Held fees**: Bounded in practice by the 28-day holding window and auto-cleanup when processed. `processHeldFeesOf` accepts a `count` parameter to limit gas per call.
206
- - **Splits**: No explicit cap, but the percentage constraint (`SPLITS_TOTAL_PERCENT = 1_000_000_000`) limits the useful number to roughly 300-500. Gas cost scales linearly (~100k gas per split during payout distribution), so very large split groups may cause out-of-gas reverts.
207
- - **Accounting contexts**: Duplicate prevention limits growth. Realistic maximum is ~100-200 tokens per terminal.
208
- - **Payout limits / surplus allowances**: Currency ordering constraint limits each group to ~30-50 entries.
89
+ ```bash
90
+ npm install
91
+ forge build
92
+ forge test
93
+ ```
209
94
 
210
- ### Price Feed DoS
95
+ Useful scripts:
211
96
 
212
- If a Chainlink price feed reverts (stale data, negative price, sequencer downtime on L2), any operation that requires that currency conversion will also revert. This is a **liveness** risk, not a fund-loss risk: no funds are at risk, but payments, payouts, or cash outs denominated in the affected currency pair will be temporarily blocked until the feed recovers. Projects using multiple currencies should be aware of this dependency.
97
+ - `npm run test:fork`
98
+ - `npm run deploy:mainnets`
99
+ - `npm run deploy:testnets`
100
+ - `npm run deploy:mainnets:periphery`
101
+ - `npm run deploy:testnets:periphery`
213
102
 
214
- ### Weight Cache Requirement
103
+ ## Deployment Notes
215
104
 
216
- Ruleset weight decay is calculated iteratively. For long-running projects with many elapsed cycles, the iteration count can exceed the block gas limit. The protocol caps iteration at 20,000 cycles per call and provides `updateRulesetWeightCache()` for progressive caching. Projects with very short durations (e.g., 1-second rulesets) that run for extended periods must periodically call this function to keep weight calculations within gas limits.
105
+ This repo contains both core deployments and periphery deployment helpers. Most other V6 packages assume these contracts exist first and treat them as the stable base layer of the ecosystem.
217
106
 
218
- ### Flash Loan Considerations
107
+ ## Repository Layout
219
108
 
220
- **C-5 (known, documented)**: Calling `cashOut` with `cashOutCount = 0` when `totalSupply == 0` returns the project's entire surplus. This is a known edge case -- it requires a project to have surplus but zero outstanding tokens, which is not a normal operating state. Projects that accumulate surplus without token holders should be aware of this behavior. The protocol's test suite includes 12 flash-loan attack vectors, all of which confirm that no profit is extractable under normal conditions (tokens minted during a payment are worth at most what was paid).
109
+ ```text
110
+ src/
111
+ core contracts, periphery helpers, interfaces, libraries, enums, structs, and abstract bases
112
+ test/
113
+ unit, integration, fork, invariant, audit, formal, and regression coverage
114
+ script/
115
+ Deploy.s.sol
116
+ DeployPeriphery.s.sol
117
+ helpers/
118
+ ```
221
119
 
222
- ## Install
120
+ ## Risks And Notes
223
121
 
224
- ```bash
225
- npm install
226
- ```
122
+ - hooks can meaningfully change payment and cash-out behavior, so core integrations must treat hook composition as part of the protocol surface
123
+ - permissions are flexible enough to be dangerous when scoped broadly or granted with wildcard project IDs
124
+ - multi-terminal and multi-token accounting is powerful but increases the chance of integration mistakes when callers assume a single-terminal model
125
+ - fee, surplus, and reclaim logic are economically sensitive and remain high-priority audit surfaces
227
126
 
228
- ## Develop
229
-
230
- | Command | Description |
231
- |---------|-------------|
232
- | `forge build` | Compile contracts |
233
- | `forge test` | Run local tests |
234
- | `forge test -vvvv` | Run tests with full traces |
235
- | `forge fmt` | Format code |
236
- | `forge fmt --check` | Check formatting (CI lint) |
237
- | `FOUNDRY_PROFILE=CI forge test` | Run fork tests |
238
- | `forge coverage --match-path "./src/*.sol"` | Generate coverage report |
127
+ The fastest way to misunderstand V6 is to treat the core contracts like a simple crowdfunding terminal. They are closer to a configurable accounting and settlement substrate.
package/RISKS.md CHANGED
@@ -1,18 +1,31 @@
1
- # nana-core-v6 -- Active Risk Vectors
1
+ # Juicebox Core Risk Register
2
2
 
3
- Known security properties, trust assumptions, active vulnerability surfaces, and operational risks. Intended audience: experienced Solidity auditors looking for where to focus.
3
+ This file focuses on the accounting, permission, and liveness risks inside the core protocol contracts that everything else in the V6 ecosystem composes with.
4
4
 
5
- ## 1. Trust Assumptions
5
+ ## How to use this file
6
+
7
+ - Read `Priority risks` first; these are the core failures that would propagate far beyond this repo.
8
+ - Use the detailed sections below for protocol accounting, reentrancy, access control, preview, and integration reasoning.
9
+ - Treat `Invariants to Verify` as ecosystem-critical properties, not optional test ideas.
10
+
11
+ ## Priority risks
6
12
 
7
- What must be true for the system to remain safe:
13
+ | Priority | Risk | Why it matters | Primary controls |
14
+ |----------|------|----------------|------------------|
15
+ | P0 | Core accounting corruption | Terminal, store, and controller accounting are the source of truth for balances, surplus, fees, and supply. A bug here propagates everywhere. | Heavy invariant testing, previews aligned with settlement paths, and conservative external integrations. |
16
+ | P0 | Permission or migration mistakes | Controllers, terminals, and operators can redirect authority or value if access control or migration sequencing is wrong. | Strict permission review, migration tests, and scrutiny of wildcard or root-like authority. |
17
+ | P1 | Preview or settlement divergence | Many higher-level hooks and routers depend on previews matching reality closely enough to route safely. | Explicit preview analysis, regression tests, and downstream composition review. |
18
+
19
+ ## 1. Trust Assumptions
8
20
 
9
21
  - **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.
22
+ - **Data hooks are honest.** A data hook has absolute control over payment weight, cash out tax rate, `effectiveTotalSupply`, `effectiveCashOutCount`, and fund-forwarding amounts. A malicious data hook can bypass the bonding curve entirely (e.g., set `effectiveTotalSupply = 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. The terminal still burns the caller-supplied cash-out count; the hook-adjusted values affect pricing only.
11
23
  - **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`.
24
+ - **ERC-20 tokens behave standardly.** `_acceptFundsFor` uses a balance-before/after pattern, which handles fee-on-transfer tokens on ingress. However, outbound fee-on-transfer behavior and rebasing tokens that change balances between transactions will cause `balanceOf` in `JBTerminalStore` to diverge from actual terminal holdings. These accounting risks are limited to projects that opt into those accounting contexts. Missing-return-value tokens are handled by `SafeERC20`.
25
+ - **Reentrant or abusive ERC-20s are out of scope.** Projects choose which accounting contexts this terminal will accept. A token that reenters `pay` or `addToBalanceOf` from `transferFrom`, or otherwise manipulates terminal balance observations mid-transfer, can break the assumptions behind `_acceptFundsFor`'s balance-delta accounting. Core does not harden against intentionally adversarial tokens here; projects that want safe accounting must only accept standard ERC-20s.
13
26
  - **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` and `queueRulesetsOf`. It can also indirectly set terminals through `launchRulesetsFor`, but cannot call `setTerminalsOf` directly. A compromised operator can queue arbitrary rulesets for any project.
27
+ - **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 via `_recordAddedBalanceFor`. This is an intentional fail-open design: the fee is forgiven and a `FeeReverted` event is emitted. For held fees specifically, `processHeldFeesOf` deletes the held-fee entry and advances the index *before* calling `_processFee`, so there is no retry path — once a held fee fails to process, it is permanently forgiven. This prevents a broken fee route from locking project funds indefinitely. The tradeoff is that protocol revenue (project #1) can be lost if the fee terminal is misconfigured. Core deployment alone does not fully initialize fee collection for project `#1`; fee-bearing flows should be treated as fail-open until the fee project's controller, terminals, and accounting contexts are configured.
28
+ - **`OMNICHAIN_RULESET_OPERATOR` is trusted.** This immutable address bypasses owner permission checks for `launchRulesetsFor` and `queueRulesetsOf`. It can also indirectly set terminals through `launchRulesetsFor`, but cannot call `setTerminalsOf` directly. A compromised operator can queue arbitrary rulesets for any project. `DeployPeriphery` intentionally validates only that this address is nonzero, so correctness of the configured operator is an out-of-band deployment responsibility.
16
29
 
17
30
  ## 2. Economic Risks
18
31
 
@@ -20,6 +33,7 @@ What must be true for the system to remain safe:
20
33
 
21
34
  - **Zero cash out guard.** `cashOutFrom` returns 0 when `cashOutCount == 0` (early return). Auditors should verify no code path bypasses this guard or reaches the `cashOutCount >= totalSupply` branch with both values at 0.
22
35
  - **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.
36
+ - **Externally managed token supply affects only that project's cash-out pricing.** If a project opts into `setTokenFor(...)`, `JBTokens.totalSupplyOf()` trusts the attached token's `totalSupply()`. Any minting, burning, rebasing, or other supply changes on that external token affect only that project's supply-sensitive pricing and cash-out math. Projects that want protocol-controlled supply should use `deployERC20For(...)` instead.
23
37
  - **`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
38
  - **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.
25
39
 
@@ -31,11 +45,11 @@ What must be true for the system to remain safe:
31
45
  ### Weight Decay
32
46
 
33
47
  - **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.
34
- - **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.
48
+ - **Weight-cache correctness matters more than overflow.** Rulesets reject weights above `type(uint112).max` at queue time, and weight decay only reduces weight over time. The real risk surface is stale or missing cache progress causing `WeightCacheRequired` reverts or incorrect long-horizon simulations if cache updates are applied to the wrong base ruleset.
35
49
 
36
50
  ### Surplus Manipulation
37
51
 
38
- - **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. When `useTotalSurplusForCashOuts` is false, only the single token being reclaimed contributes to the surplus calculation.
52
+ - **Cross-terminal surplus aggregation.** When `useTotalSurplusForCashOuts` is enabled, `recordCashOutFor` aggregates surplus across all terminals via `JBSurplus.currentSurplusOf()`, which calls `terminal.currentSurplusOf()` on each. This is an explicit trust boundary, not a local-accounting guarantee: projects should only enable it when every listed terminal is mutually trusted to report economically compatible surplus. Defense: `InadequateTerminalStoreBalance` still prevents extracting more than the paying terminal's actual local balance, but partial burns can reclaim more from that terminal than a purely local-surplus model would allow. When `useTotalSurplusForCashOuts` is false, only the single token being reclaimed contributes to the surplus calculation.
39
53
  - **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.
40
54
 
41
55
  ## 3. Reentrancy Surface
@@ -135,8 +149,9 @@ No `ReentrancyGuard` is used. The system relies on state ordering and the `Inade
135
149
 
136
150
  ### Non-Standard ERC-20s
137
151
 
138
- - **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.
139
- - **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.
152
+ - **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 for the projects that choose to use that token.
153
+ - **Reentrant transfer hooks**: `_acceptFundsFor` assumes the token transfer itself does not recursively create another accepted inflow before the outer balance delta is finalized. This is treated as an accepted integration risk rather than a core invariant. Projects should not register ERC-20s with ERC-777-style hooks, reentrant `transferFrom`, or other adversarial transfer behavior as terminal accounting contexts.
154
+ - **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. This risk is limited to projects that opt into those rebasing accounting contexts.
140
155
  - **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.
141
156
  - **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.
142
157