@bananapus/core-v6 0.0.18 → 0.0.20
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 +3 -0
- package/ARCHITECTURE.md +24 -0
- package/AUDIT_INSTRUCTIONS.md +4 -2
- package/CHANGE_LOG.md +29 -1
- package/README.md +12 -2
- package/RISKS.md +10 -2
- package/SKILLS.md +9 -0
- package/USER_JOURNEYS.md +6 -0
- package/foundry.toml +1 -0
- package/package.json +1 -1
- package/src/JBController.sol +52 -5
- package/src/JBMultiTerminal.sol +197 -179
- package/src/JBTerminalStore.sol +367 -171
- package/src/interfaces/IJBCashOutTerminal.sol +30 -0
- package/src/interfaces/IJBController.sol +15 -0
- package/src/interfaces/IJBTerminal.sol +28 -0
- package/src/interfaces/IJBTerminalStore.sol +66 -0
- package/src/libraries/JBPayoutSplitGroupLib.sol +157 -0
- package/src/structs/JBCashOutHookSpecification.sol +2 -0
- package/src/structs/JBPayHookSpecification.sol +2 -0
- package/test/CoreExploitTests.t.sol +21 -10
- package/test/TestCashOutHooks.sol +6 -4
- package/test/TestDataHookFuzzing.sol +6 -2
- package/test/TestPayHooks.sol +1 -1
- package/test/TestRulesetQueueing.sol +4 -5
- package/test/TestRulesetQueuingStress.sol +5 -3
- package/test/TestTerminalPreviewParity.sol +208 -0
- package/test/fork/TestSequencerPriceFeedFork.sol +168 -0
- package/test/fork/TestTerminalPreviewParityFork.sol +109 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +116 -0
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +144 -25
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +11 -1
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +15 -2
- package/test/units/static/JBMultiTerminal/TestPay.sol +64 -2
- package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +116 -0
- package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +98 -0
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +11 -2
- package/test/units/static/JBRulesets/TestCurrentOf.sol +8 -6
- package/test/units/static/JBRulesets/TestRulesets.sol +25 -24
- package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +4 -17
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +49 -2
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +215 -0
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +475 -0
- package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +464 -0
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +113 -2
- package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +227 -5
package/ADMINISTRATION.md
CHANGED
|
@@ -115,6 +115,7 @@ Admin privileges and their scope in nana-core-v6.
|
|
|
115
115
|
| `beforeReceiveMigrationFrom` | JBDirectory only | N/A (msg.sender == DIRECTORY) | Per project | Called before migration to prepare the new controller. Copies metadata URI and distributes pending reserved tokens from the old controller. |
|
|
116
116
|
| `afterReceiveMigrationFrom` | JBDirectory only | N/A (msg.sender == DIRECTORY) | Per project | Called after migration completes. Currently a no-op. |
|
|
117
117
|
| `executePayReservedTokenToTerminal` | Self only | N/A (msg.sender == address(this)) | Internal | Pays a terminal with reserved tokens. Called internally via try-catch during reserved token distribution. |
|
|
118
|
+
| `previewMintOf` | Anyone | N/A (view) | Per project | Simulates a mint under the current ruleset and returns the beneficiary and reserved token counts. No state modification. |
|
|
118
119
|
|
|
119
120
|
### JBMultiTerminal
|
|
120
121
|
|
|
@@ -131,6 +132,8 @@ Admin privileges and their scope in nana-core-v6.
|
|
|
131
132
|
| `executePayout` | Self only | N/A (msg.sender == address(this)) | Internal | Executes a single payout to a split. Called internally via try-catch during payout distribution. |
|
|
132
133
|
| `executeProcessFee` | Self only | N/A (msg.sender == address(this)) | Internal | Processes a fee payment to the fee beneficiary project. Called internally via try-catch. |
|
|
133
134
|
| `executeTransferTo` | Self only | N/A (msg.sender == address(this)) | Internal | Transfers tokens to an address. Called internally via try-catch during payout leftover distribution. |
|
|
135
|
+
| `previewPayFor` | Anyone | N/A (view) | Per project | Simulates a payment and returns the ruleset, beneficiary token count, reserved token count, and hook specifications. No state modification. |
|
|
136
|
+
| `previewCashOutFrom` | Anyone | N/A (view) | Per project | Simulates a cash out and returns the ruleset, reclaim amount, cash out tax rate, and hook specifications. No state modification. |
|
|
134
137
|
|
|
135
138
|
### JBTokens
|
|
136
139
|
|
package/ARCHITECTURE.md
CHANGED
|
@@ -72,6 +72,30 @@ Holder -> JBMultiTerminal.cashOutTokensOf()
|
|
|
72
72
|
OR if cashOutTaxRate == 0 and project has unconsumed fee-free surplus (_feeFreeSurplusOf)
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### Preview Flow
|
|
76
|
+
|
|
77
|
+
Every core user action has a `view` counterpart that simulates the operation without modifying state. These compose the same internal computation paths as their non-preview counterparts and include data hook effects.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Caller -> JBMultiTerminal.previewPayFor(projectId, token, amount, beneficiary, metadata)
|
|
81
|
+
-> JBTerminalStore.previewPayFrom()
|
|
82
|
+
-> Read current ruleset, apply data hook
|
|
83
|
+
-> Calculate token count from weight
|
|
84
|
+
-> JBController.previewMintOf()
|
|
85
|
+
-> Split token count into beneficiary + reserved portions
|
|
86
|
+
-> Returns (ruleset, beneficiaryTokenCount, reservedTokenCount, hookSpecifications)
|
|
87
|
+
|
|
88
|
+
Caller -> JBMultiTerminal.previewCashOutFrom(holder, projectId, cashOutCount, tokenToReclaim, beneficiary, metadata)
|
|
89
|
+
-> JBTerminalStore.previewCashOutFrom()
|
|
90
|
+
-> Calculate surplus, get totalSupply, apply data hook
|
|
91
|
+
-> JBCashOuts.cashOutFrom() — bonding curve
|
|
92
|
+
-> Returns (ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)
|
|
93
|
+
|
|
94
|
+
Caller -> JBController.previewMintOf(projectId, tokenCount, useReservedPercent)
|
|
95
|
+
-> Read current ruleset
|
|
96
|
+
-> Returns (beneficiaryTokenCount, reservedTokenCount)
|
|
97
|
+
```
|
|
98
|
+
|
|
75
99
|
### Payout Flow
|
|
76
100
|
|
|
77
101
|
```
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -26,8 +26,8 @@ Read [RISKS.md](./RISKS.md) for known risks, trust model, and reentrancy analysi
|
|
|
26
26
|
|
|
27
27
|
| Contract | Lines | Role | Calls |
|
|
28
28
|
|----------|-------|------|-------|
|
|
29
|
-
| **JBMultiTerminal** | ~2024 | Payment terminal. Handles pay, cash out, payouts, surplus allowance, fees. Multi-token. Permit2 integration. | Store, Controller, Splits, Directory, Prices |
|
|
30
|
-
| **JBController** | ~1186 | Orchestrator. Project lifecycle, ruleset queuing, token minting/burning, reserved token distribution. ERC-2771 meta-tx. | Rulesets, Tokens, Splits, FundAccessLimits, Directory, Prices |
|
|
29
|
+
| **JBMultiTerminal** | ~2024 | Payment terminal. Handles pay, cash out, payouts, surplus allowance, fees, and previews (`previewPayFor`, `previewCashOutFrom`). Multi-token. Permit2 integration. | Store, Controller, Splits, Directory, Prices |
|
|
30
|
+
| **JBController** | ~1186 | Orchestrator. Project lifecycle, ruleset queuing, token minting/burning, reserved token distribution, mint preview (`previewMintOf`). ERC-2771 meta-tx. | Rulesets, Tokens, Splits, FundAccessLimits, Directory, Prices |
|
|
31
31
|
| **JBTerminalStore** | ~800 | Bookkeeping. Balances, payout limit tracking, surplus calculation, bonding curve reclaim math. Data hook integration point. | Rulesets, Prices, Directory |
|
|
32
32
|
| **JBRulesets** | ~1093 | Ruleset lifecycle. Linked-list via `basedOnId`. Weight decay with cache (20k iteration threshold). Approval hooks. Bit-packed storage. | Directory (via JBControlled) |
|
|
33
33
|
| **JBDirectory** | ~300 | Routes projects to terminals and controllers. Migration lifecycle (before/after). | Projects, Permissions |
|
|
@@ -262,6 +262,8 @@ These are the patterns that will trip you up if you are not aware of them:
|
|
|
262
262
|
12. **Credits are burned before ERC-20 tokens** in `JBTokens.burnFrom()`.
|
|
263
263
|
13. **`JBERC20` is cloned via `Clones.clone()`** -- constructor sets invalid name/symbol; real values set in `initialize()`.
|
|
264
264
|
14. **Named returns auto-return** -- several functions use named return variables without explicit `return` statements.
|
|
265
|
+
15. **Preview functions call data hooks** -- `previewPayFor`, `previewCashOutFrom`, and their store-level counterparts invoke data hooks during simulation. A reverting data hook will cause the preview to revert. Preview functions are `view` but still make external calls to hooks.
|
|
266
|
+
16. **Store preview functions use `msg.sender` as terminal** -- `JBTerminalStore.previewPayFrom` and `previewCashOutFrom` use `msg.sender` (not an explicit parameter) as the terminal context. Only terminal contracts calling these will get correct balance/surplus lookups. Use the terminal-level `JBMultiTerminal.previewPayFor` / `previewCashOutFrom` instead for general-purpose previews.
|
|
265
267
|
|
|
266
268
|
## Priority Areas to Audit
|
|
267
269
|
|
package/CHANGE_LOG.md
CHANGED
|
@@ -14,6 +14,28 @@ This document describes all changes between `nana-core` (v5, Solidity 0.8.23) an
|
|
|
14
14
|
|
|
15
15
|
A new `_feeFreeSurplusOf` mapping (`projectId => token => uint256`) tracks cumulative fee-free intra-terminal payouts received by each project. When a split payout lands on the same terminal (intra-terminal routing, i.e. `terminal == this`), the net payout amount is added to `_feeFreeSurplusOf[projectId][token]`. During a cashout with `cashOutTaxRate == 0`, fees are now charged on the reclaim amount up to the tracked fee-free surplus (and the tracker is decremented accordingly). Cashouts beyond the fee-free surplus remain fee-free. This closes a round-trip fee bypass where funds could be routed fee-free into a project via an intra-terminal split payout and then cashed out fee-free via a zero-tax cashout.
|
|
16
16
|
|
|
17
|
+
### 0.4 JBTerminalStore -- Preview Functions
|
|
18
|
+
|
|
19
|
+
Two `view` functions on `JBTerminalStore` and `IJBTerminalStore`:
|
|
20
|
+
|
|
21
|
+
- `previewPayFrom(address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` -- Simulates a payment and returns `(JBRuleset ruleset, uint256 tokenCount, JBPayHookSpecification[] hookSpecifications)`. Uses `msg.sender` as the terminal context. Invokes data hooks if configured. Does not modify state.
|
|
22
|
+
- `previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, JBAccountingContext accountingContext, JBAccountingContext[] balanceAccountingContexts, bool beneficiaryIsFeeless, bytes metadata)` -- Simulates a cash out and returns `(JBRuleset ruleset, uint256 reclaimAmount, uint256 cashOutTaxRate, JBCashOutHookSpecification[] hookSpecifications)`. Uses `msg.sender` as the terminal context. Invokes data hooks if configured. Does not modify state.
|
|
23
|
+
|
|
24
|
+
Internal computation logic was extracted into shared `_computePayFrom` and `_computeCashOutFrom` view helpers; the existing `recordPaymentFrom` and `recordCashOutFor` functions were refactored to call these helpers before writing state.
|
|
25
|
+
|
|
26
|
+
### 0.5 Terminal-Level Preview APIs
|
|
27
|
+
|
|
28
|
+
New `view` functions on `JBMultiTerminal`, `JBController`, and their interfaces provide user-facing preview entry points that compose the store-level previews with the mint token split:
|
|
29
|
+
|
|
30
|
+
- `JBMultiTerminal.previewPayFor(uint256 projectId, address token, uint256 amount, address beneficiary, bytes metadata)` -- Simulates a full payment including the reserved/beneficiary token split. Calls `STORE.previewPayFrom` then `controller.previewMintOf`. Returns `(JBRuleset ruleset, uint256 beneficiaryTokenCount, uint256 reservedTokenCount, JBPayHookSpecification[] hookSpecifications)`.
|
|
31
|
+
- `JBMultiTerminal.previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, address beneficiary, bytes metadata)` -- Simulates a full cash out. Resolves accounting context internally and delegates to `STORE.previewCashOutFrom`. Returns `(JBRuleset ruleset, uint256 reclaimAmount, uint256 cashOutTaxRate, JBCashOutHookSpecification[] hookSpecifications)`.
|
|
32
|
+
- `JBController.previewMintOf(uint256 projectId, uint256 tokenCount, bool useReservedPercent)` -- Previews how a mint splits between beneficiary and reserved tokens under the current ruleset. Returns `(uint256 beneficiaryTokenCount, uint256 reservedTokenCount)`.
|
|
33
|
+
|
|
34
|
+
Also in this release:
|
|
35
|
+
- **Error consolidation**: Three separate `UnderMin*` errors on `JBMultiTerminal` consolidated into a single `JBMultiTerminal_UnderMin()` error for bytecode size reduction.
|
|
36
|
+
- **`via_ir = true`**: Added to `foundry.toml` to enable the Solidity IR optimizer pipeline, reducing deployed bytecode size (EIP-170 compliance).
|
|
37
|
+
- Internal helpers extracted: `_accountingContextOf` and `_tokenAmountOf` on `JBMultiTerminal`, `_splitTokenCount` on `JBController`.
|
|
38
|
+
|
|
17
39
|
### 0.3 JBBeforeCashOutRecordedContext -- beneficiaryIsFeeless Field
|
|
18
40
|
|
|
19
41
|
A `bool beneficiaryIsFeeless` field was added to the `JBBeforeCashOutRecordedContext` struct (before the `metadata` field). `recordCashOutFor` in `IJBTerminalStore` gained a corresponding `bool beneficiaryIsFeeless` parameter. The terminal passes the result of its feeless address check, allowing data hooks to skip their own fees when the beneficiary is already feeless (e.g., project-to-project routing via the router terminal). This is a **breaking change** to both the struct layout and the `recordCashOutFor` function signature.
|
|
@@ -91,6 +113,12 @@ Parameters changed from `memory` to `calldata` for gas efficiency.
|
|
|
91
113
|
| `setTokenMetadataOf(uint256 projectId, string name, string symbol)` | Sets the name and symbol of a project's ERC-20 token. Requires the `SET_TOKEN_METADATA` permission. |
|
|
92
114
|
| `afterReceiveMigrationFrom(IERC165 from, uint256 projectId)` | Called by the directory after this controller has been set as the active controller. Added to the `IJBMigratable` interface. |
|
|
93
115
|
|
|
116
|
+
#### IJBTerminalStore / JBTerminalStore
|
|
117
|
+
|
|
118
|
+
| Function | Description |
|
|
119
|
+
|----------|-------------|
|
|
120
|
+
| `currentTotalReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 decimals, uint256 currency)` | Convenience view that returns the reclaimable surplus across all terminals using all accounting contexts. Delegates to `currentReclaimableSurplusOf` with empty `terminals` and `accountingContexts` arrays. Mirrors the `currentTotalSurplusOf` pattern. |
|
|
121
|
+
|
|
94
122
|
#### IJBTokens / JBTokens
|
|
95
123
|
|
|
96
124
|
| Function | Description |
|
|
@@ -353,7 +381,7 @@ Throughout the codebase, function calls were updated to use named argument synta
|
|
|
353
381
|
|----|----|-------|
|
|
354
382
|
| `IJBController` | `IJBController` | Gained `setTokenMetadataOf`. `calldata` for terminal configs. |
|
|
355
383
|
| `IJBRulesets` | `IJBRulesets` | `updateRulesetWeightCache` gained `rulesetId` parameter |
|
|
356
|
-
| `IJBTerminalStore` | `IJBTerminalStore` | `tokenCount` renamed to `cashOutCount` in `currentReclaimableSurplusOf`. `recordCashOutFor` gained `beneficiaryIsFeeless` param. View functions reordered. |
|
|
384
|
+
| `IJBTerminalStore` | `IJBTerminalStore` | `tokenCount` renamed to `cashOutCount` in `currentReclaimableSurplusOf`. `recordCashOutFor` gained `beneficiaryIsFeeless` param. New `currentTotalReclaimableSurplusOf` convenience view. View functions reordered. |
|
|
357
385
|
| `IJBPayoutTerminal` | `IJBPayoutTerminal` | `sendPayoutsOf` returns `amountPaidOut` (was `netLeftoverPayoutAmount`). `SendPayoutToSplit` event moved. |
|
|
358
386
|
| `IJBPermitTerminal` | `IJBPermitTerminal` | Gained `Permit2AllowanceFailed` event |
|
|
359
387
|
| `IJBMigratable` | `IJBMigratable` | Gained `afterReceiveMigrationFrom` function |
|
package/README.md
CHANGED
|
@@ -39,6 +39,16 @@ Credits and tokens can be **cashed out** to reclaim surplus funds along a bondin
|
|
|
39
39
|
- A **100% tax rate** means nothing can be reclaimed (all surplus is locked).
|
|
40
40
|
- Tax rates between 0% and 100% create a bonding curve that incentivizes holding -- later cashers-out get a better rate per token.
|
|
41
41
|
|
|
42
|
+
### Preview APIs
|
|
43
|
+
|
|
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.
|
|
45
|
+
|
|
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.
|
|
49
|
+
|
|
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.
|
|
51
|
+
|
|
42
52
|
### Reserved Tokens
|
|
43
53
|
|
|
44
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.
|
|
@@ -97,8 +107,8 @@ All contracts use Solidity `0.8.26`.
|
|
|
97
107
|
| `JBProjects` | ERC-721 registry of projects. Minting an NFT creates a project. Optionally mints project #1 to a fee beneficiary owner. |
|
|
98
108
|
| `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). |
|
|
99
109
|
| `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. |
|
|
100
|
-
| `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,
|
|
101
|
-
| `JBMultiTerminal` | Accepts payments (native ETH and ERC-20s), processes cash outs, distributes payouts, manages surplus allowances, and handles fees. Integrates with Permit2 for ERC-20 approvals. |
|
|
110
|
+
| `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`. |
|
|
111
|
+
| `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. |
|
|
102
112
|
| `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. |
|
|
103
113
|
| `JBRulesets` | Stores and manages project rulesets. Handles queuing, cycling, weight decay, approval hook validation, and weight caching for long-running projects. |
|
|
104
114
|
| `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. |
|
package/RISKS.md
CHANGED
|
@@ -124,7 +124,15 @@ No `ReentrancyGuard` is used. The system relies on state ordering and the `Inade
|
|
|
124
124
|
- `sendPayoutsOf` is callable by anyone (unless `ownerMustSendPayouts` is set). A split recipient that always reverts will cause that split's payout to fail, but the try-catch returns the amount to the project balance. Payout limit is still consumed. The project owner must wait until the next cycle.
|
|
125
125
|
- `addAccountingContextsFor` is gated by `allowAddAccountingContext` in the ruleset, but the contexts array is append-only and never shrinks. Over many rulesets, this could grow large enough to cause gas issues in functions that iterate over all contexts (e.g., `currentSurplusOf` when no explicit contexts are passed).
|
|
126
126
|
|
|
127
|
-
## 6.
|
|
127
|
+
## 6. Preview Functions
|
|
128
|
+
|
|
129
|
+
`JBMultiTerminal.previewPayFor`, `JBMultiTerminal.previewCashOutFrom`, and `JBController.previewMintOf` are `view` functions that simulate operations without modifying state. They compose the same computation paths as the real operations.
|
|
130
|
+
|
|
131
|
+
- **Data hooks are called during previews.** `previewPayFor` and `previewCashOutFrom` invoke `beforePayRecordedWith` and `beforeCashOutRecordedWith` on data hooks. A reverting data hook causes the preview to revert. A gas-consuming hook can cause the preview to run out of gas.
|
|
132
|
+
- **Store previews use `msg.sender` as terminal.** `JBTerminalStore.previewPayFrom` and `previewCashOutFrom` use `msg.sender` for balance/surplus lookups. Only a registered terminal calling these will get correct results. External callers should use the terminal-level functions (`JBMultiTerminal.previewPayFor` / `previewCashOutFrom`) which handle this automatically.
|
|
133
|
+
- **No state modification risk.** Preview functions cannot change balances, mint/burn tokens, or consume limits. They are safe to call from any context.
|
|
134
|
+
|
|
135
|
+
## 7. Integration Risks
|
|
128
136
|
|
|
129
137
|
### Non-Standard ERC-20s
|
|
130
138
|
|
|
@@ -148,7 +156,7 @@ No `ReentrancyGuard` is used. The system relies on state ordering and the `Inade
|
|
|
148
156
|
|
|
149
157
|
- `JBTerminalStore.recordAddedBalanceFor` has **no access control**. Any address can call it. The balance is keyed by `msg.sender` (the terminal address), so only a terminal can inflate its own recorded balance. This is safe as long as all terminals correctly track their actual holdings. A buggy or malicious terminal implementation could call `recordAddedBalanceFor` without actually receiving tokens, inflating the recorded balance above actual holdings.
|
|
150
158
|
|
|
151
|
-
##
|
|
159
|
+
## 8. Invariants to Verify
|
|
152
160
|
|
|
153
161
|
These should hold at all times and are the most productive targets for formal verification or invariant testing:
|
|
154
162
|
|
package/SKILLS.md
CHANGED
|
@@ -52,6 +52,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
52
52
|
| `allRulesetsOf(uint256 projectId, uint256 startingId, uint256 size)` | Returns an array of rulesets with metadata, paginated. |
|
|
53
53
|
| `pendingReservedTokenBalanceOf(uint256 projectId)` | Returns accumulated reserved tokens not yet distributed. |
|
|
54
54
|
| `totalTokenSupplyWithReservedTokensOf(uint256 projectId)` | Returns total supply including pending reserved tokens. |
|
|
55
|
+
| `previewMintOf(uint256 projectId, uint256 tokenCount, bool useReservedPercent)` | Simulates a mint under the current ruleset. Returns `(beneficiaryTokenCount, reservedTokenCount)`. Reverts if `tokenCount` is 0. |
|
|
55
56
|
|
|
56
57
|
### JBMultiTerminal
|
|
57
58
|
|
|
@@ -68,6 +69,8 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
68
69
|
| `currentSurplusOf(uint256 projectId, JBAccountingContext[] accountingContexts, uint256 decimals, uint256 currency)` | Returns the project's current surplus in the specified currency. |
|
|
69
70
|
| `accountingContextForTokenOf(uint256 projectId, address token)` | Returns the accounting context for a specific token. |
|
|
70
71
|
| `accountingContextsOf(uint256 projectId)` | Returns all accounting contexts for a project. |
|
|
72
|
+
| `previewPayFor(uint256 projectId, address token, uint256 amount, address beneficiary, bytes metadata)` | Simulates a full payment including the reserved/beneficiary token split. Returns `(ruleset, beneficiaryTokenCount, reservedTokenCount, hookSpecifications)`. Composes `STORE.previewPayFrom` + `controller.previewMintOf`. |
|
|
73
|
+
| `previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, address beneficiary, bytes metadata)` | Simulates a full cash out including bonding curve and data hook effects. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. Delegates to `STORE.previewCashOutFrom`. |
|
|
71
74
|
| `heldFeesOf(uint256 projectId, address token, uint256 count)` | Returns up to `count` held fees for a project/token. |
|
|
72
75
|
|
|
73
76
|
### JBTerminalStore
|
|
@@ -83,7 +86,13 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
83
86
|
| `balanceOf(address terminal, uint256 projectId, address token)` | Returns the balance of a project at a terminal for a given token. |
|
|
84
87
|
| `usedPayoutLimitOf(address terminal, uint256 projectId, address token, uint256 rulesetCycleNumber, uint256 currency)` | Returns the used payout limit for a project in a given cycle. |
|
|
85
88
|
| `usedSurplusAllowanceOf(address terminal, uint256 projectId, address token, uint256 rulesetId, uint256 currency)` | Returns the used surplus allowance for a project in a given ruleset. |
|
|
89
|
+
| `currentReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 totalSupply, uint256 surplus)` | Returns the reclaimable surplus given raw total supply and surplus values. Applies bonding curve from current ruleset. |
|
|
90
|
+
| `currentReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, IJBTerminal[] terminals, JBAccountingContext[] accountingContexts, uint256 decimals, uint256 currency)` | Returns the reclaimable surplus across specified terminals. Empty arrays default to all terminals/contexts. |
|
|
91
|
+
| `currentTotalReclaimableSurplusOf(uint256 projectId, uint256 cashOutCount, uint256 decimals, uint256 currency)` | Convenience view: reclaimable surplus across all terminals using all accounting contexts. Mirrors `currentTotalSurplusOf`. |
|
|
86
92
|
| `currentSurplusOf(address terminal, uint256 projectId, JBAccountingContext[] accountingContexts, uint256 decimals, uint256 currency)` | Returns the current surplus for a project at a terminal. |
|
|
93
|
+
| `currentTotalSurplusOf(uint256 projectId, uint256 decimals, uint256 currency)` | Returns the total surplus across all terminals. |
|
|
94
|
+
| `previewPayFrom(address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` | Simulates a payment without modifying state. Uses `msg.sender` as the terminal context. Invokes data hooks if configured. Returns ruleset, token count, and hook specifications. |
|
|
95
|
+
| `previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, JBAccountingContext accountingContext, JBAccountingContext[] balanceAccountingContexts, bool beneficiaryIsFeeless, bytes metadata)` | Simulates a cash out without modifying state. Uses `msg.sender` as the terminal context. Invokes data hooks if configured. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
|
|
87
96
|
|
|
88
97
|
### JBRulesets
|
|
89
98
|
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -69,6 +69,8 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
|
|
|
69
69
|
- Data hook can return empty weight (0) to suppress minting while still recording payment
|
|
70
70
|
- Fee-on-transfer tokens: actual amount received is `_balanceOf(token) - balanceBefore` (measured via balance diff)
|
|
71
71
|
|
|
72
|
+
**Preview**: Call `JBMultiTerminal.previewPayFor(projectId, token, amount, beneficiary, metadata)` to simulate the full payment on-chain -- including data hook effects and the reserved/beneficiary token split. Returns `(ruleset, beneficiaryTokenCount, reservedTokenCount, hookSpecifications)`. This is a `view` function that does not modify state. For lower-level access without the mint split, the terminal delegates to `JBTerminalStore.previewPayFrom(payer, amount, projectId, beneficiary, metadata)` which returns the raw `(ruleset, tokenCount, hookSpecifications)`.
|
|
73
|
+
|
|
72
74
|
---
|
|
73
75
|
|
|
74
76
|
## 3. Cash Out Tokens
|
|
@@ -95,6 +97,8 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
|
|
|
95
97
|
|
|
96
98
|
**Events**: `CashOutTokens(rulesetId, rulesetCycleNumber, projectId, holder, beneficiary, cashOutCount, cashOutTaxRate, reclaimAmount, metadata, caller)`
|
|
97
99
|
|
|
100
|
+
**Preview**: Call `JBMultiTerminal.previewCashOutFrom(holder, projectId, cashOutCount, tokenToReclaim, beneficiary, metadata)` to simulate the full cash out on-chain -- including data hook effects on tax rate, supply, and hook specifications. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. This is a `view` function that does not modify state. The terminal delegates to `JBTerminalStore.previewCashOutFrom(holder, projectId, cashOutCount, accountingContext, balanceAccountingContexts, beneficiaryIsFeeless, metadata)` for the bonding curve computation. For a simpler estimate without data hook effects, use `currentTotalReclaimableSurplusOf(projectId, cashOutCount, decimals, currency)` or the 6-param `currentReclaimableSurplusOf` overload.
|
|
101
|
+
|
|
98
102
|
**Edge cases**:
|
|
99
103
|
- `cashOutCount = 0` with `totalSupply = 0` -- returns entire surplus (C-5 known bug)
|
|
100
104
|
- `cashOutTaxRate = MAX (10,000)` -- returns 0 (all surplus locked)
|
|
@@ -188,6 +192,8 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
|
|
|
188
192
|
|
|
189
193
|
**Events**: `MintTokens(beneficiary, projectId, tokenCount, beneficiaryTokenCount, memo, reservedPercent, caller)`
|
|
190
194
|
|
|
195
|
+
**Preview**: Call `JBController.previewMintOf(projectId, tokenCount, useReservedPercent)` to see how a mint would split between the beneficiary and reserved token pools under the current ruleset. Returns `(beneficiaryTokenCount, reservedTokenCount)`. This is a `view` function that does not modify state.
|
|
196
|
+
|
|
191
197
|
**Edge cases**:
|
|
192
198
|
- `tokenCount = 0` -- reverts (`ZeroTokensToMint`)
|
|
193
199
|
- If `allowOwnerMinting` is false in ruleset, only terminals and data hooks can mint
|
package/foundry.toml
CHANGED
package/package.json
CHANGED
package/src/JBController.sol
CHANGED
|
@@ -535,12 +535,10 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
535
535
|
// Determine the reserved percent to use.
|
|
536
536
|
reservedPercent = useReservedPercent ? ruleset.reservedPercent() : 0;
|
|
537
537
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
beneficiaryTokenCount = mulDiv(
|
|
541
|
-
tokenCount, JBConstants.MAX_RESERVED_PERCENT - reservedPercent, JBConstants.MAX_RESERVED_PERCENT
|
|
542
|
-
);
|
|
538
|
+
// Split the requested token count into beneficiary and reserved portions.
|
|
539
|
+
(beneficiaryTokenCount,) = _splitTokenCount({tokenCount: tokenCount, reservedPercent: reservedPercent});
|
|
543
540
|
|
|
541
|
+
if (beneficiaryTokenCount != 0) {
|
|
544
542
|
// Mint the tokens.
|
|
545
543
|
// slither-disable-next-line reentrancy-benign,reentrancy-events,unused-return
|
|
546
544
|
TOKENS.mintFor({holder: beneficiary, projectId: projectId, count: beneficiaryTokenCount});
|
|
@@ -799,6 +797,34 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
799
797
|
metadata = ruleset.expandMetadata();
|
|
800
798
|
}
|
|
801
799
|
|
|
800
|
+
/// @notice Previews how many beneficiary and reserved tokens `mintTokensOf(...)` would produce.
|
|
801
|
+
/// @param projectId The ID of the project whose tokens are being minted.
|
|
802
|
+
/// @param tokenCount The number of tokens to mint, including any reserved tokens.
|
|
803
|
+
/// @param useReservedPercent Whether to apply the ruleset's reserved percent.
|
|
804
|
+
/// @return beneficiaryTokenCount The number of tokens that would be minted for the beneficiary.
|
|
805
|
+
/// @return reservedTokenCount The number of tokens that would be reserved.
|
|
806
|
+
function previewMintOf(
|
|
807
|
+
uint256 projectId,
|
|
808
|
+
uint256 tokenCount,
|
|
809
|
+
bool useReservedPercent
|
|
810
|
+
)
|
|
811
|
+
external
|
|
812
|
+
view
|
|
813
|
+
override
|
|
814
|
+
returns (uint256 beneficiaryTokenCount, uint256 reservedTokenCount)
|
|
815
|
+
{
|
|
816
|
+
// Revert if there are no tokens to split.
|
|
817
|
+
if (tokenCount == 0) revert JBController_ZeroTokensToMint();
|
|
818
|
+
|
|
819
|
+
// Keep a reference to the current ruleset.
|
|
820
|
+
JBRuleset memory ruleset = _currentRulesetOf(projectId);
|
|
821
|
+
|
|
822
|
+
return
|
|
823
|
+
_splitTokenCount({
|
|
824
|
+
tokenCount: tokenCount, reservedPercent: useReservedPercent ? ruleset.reservedPercent() : 0
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
802
828
|
/// @notice Check whether the project's controller can currently be set.
|
|
803
829
|
/// @param projectId The ID of the project to check.
|
|
804
830
|
/// @return A `bool` which is true if the project allows controllers to be set.
|
|
@@ -1141,6 +1167,27 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
|
|
|
1141
1167
|
}
|
|
1142
1168
|
}
|
|
1143
1169
|
|
|
1170
|
+
/// @notice Splits a token count into beneficiary and reserved portions.
|
|
1171
|
+
/// @param tokenCount The total token count, including reserved tokens.
|
|
1172
|
+
/// @param reservedPercent The reserved percent to apply.
|
|
1173
|
+
/// @return beneficiaryTokenCount The number of tokens for the beneficiary.
|
|
1174
|
+
/// @return reservedTokenCount The number of tokens to reserve.
|
|
1175
|
+
function _splitTokenCount(
|
|
1176
|
+
uint256 tokenCount,
|
|
1177
|
+
uint256 reservedPercent
|
|
1178
|
+
)
|
|
1179
|
+
internal
|
|
1180
|
+
pure
|
|
1181
|
+
returns (uint256 beneficiaryTokenCount, uint256 reservedTokenCount)
|
|
1182
|
+
{
|
|
1183
|
+
// Compute the beneficiary's portion after removing the reserved share.
|
|
1184
|
+
beneficiaryTokenCount =
|
|
1185
|
+
mulDiv(tokenCount, JBConstants.MAX_RESERVED_PERCENT - reservedPercent, JBConstants.MAX_RESERVED_PERCENT);
|
|
1186
|
+
|
|
1187
|
+
// The remaining tokens are reserved.
|
|
1188
|
+
reservedTokenCount = tokenCount - beneficiaryTokenCount;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1144
1191
|
//*********************************************************************//
|
|
1145
1192
|
// -------------------------- internal views ------------------------- //
|
|
1146
1193
|
//*********************************************************************//
|