@bananapus/core-v6 0.0.35 → 0.0.36
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 +29 -29
- package/ARCHITECTURE.md +41 -34
- package/AUDIT_INSTRUCTIONS.md +44 -42
- package/README.md +37 -37
- package/RISKS.md +115 -154
- package/SKILLS.md +17 -18
- package/USER_JOURNEYS.md +49 -51
- package/package.json +1 -1
- package/test/TestAccessToFunds.sol +24 -0
package/RISKS.md
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
1
|
# Juicebox Core Risk Register
|
|
2
2
|
|
|
3
|
-
This file
|
|
3
|
+
This file covers the main accounting, permission, and liveness risks in the core protocol contracts that the rest of V6 builds on.
|
|
4
4
|
|
|
5
|
-
## How
|
|
5
|
+
## How To Use This File
|
|
6
6
|
|
|
7
|
-
- Read `Priority risks` first
|
|
8
|
-
- Use the
|
|
9
|
-
- Treat `Invariants to
|
|
7
|
+
- Read `Priority risks` first. Those are the failures with the widest blast radius.
|
|
8
|
+
- Use the later sections when you need detail on accounting, reentrancy, access control, previews, or integrations.
|
|
9
|
+
- Treat `Invariants to verify` as core properties, not optional test ideas.
|
|
10
10
|
|
|
11
|
-
## Priority
|
|
11
|
+
## Priority Risks
|
|
12
12
|
|
|
13
13
|
| Priority | Risk | Why it matters | Primary controls |
|
|
14
14
|
|----------|------|----------------|------------------|
|
|
15
|
-
| P0 | Core accounting corruption | Terminal, store, and controller accounting
|
|
16
|
-
| P0 | Permission or migration mistakes | Controllers, terminals, and operators can redirect authority or value if
|
|
17
|
-
| P1 | Preview or settlement
|
|
15
|
+
| P0 | Core accounting corruption | Terminal, store, and controller accounting define balances, surplus, fees, and supply for the whole ecosystem. | Invariant tests, preview/settlement alignment, and conservative integrations. |
|
|
16
|
+
| P0 | Permission or migration mistakes | Controllers, terminals, and operators can redirect authority or value if checks or sequencing are wrong. | Permission review, migration tests, and scrutiny of wildcard or root-like authority. |
|
|
17
|
+
| P1 | Preview or settlement drift | Hooks and routers often depend on previews being close to execution. | Preview analysis, regression tests, and downstream composition review. |
|
|
18
18
|
|
|
19
19
|
## 1. Trust Assumptions
|
|
20
20
|
|
|
21
|
-
- **Hooks
|
|
22
|
-
- **Data hooks are
|
|
23
|
-
- **Price feeds
|
|
24
|
-
- **ERC-
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **Project
|
|
28
|
-
- **`OMNICHAIN_RULESET_OPERATOR` is trusted.** This
|
|
21
|
+
- **Hooks are not exploiting reentrancy.** Core does not use `ReentrancyGuard`. Safety depends on call ordering and the `JBTerminalStore_InadequateTerminalStoreBalance` backstop.
|
|
22
|
+
- **Data hooks are highly trusted.** A data hook can change payment weight, cash-out tax rate, `effectiveTotalSupply`, `effectiveCashOutCount`, and hook-forwarding amounts. The protocol only bounds the final amounts.
|
|
23
|
+
- **Price feeds are honest enough.** Surplus, payout conversions, and allowance math depend on `JBPrices`. Stale or manipulated feeds misprice the system.
|
|
24
|
+
- **Accepted ERC-20s behave like standard tokens.** Inbound fee-on-transfer handling is safer than outbound handling. Rebasing or nonstandard outbound behavior can still break accounting assumptions.
|
|
25
|
+
- **Accepted tokens are not actively adversarial.** Core does not harden against tokens that reenter or distort balance observations during transfer.
|
|
26
|
+
- **The trusted forwarder is not compromised.** If it is, `_msgSender()` can be spoofed across permission-gated contracts.
|
|
27
|
+
- **Project `#1` fee routing stays live enough.** If fee processing into project `#1` fails, core favors liveness and returns value to the originating project instead of trapping it. That can forgive fees.
|
|
28
|
+
- **`OMNICHAIN_RULESET_OPERATOR` is trusted.** This address can bypass some owner checks for ruleset flows and is a broad trust point.
|
|
29
29
|
|
|
30
30
|
## 2. Economic Risks
|
|
31
31
|
|
|
32
32
|
### Bonding Curve
|
|
33
33
|
|
|
34
|
-
- **Zero cash
|
|
35
|
-
- **Pending reserved tokens
|
|
36
|
-
- **
|
|
37
|
-
- **`mulDiv` rounding.**
|
|
38
|
-
-
|
|
34
|
+
- **Zero cash-out guard.** `cashOutFrom` returns `0` when `cashOutCount == 0`. Verify no path bypasses that guard.
|
|
35
|
+
- **Pending reserved tokens lower cash-out value.** `totalTokenSupplyWithReservedTokensOf()` includes `pendingReservedTokenBalanceOf`, which can reduce per-token reclaim value until reserves are distributed.
|
|
36
|
+
- **External token supply only affects that project.** If a project uses `setTokenFor(...)`, the external token's `totalSupply()` feeds that project's cash-out math.
|
|
37
|
+
- **`mulDiv` rounding exists.** Split cash outs can differ slightly from a combined cash out because of floor rounding.
|
|
38
|
+
- **`minCashOutCountFor` uses binary search.** Large supplies increase loop count. Gas should stay bounded.
|
|
39
39
|
|
|
40
40
|
### Fee Arithmetic
|
|
41
41
|
|
|
42
|
-
- **Forward
|
|
43
|
-
- **Held fee
|
|
42
|
+
- **Forward and backward fee math round differently.** `feeAmountFrom` and `feeAmountResultingIn` are close but not identical under rounding. Their interaction matters in held-fee paths.
|
|
43
|
+
- **Held fee entries are mutated in place.** If the accounting is off by even one unit in the wrong direction, `_returnHeldFees` can corrupt the entry.
|
|
44
44
|
|
|
45
45
|
### Weight Decay
|
|
46
46
|
|
|
47
|
-
- **
|
|
48
|
-
- **Weight-cache correctness matters more than overflow.**
|
|
47
|
+
- **Stale weight cache can block a project.** Short-duration rulesets with nonzero `weightCutPercent` can hit `WeightCacheRequired` after enough cycles.
|
|
48
|
+
- **Weight-cache correctness matters more than overflow.** Overflow is already bounded at queue time. The real risk is stale or wrongly-updated cache state.
|
|
49
49
|
|
|
50
50
|
### Surplus Manipulation
|
|
51
51
|
|
|
52
|
-
- **Cross-terminal surplus
|
|
53
|
-
- **
|
|
52
|
+
- **Cross-terminal surplus is a trust boundary.** When `useTotalSurplusForCashOuts` is enabled, one terminal can price a cash out using value reported by other terminals.
|
|
53
|
+
- **Cross-terminal price-feed mismatch changes reclaim values.** If feeds differ or go stale across terminals, aggregated surplus can be wrong.
|
|
54
54
|
|
|
55
55
|
## 3. Reentrancy Surface
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
Core does not use `ReentrancyGuard`. It relies on state ordering plus `InadequateTerminalStoreBalance` as the last balance-extraction backstop.
|
|
58
58
|
|
|
59
59
|
### External Call Map
|
|
60
60
|
|
|
61
61
|
| Function | State Changes Before External Call | External Calls | Risk |
|
|
62
62
|
|----------|-----------------------------------|----------------|------|
|
|
63
|
-
| `_pay` | `STORE.recordPaymentFrom
|
|
64
|
-
| `_cashOutTokensOf` | `STORE.recordCashOutFor
|
|
65
|
-
| `executePayout` | `STORE.recordPayoutFor` already consumed payout limit |
|
|
66
|
-
| `processHeldFeesOf` |
|
|
67
|
-
| `_sendReservedTokensToSplitsOf` |
|
|
68
|
-
| `_useAllowanceOf` | `STORE.recordUsedAllowanceOf`
|
|
69
|
-
| `migrateBalanceOf` | `STORE.recordTerminalMigration`
|
|
70
|
-
|
|
71
|
-
### Cross-Function Reentrancy
|
|
72
|
-
|
|
73
|
-
- **Pay hook -> `cashOutTokensOf
|
|
74
|
-
- **Cash
|
|
75
|
-
- **Split hook -> `pay` on
|
|
76
|
-
- **Reserved
|
|
77
|
-
- **Fee processing
|
|
63
|
+
| `_pay` | `STORE.recordPaymentFrom`, `controller.mintTokensOf` | Pay hooks | LOW |
|
|
64
|
+
| `_cashOutTokensOf` | `STORE.recordCashOutFor`, `controller.burnTokensOf`, beneficiary transfer | Cash-out hooks, then fee processing | MEDIUM |
|
|
65
|
+
| `executePayout` | `STORE.recordPayoutFor` already consumed payout limit | Split hooks, terminal pay/addToBalance | MEDIUM |
|
|
66
|
+
| `processHeldFeesOf` | Held-fee entry deleted and index advanced | `_processFee` -> `this.executeProcessFee` -> `terminal.pay` | LOW |
|
|
67
|
+
| `_sendReservedTokensToSplitsOf` | Pending reserved balance zeroed, tokens minted | Split hooks, terminal payments | LOW |
|
|
68
|
+
| `_useAllowanceOf` | `STORE.recordUsedAllowanceOf` | Fee processing, beneficiary transfer | LOW |
|
|
69
|
+
| `migrateBalanceOf` | `STORE.recordTerminalMigration` | `to.addToBalanceOf` | LOW |
|
|
70
|
+
|
|
71
|
+
### Cross-Function Reentrancy To Explore
|
|
72
|
+
|
|
73
|
+
- **Pay hook -> `cashOutTokensOf`.** The hook sees post-payment balance and post-mint supply.
|
|
74
|
+
- **Cash-out hook -> `pay`.** The hook runs after burn and payout but before fee processing completes.
|
|
75
|
+
- **Split hook -> `pay` on the same project.** Core now reverts same-project intra-terminal self-pay minting, but the path is still worth checking.
|
|
76
|
+
- **Reserved-token split hook reentry.** Hooks see post-mint state after pending reserved balance is zeroed.
|
|
77
|
+
- **Fee processing reentry.** `_processFee` makes an external fee payment into project `#1`; hook behavior there still matters.
|
|
78
78
|
|
|
79
79
|
### Key Backstop
|
|
80
80
|
|
|
81
|
-
`JBTerminalStore_InadequateTerminalStoreBalance`
|
|
81
|
+
`JBTerminalStore_InadequateTerminalStoreBalance` should stop any path from pulling more than the terminal's recorded balance. Auditors should verify no caller can inflate that recorded balance without the terminal actually holding the funds.
|
|
82
82
|
|
|
83
83
|
## 4. Access Control
|
|
84
84
|
|
|
85
85
|
### Permission System
|
|
86
86
|
|
|
87
|
-
- **ROOT
|
|
88
|
-
- **ROOT
|
|
89
|
-
- **Empty permission arrays pass `hasPermissions`.**
|
|
90
|
-
- **`OMNICHAIN_RULESET_OPERATOR` bypass.**
|
|
87
|
+
- **ROOT grants all permissions.** That includes permissions added in the future.
|
|
88
|
+
- **ROOT plus wildcard is allowed only for self-grants.** An account can delegate broad power over its own projects, but third parties should not be able to escalate into it.
|
|
89
|
+
- **Empty permission arrays pass `hasPermissions`.** Callers must check for non-empty arrays if that matters to their logic.
|
|
90
|
+
- **`OMNICHAIN_RULESET_OPERATOR` is a broad bypass.** It can queue or launch rulesets for any project.
|
|
91
91
|
|
|
92
92
|
### Directory Terminal Addition
|
|
93
93
|
|
|
94
|
-
- **`setPrimaryTerminalOf`
|
|
94
|
+
- **`setPrimaryTerminalOf` can also add a terminal.** When the terminal is not already installed, the call must satisfy `ADD_TERMINALS` as well as the primary-terminal permission.
|
|
95
95
|
|
|
96
96
|
### Migration
|
|
97
97
|
|
|
98
|
-
- **Controller migration
|
|
99
|
-
- **Terminal migration
|
|
100
|
-
- **Directory updates
|
|
98
|
+
- **Controller migration depends on ruleset permission.** `allowSetController` must be active, and migration fails if reserved tokens are still pending.
|
|
99
|
+
- **Terminal migration also depends on ruleset permission.** Held fees are not migrated, and migration into a non-feeless terminal charges the normal protocol fee.
|
|
100
|
+
- **Directory updates are high-impact.** `setTerminalsOf` and `setControllerOf` can redirect a project's fund and authority flow.
|
|
101
101
|
|
|
102
102
|
### Ruleset Queuing
|
|
103
103
|
|
|
104
|
-
- Only the
|
|
105
|
-
- The controller
|
|
106
|
-
- For `duration = 0` projects, a queued ruleset
|
|
104
|
+
- Only the current controller can call `RULESETS.queueFor()`.
|
|
105
|
+
- The controller lets the owner, an allowed operator, or `OMNICHAIN_RULESET_OPERATOR` queue rulesets.
|
|
106
|
+
- For `duration = 0` projects, a queued ruleset can take effect immediately.
|
|
107
107
|
|
|
108
108
|
## 5. DoS Vectors
|
|
109
109
|
|
|
@@ -111,144 +111,105 @@ No `ReentrancyGuard` is used. The system relies on state ordering and the `Inade
|
|
|
111
111
|
|
|
112
112
|
| Array | Growth Mechanism | Cleanup | Risk |
|
|
113
113
|
|-------|-----------------|---------|------|
|
|
114
|
-
| `_heldFeesOf[projectId][token]` | Each held-fee payout appends |
|
|
115
|
-
| `splits[]` | Set by project owner per ruleset | Replaced wholesale | MODERATE
|
|
116
|
-
| `_accountingContextsOf[projectId]` | `addAccountingContextsFor`
|
|
117
|
-
| Payout limits / surplus allowances | Set per ruleset | Replaced per ruleset | LOW
|
|
118
|
-
| `_terminalsOf[projectId]` | `setTerminalsOf`
|
|
114
|
+
| `_heldFeesOf[projectId][token]` | Each held-fee payout appends | Index pointer skips processed entries | MODERATE |
|
|
115
|
+
| `splits[]` | Set by project owner per ruleset | Replaced wholesale | MODERATE |
|
|
116
|
+
| `_accountingContextsOf[projectId]` | `addAccountingContextsFor` append-only | Never shrinks | LOW |
|
|
117
|
+
| Payout limits / surplus allowances | Set per ruleset | Replaced per ruleset | LOW |
|
|
118
|
+
| `_terminalsOf[projectId]` | `setTerminalsOf` replace-only | Replaced | LOW |
|
|
119
119
|
|
|
120
120
|
### Price Feed Reverts
|
|
121
121
|
|
|
122
|
-
-
|
|
123
|
-
- L2 sequencer downtime
|
|
124
|
-
- Single-currency projects
|
|
125
|
-
- Price feeds are immutable once set in `JBPrices
|
|
122
|
+
- Stale or incomplete Chainlink data can block multi-currency operations.
|
|
123
|
+
- L2 sequencer downtime can also block feeds behind a sequencer-check wrapper.
|
|
124
|
+
- Single-currency projects are unaffected when they do not need conversion.
|
|
125
|
+
- Price feeds are immutable once set in `JBPrices`.
|
|
126
126
|
|
|
127
127
|
### Approval Hook Griefing
|
|
128
128
|
|
|
129
|
-
- A reverting approval hook is caught
|
|
130
|
-
- A gas-
|
|
131
|
-
-
|
|
129
|
+
- A reverting approval hook is caught and treated as failed approval.
|
|
130
|
+
- A gas-burning approval hook can still DoS `currentOf()` by exhausting gas.
|
|
131
|
+
- Repeated approval-hook rejection at a ruleset boundary can create complex fallback behavior that needs testing.
|
|
132
132
|
|
|
133
133
|
### Other DoS Surfaces
|
|
134
134
|
|
|
135
|
-
-
|
|
136
|
-
- `addAccountingContextsFor` is
|
|
135
|
+
- Failed split payouts consume payout limit even when value is returned to project balance.
|
|
136
|
+
- `addAccountingContextsFor` is append-only, so projects that add many contexts over time can make some loops more expensive.
|
|
137
137
|
|
|
138
138
|
## 6. Preview Functions
|
|
139
139
|
|
|
140
|
-
`JBMultiTerminal.previewPayFor`, `JBMultiTerminal.previewCashOutFrom`, and `JBController.previewMintOf` are
|
|
140
|
+
`JBMultiTerminal.previewPayFor`, `JBMultiTerminal.previewCashOutFrom`, and `JBController.previewMintOf` are read-only simulations of state-changing operations.
|
|
141
141
|
|
|
142
|
-
- **
|
|
143
|
-
- **Store previews
|
|
144
|
-
- **
|
|
145
|
-
- **Preview
|
|
146
|
-
- **
|
|
147
|
-
- **Not all read-only cash-out estimates are hook-aware.** `JBTerminalStore.currentReclaimableSurplusOf` and `currentTotalReclaimableSurplusOf` intentionally do not run the data hook. They use the current ruleset's plain `cashOutTaxRate` and the controller-reported total supply, so they cannot reflect hook-provided overrides such as omnichain or custom `effectiveTotalSupply` logic. Integrators should use `previewCashOutFrom` or simulate `recordCashOutFor` when they need an estimate that matches hook-adjusted execution more closely.
|
|
142
|
+
- **Previews call data hooks.** A reverting or gas-heavy hook can break previews.
|
|
143
|
+
- **Store previews require the correct terminal input.** Passing the wrong terminal gives the wrong answer.
|
|
144
|
+
- **Previews do not mutate state.** They cannot consume limits, move funds, or mint and burn tokens.
|
|
145
|
+
- **Preview and execution can still drift.** Shared logic helps, but state can change between calls and hooks can be stateful.
|
|
146
|
+
- **Some read-only surplus views are not hook-aware.** `currentReclaimableSurplusOf` and `currentTotalReclaimableSurplusOf` intentionally skip data hooks.
|
|
148
147
|
|
|
149
148
|
## 7. Integration Risks
|
|
150
149
|
|
|
151
150
|
### Non-Standard ERC-20s
|
|
152
151
|
|
|
153
|
-
- **Fee-on-transfer tokens
|
|
154
|
-
- **Reentrant transfer hooks
|
|
155
|
-
- **Rebasing tokens
|
|
156
|
-
- **
|
|
157
|
-
- **Low-decimal tokens
|
|
152
|
+
- **Fee-on-transfer tokens.** Inbound handling is safer than outbound handling. Outbound transfer fees can leave store accounting higher than real holdings.
|
|
153
|
+
- **Reentrant transfer hooks.** Core treats them as an accepted integration risk, not a hardened invariant.
|
|
154
|
+
- **Rebasing tokens.** Positive or negative rebases can desync terminal balances from store balances.
|
|
155
|
+
- **Blocklist tokens.** Beneficiary-specific transfer failures can revert user cash outs or return payout value to the project.
|
|
156
|
+
- **Low-decimal tokens.** Fixed-point conversions can lose meaningful precision.
|
|
158
157
|
|
|
159
158
|
### Permit2 Interactions
|
|
160
159
|
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
- The `uint160` cast in `
|
|
160
|
+
- Permit2 is only used for inbound transfers.
|
|
161
|
+
- Outbound transfers never rely on Permit2.
|
|
162
|
+
- The `uint160` cast in `_acceptFundsFor` caps Permit2 transfer size.
|
|
164
163
|
|
|
165
164
|
### Cross-Terminal Surplus Aggregation
|
|
166
165
|
|
|
167
|
-
- `JBSurplus.currentSurplusOf`
|
|
168
|
-
-
|
|
166
|
+
- `JBSurplus.currentSurplusOf` makes external view calls into each terminal with no gas cap.
|
|
167
|
+
- Aggregated surplus also compounds price-conversion rounding across terminals.
|
|
169
168
|
|
|
170
|
-
### `addToBalanceOf`
|
|
169
|
+
### `addToBalanceOf` Metadata
|
|
171
170
|
|
|
172
|
-
- `addToBalanceOf` accepts arbitrary
|
|
171
|
+
- `addToBalanceOf` accepts arbitrary metadata.
|
|
172
|
+
- Core ignores that metadata directly, but hooks may interpret it.
|
|
173
173
|
|
|
174
174
|
### `recordAddedBalanceFor` Access Control
|
|
175
175
|
|
|
176
|
-
- `JBTerminalStore.recordAddedBalanceFor` has
|
|
176
|
+
- `JBTerminalStore.recordAddedBalanceFor` has no explicit access control.
|
|
177
|
+
- The balance key includes `msg.sender`, so only a terminal can inflate its own recorded balance.
|
|
178
|
+
- A buggy or malicious terminal can still lie about funds it received.
|
|
177
179
|
|
|
178
|
-
### Split
|
|
180
|
+
### Split And Owner-Payout Failure Semantics
|
|
179
181
|
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
182
|
+
- Failed split payouts still consume payout limit.
|
|
183
|
+
- Failed owner payouts also still consume payout limit.
|
|
184
|
+
- Reserved-token split hook reverts can strand tokens at the hook after transfer.
|
|
183
185
|
|
|
184
186
|
## 8. Accepted Behaviors
|
|
185
187
|
|
|
186
|
-
### 8.1 Cross-terminal surplus is
|
|
188
|
+
### 8.1 Cross-terminal surplus is opt-in shared trust
|
|
187
189
|
|
|
188
|
-
When a project enables `useTotalSurplusForCashOuts`,
|
|
189
|
-
balance as the full economic truth and instead trusts every registered terminal's reported surplus. This means a
|
|
190
|
-
project can get richer cash-out pricing from value held elsewhere, but it also means a bad or economically
|
|
191
|
-
incompatible terminal can distort the aggregate. This is accepted because cross-terminal projects explicitly opt into
|
|
192
|
-
shared treasury semantics; the alternative is forcing every terminal to behave as an isolated silo. Projects should
|
|
193
|
-
only enable this mode when all participating terminals are mutually trusted and economically compatible.
|
|
190
|
+
When a project enables `useTotalSurplusForCashOuts`, it is choosing shared treasury semantics across terminals. That can improve pricing, but it also means each listed terminal is part of the trust boundary.
|
|
194
191
|
|
|
195
|
-
### 8.2
|
|
192
|
+
### 8.2 Failed fee routing is intentionally fail-open
|
|
196
193
|
|
|
197
|
-
|
|
198
|
-
`_processFee` returns the fee amount to the originating project's balance instead of locking funds. For held fees,
|
|
199
|
-
`processHeldFeesOf` advances the queue before retrying the payment, so a failed held-fee processing attempt
|
|
200
|
-
permanently forgives that fee. This is accepted because a broken fee route should not brick project treasury flows.
|
|
201
|
-
The tradeoff is explicit revenue leakage for the fee beneficiary when the fee route is unavailable or incompletely
|
|
202
|
-
wired.
|
|
194
|
+
If project `#1` cannot accept a fee payment, core prefers liveness over strict fee collection. For held fees, a failed processing attempt can forgive the fee permanently.
|
|
203
195
|
|
|
204
|
-
### 8.3 Surplus allowance is ruleset
|
|
196
|
+
### 8.3 Surplus allowance is keyed by ruleset, not by an abstract cycle
|
|
205
197
|
|
|
206
|
-
`usedSurplusAllowanceOf` is keyed by
|
|
207
|
-
incrementing "cycle" counter. For projects whose rulesets roll forward implicitly without a new ruleset ID, allowance
|
|
208
|
-
usage carries forward until a new ruleset actually takes effect. This is accepted because surplus allowance is meant
|
|
209
|
-
to be tied to the active ruleset's economics, not to a synthetic cycle abstraction layered on top of an unchanged
|
|
210
|
-
ruleset. Integrators that expect per-cycle resets should queue distinct rulesets instead of relying on implicit
|
|
211
|
-
rollover.
|
|
198
|
+
`usedSurplusAllowanceOf` is keyed by `ruleset.id`. If a ruleset auto-rolls without a new ID, allowance usage carries forward.
|
|
212
199
|
|
|
213
|
-
### 8.4
|
|
200
|
+
### 8.4 Fee routing starts fail-open until the wider deployment is wired
|
|
214
201
|
|
|
215
|
-
Core
|
|
216
|
-
controller, terminals, and accounting contexts are wired, fee-bearing flows remain fail-open: fees are forgiven back
|
|
217
|
-
to the originating project rather than trapped. This is accepted because deployment sequencing across repos is staged,
|
|
218
|
-
and the protocol prioritizes keeping project flows live during rollout over enforcing fee collection before the fee
|
|
219
|
-
beneficiary is ready.
|
|
202
|
+
Core can be deployed before project `#1` is fully ready. During that period, fee-bearing flows may forgive fees instead of trapping funds.
|
|
220
203
|
|
|
221
|
-
## 9. Invariants
|
|
204
|
+
## 9. Invariants To Verify
|
|
222
205
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
### Token Supply Consistency
|
|
235
|
-
- `TOKENS.totalSupplyOf(projectId) == creditSupply + erc20.totalSupply()` at all times.
|
|
236
|
-
- `totalTokenSupplyWithReservedTokensOf(projectId) == TOKENS.totalSupplyOf(projectId) + pendingReservedTokenBalanceOf[projectId]`.
|
|
237
|
-
|
|
238
|
-
### Payout Limit Enforcement
|
|
239
|
-
- `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.
|
|
240
|
-
|
|
241
|
-
### Surplus Allowance Enforcement
|
|
242
|
-
- `usedSurplusAllowanceOf[terminal][projectId][token][rulesetId][currency] <= surplusAllowanceOf(...)` after every `recordUsedAllowanceOf`.
|
|
243
|
-
|
|
244
|
-
### Cash Out Bound
|
|
245
|
-
- `reclaimAmount + sum(hookSpecification.amounts) <= balanceOf[terminal][projectId][token]` after every `recordCashOutFor`. This is the `InadequateTerminalStoreBalance` check. Verify it is never circumvented.
|
|
246
|
-
|
|
247
|
-
### Ruleset Existence
|
|
248
|
-
- 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.
|
|
249
|
-
|
|
250
|
-
### No Flash-Loan Profit
|
|
251
|
-
- `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.
|
|
252
|
-
|
|
253
|
-
### Held Fee Integrity
|
|
254
|
-
- `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.
|
|
206
|
+
- **Balance conservation:** `terminal.balance(token) >= sum(store.balanceOf(projectId, terminal, token))` for projects sharing a terminal.
|
|
207
|
+
- **Fund conservation:** project inflows should cover project outflows plus fees, with rounding favoring the protocol.
|
|
208
|
+
- **Fee monotonicity:** project `#1` should only gain protocol fees through normal mechanics.
|
|
209
|
+
- **Token supply consistency:** protocol credit supply, ERC-20 supply, and pending reserved supply should reconcile.
|
|
210
|
+
- **Payout-limit enforcement:** `usedPayoutLimitOf(...)` must stay `<= payoutLimitOf(...)`.
|
|
211
|
+
- **Surplus-allowance enforcement:** `usedSurplusAllowanceOf(...)` must stay `<= surplusAllowanceOf(...)`.
|
|
212
|
+
- **Cash-out bound:** reclaim plus hook-forwarded amounts must not exceed recorded balance.
|
|
213
|
+
- **Ruleset existence:** after launch, `RULESETS.currentOf(projectId)` should not accidentally go empty.
|
|
214
|
+
- **No flash-loan profit:** `pay()` followed by `cashOutTokensOf()` in one transaction should not be profitable after fees.
|
|
215
|
+
- **Held-fee integrity:** active held-fee entries plus processed fees should equal all fees ever taken under held-fee mode.
|
package/SKILLS.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Use This File For
|
|
4
4
|
|
|
5
|
-
- Use this file when the task touches protocol
|
|
6
|
-
- Start here
|
|
5
|
+
- Use this file when the task touches core protocol behavior: payments, cash outs, terminals, controller actions, rulesets, splits, tokens, permissions, or price feeds.
|
|
6
|
+
- Start here when you know the issue is in core. Then narrow it to one state transition before reading more broadly.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
| Rulesets, permissions, directory, and prices | [`src/JBRulesets.sol`](./src/JBRulesets.sol), [`src/JBPermissions.sol`](./src/JBPermissions.sol), [`src/JBDirectory.sol`](./src/JBDirectory.sol), [`src/JBPrices.sol`](./src/JBPrices.sol) |
|
|
16
16
|
| Shared math, metadata parsing, and constants | [`src/libraries/`](./src/libraries/), [`src/structs/`](./src/structs/), [`src/enums/`](./src/enums/) |
|
|
17
17
|
| Periphery helpers and deployment | [`src/periphery/`](./src/periphery/), [`script/Deploy.s.sol`](./script/Deploy.s.sol), [`script/DeployPeriphery.s.sol`](./script/DeployPeriphery.s.sol) |
|
|
18
|
-
| Payment and cash-out
|
|
19
|
-
| Packed metadata,
|
|
20
|
-
|
|
|
18
|
+
| Payment and cash-out entrypoints | [`references/entrypoints.md`](./references/entrypoints.md) |
|
|
19
|
+
| Packed metadata, errors, events, and hook return shapes | [`references/types-errors-events.md`](./references/types-errors-events.md) |
|
|
20
|
+
| Payment and cash-out behavior in tests | [`test/TestPayBurnRedeemFlow.sol`](./test/TestPayBurnRedeemFlow.sol), [`test/TestCashOut.sol`](./test/TestCashOut.sol), [`test/TestMultiTerminalSurplus.sol`](./test/TestMultiTerminalSurplus.sol), [`test/TestTerminalPreviewParity.sol`](./test/TestTerminalPreviewParity.sol) |
|
|
21
21
|
| Permissions, rulesets, and invariants | [`test/TestPermissions.sol`](./test/TestPermissions.sol), [`test/PermissionEscalation.t.sol`](./test/PermissionEscalation.t.sol), [`test/TestRulesetQueueing.sol`](./test/TestRulesetQueueing.sol), [`test/ComprehensiveInvariant.t.sol`](./test/ComprehensiveInvariant.t.sol), [`test/PermissionsInvariant.t.sol`](./test/PermissionsInvariant.t.sol) |
|
|
22
|
-
| Economic
|
|
22
|
+
| Economic and exploit coverage | [`test/EconomicSimulation.t.sol`](./test/EconomicSimulation.t.sol), [`test/CoreExploitTests.t.sol`](./test/CoreExploitTests.t.sol), [`test/FlashLoanAttacks.t.sol`](./test/FlashLoanAttacks.t.sol), [`test/WeirdTokenTests.t.sol`](./test/WeirdTokenTests.t.sol), [`test/AuditFixes.t.sol`](./test/AuditFixes.t.sol) |
|
|
23
23
|
|
|
24
24
|
## Repo Map
|
|
25
25
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
## Purpose
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
This is the core Juicebox V6 protocol on EVM. It lets projects launch treasury-backed tokens with configurable rulesets for payments, payouts, cash outs, and token issuance.
|
|
36
36
|
|
|
37
37
|
## Reference Files
|
|
38
38
|
|
|
@@ -43,14 +43,13 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
43
43
|
|
|
44
44
|
## Working Rules
|
|
45
45
|
|
|
46
|
-
- Open source before relying on any summary here.
|
|
47
|
-
- For runtime bugs, start from the terminal
|
|
48
|
-
- `JBMultiTerminal` and `JBTerminalStore`
|
|
49
|
-
- Payment and cash-out previews are
|
|
50
|
-
- Payout limits reset by ruleset cycle number
|
|
51
|
-
- Fee handling is
|
|
52
|
-
- Fee-free surplus is a bounded anti-bypass mechanism, not a
|
|
53
|
-
- For config or
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- When a bug smells cross-repo, prove it is not really coming from a hook, router, or downstream deployer before patching core.
|
|
46
|
+
- Open the source before relying on any summary here.
|
|
47
|
+
- For runtime bugs, start from the terminal, controller, or store contract that owns the state transition.
|
|
48
|
+
- `JBMultiTerminal` and `JBTerminalStore` should usually be read together.
|
|
49
|
+
- Payment and cash-out previews are part of the protocol surface. Keep them aligned with execution.
|
|
50
|
+
- Payout limits reset by ruleset cycle number. Surplus allowances are keyed by `ruleset.id`. They do not always reset together.
|
|
51
|
+
- Fee handling is subtle. Re-check held fees, fee-free surplus tracking, and feeless-address behavior before changing payout or cash-out logic.
|
|
52
|
+
- Fee-free surplus is a bounded anti-bypass mechanism, not a general exemption bucket.
|
|
53
|
+
- For config or metadata-shape issues, open `references/types-errors-events.md` before changing structs or packed metadata.
|
|
54
|
+
- If previews, accounting, or fee behavior change, verify the other two as well.
|
|
55
|
+
- If a bug looks cross-repo, prove it is not caused by a hook, router, or deployer before patching core.
|