@bananapus/core-v6 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ADMINISTRATION.md +43 -13
- package/ARCHITECTURE.md +62 -137
- package/AUDIT_INSTRUCTIONS.md +149 -428
- package/CHANGELOG.md +73 -0
- package/README.md +90 -201
- package/RISKS.md +27 -12
- package/SKILLS.md +31 -441
- package/STYLE_GUIDE.md +52 -19
- package/USER_JOURNEYS.md +76 -627
- package/package.json +1 -2
- package/references/entrypoints.md +160 -0
- package/references/types-errors-events.md +297 -0
- package/script/Deploy.s.sol +7 -2
- package/script/DeployPeriphery.s.sol +51 -4
- package/src/JBController.sol +45 -17
- package/src/JBDirectory.sol +26 -13
- package/src/JBFundAccessLimits.sol +28 -7
- package/src/JBMultiTerminal.sol +180 -86
- package/src/JBPermissions.sol +17 -17
- package/src/JBRulesets.sol +82 -23
- package/src/JBSplits.sol +31 -12
- package/src/JBTerminalStore.sol +137 -53
- package/src/JBTokens.sol +5 -2
- package/src/abstract/JBControlled.sol +10 -3
- package/src/abstract/JBPermissioned.sol +1 -1
- package/src/interfaces/IJBRulesetDataHook.sol +5 -4
- package/src/libraries/JBCashOuts.sol +1 -1
- package/src/libraries/JBConstants.sol +1 -1
- package/src/libraries/JBCurrencyIds.sol +1 -1
- package/src/libraries/JBFees.sol +1 -1
- package/src/libraries/JBFixedPointNumber.sol +1 -1
- package/src/libraries/JBMetadataResolver.sol +5 -2
- package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
- package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
- package/src/libraries/JBSplitGroupIds.sol +1 -1
- package/src/libraries/JBSurplus.sol +5 -2
- package/src/structs/JBSplit.sol +4 -1
- package/test/TestForwardedTokenConsumption.sol +419 -0
- package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
- package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
- package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
- package/CHANGE_LOG.md +0 -479
package/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
|
-
|
|
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
|
-
|
|
5
|
+
Docs: <https://docs.juicebox.money>
|
|
6
|
+
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Overview
|
|
8
9
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
If you only read one repo before auditing the rest of the ecosystem, read this one.
|
|
19
24
|
|
|
20
|
-
|
|
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
|
-
|
|
27
|
+
The core protocol is easiest to reason about in four layers:
|
|
27
28
|
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
The shortest path through the repo is:
|
|
34
37
|
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
+
## Key Contracts
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
64
|
+
## Integration Traps
|
|
51
65
|
|
|
52
|
-
|
|
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
|
-
|
|
71
|
+
## Where State Lives
|
|
55
72
|
|
|
56
|
-
|
|
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
|
-
|
|
79
|
+
When in doubt, read the state-owning contract before the contract that merely forwards into it.
|
|
59
80
|
|
|
60
|
-
|
|
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
|
-
```
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
95
|
+
Useful scripts:
|
|
211
96
|
|
|
212
|
-
|
|
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
|
-
|
|
103
|
+
## Deployment Notes
|
|
215
104
|
|
|
216
|
-
|
|
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
|
-
|
|
107
|
+
## Repository Layout
|
|
219
108
|
|
|
220
|
-
|
|
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
|
-
##
|
|
120
|
+
## Risks And Notes
|
|
223
121
|
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
1
|
+
# Juicebox Core Risk Register
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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, `
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
- **
|
|
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
|
|