@bananapus/core-v6 0.0.33 → 0.0.35
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 +75 -348
- package/ARCHITECTURE.md +86 -44
- package/AUDIT_INSTRUCTIONS.md +29 -42
- package/README.md +22 -3
- package/RISKS.md +45 -1
- package/SKILLS.md +16 -4
- package/USER_JOURNEYS.md +130 -30
- package/foundry.toml +2 -0
- package/package.json +2 -2
- package/references/entrypoints.md +1 -1
- package/script/Deploy.s.sol +2 -1
- package/src/JBERC20.sol +100 -30
- package/src/JBTerminalStore.sol +64 -23
- package/src/JBTokens.sol +1 -1
- package/src/abstract/JBPermissioned.sol +28 -0
- package/src/interfaces/IJBRulesetDataHook.sol +6 -1
- package/src/interfaces/IJBToken.sol +3 -3
- package/src/structs/JBAccountingContext.sol +0 -1
- package/src/structs/JBAfterCashOutRecordedContext.sol +0 -1
- package/src/structs/JBAfterPayRecordedContext.sol +0 -1
- package/src/structs/JBBeforeCashOutRecordedContext.sol +0 -1
- package/src/structs/JBBeforePayRecordedContext.sol +0 -1
- package/src/structs/JBCashOutHookSpecification.sol +0 -1
- package/src/structs/JBCurrencyAmount.sol +0 -1
- package/src/structs/JBFee.sol +0 -1
- package/src/structs/JBFundAccessLimitGroup.sol +0 -1
- package/src/structs/JBPayHookSpecification.sol +0 -1
- package/src/structs/JBPermissionsData.sol +0 -1
- package/src/structs/JBRuleset.sol +0 -1
- package/src/structs/JBRulesetConfig.sol +0 -1
- package/src/structs/JBRulesetMetadata.sol +0 -1
- package/src/structs/JBRulesetWeightCache.sol +0 -1
- package/src/structs/JBRulesetWithMetadata.sol +0 -1
- package/src/structs/JBSingleAllowance.sol +0 -1
- package/src/structs/JBSplit.sol +0 -1
- package/src/structs/JBSplitGroup.sol +0 -1
- package/src/structs/JBSplitHookContext.sol +0 -1
- package/src/structs/JBTerminalConfig.sol +0 -1
- package/src/structs/JBTokenAmount.sol +0 -1
- package/test/TestCashOutHooks.sol +12 -2
- package/test/TestDataHookFuzzing.sol +4 -4
- package/test/TestForwardedTokenConsumption.sol +7 -1
- package/test/TestJBERC20Inheritance.sol +3 -1
- package/test/TestTokenFlow.sol +2 -2
- package/test/audit/CashOutReenterPay.t.sol +5 -0
- package/test/audit/CodexHeldFeeRounding.t.sol +159 -0
- package/test/helpers/TestBaseWorkflow.sol +1 -1
- package/test/units/static/JBERC20/JBERC20Setup.sol +8 -3
- package/test/units/static/JBERC20/TestInitialize.sol +12 -13
- package/test/units/static/JBERC20/TestName.sol +1 -1
- package/test/units/static/JBERC20/TestNonces.sol +2 -1
- package/test/units/static/JBERC20/TestSymbol.sol +1 -1
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +1 -1
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +4 -4
- package/test/units/static/JBTokens/JBTokensSetup.sol +5 -1
package/USER_JOURNEYS.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# User Journeys
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Repo Purpose
|
|
4
|
+
|
|
5
|
+
This repo is the canonical Juicebox V6 runtime surface.
|
|
6
|
+
It owns project identity, rulesets, terminal execution, treasury accounting, permissions, price feeds, and migration
|
|
7
|
+
paths. Most other V6 repos wrap or extend this behavior rather than replacing it.
|
|
8
|
+
|
|
9
|
+
## Primary Actors
|
|
4
10
|
|
|
5
11
|
- founders launching and evolving Juicebox projects
|
|
6
12
|
- supporters paying projects in the asset a terminal accepts
|
|
@@ -8,109 +14,203 @@
|
|
|
8
14
|
- operators managing permissions, splits, fund access limits, and rulesets
|
|
9
15
|
- integrators wiring hooks, terminals, price feeds, and migrations into the canonical protocol surface
|
|
10
16
|
|
|
17
|
+
## Key Surfaces
|
|
18
|
+
|
|
19
|
+
- `JBController`: project launch, ruleset queueing, token setup, splits, and controller migration
|
|
20
|
+
- `JBMultiTerminal`: pay, payout, allowance, preview, and cash-out entrypoint
|
|
21
|
+
- `JBTerminalStore`: balance, surplus, fee, and reclaim accounting
|
|
22
|
+
- `JBDirectory`: controller and terminal routing
|
|
23
|
+
- `JBPermissions`: packed operator-permission registry
|
|
24
|
+
- `JBProjects`, `JBRulesets`, `JBPrices`, `JBFundAccessLimits`, `JBSplits`, `JBTokens`: core state and helper surfaces
|
|
25
|
+
|
|
11
26
|
## Journey 1: Launch A Project With The Right Initial Shape
|
|
12
27
|
|
|
13
|
-
**
|
|
28
|
+
**Actor:** founder, deployer, or protocol integrator.
|
|
14
29
|
|
|
15
|
-
**
|
|
30
|
+
**Intent:** create a project with the right owner, terminal, ruleset, and hook assumptions from block zero.
|
|
16
31
|
|
|
17
|
-
**
|
|
32
|
+
**Preconditions**
|
|
33
|
+
- the team knows who should own the project, which terminals it should use, and what the first ruleset should allow
|
|
34
|
+
|
|
35
|
+
**Main Flow**
|
|
18
36
|
1. Call `JBController.launchProjectFor(...)` with the owner, URI, ruleset config, terminal configs, and any split or hook metadata.
|
|
19
37
|
2. `JBProjects` mints the project NFT, `JBDirectory` records controller and terminal routing, and `JBRulesets` stores the first ruleset.
|
|
20
38
|
3. If the project wants ERC-20 tokens, reserved-rate behavior, or hook-driven behavior, that configuration is committed at launch instead of being inferred later.
|
|
21
39
|
4. The project can now accept payments and queue future rulesets without changing project identity.
|
|
22
40
|
|
|
23
|
-
**Failure
|
|
41
|
+
**Failure Modes**
|
|
42
|
+
- accounting contexts do not match the intended terminal asset
|
|
43
|
+
- hook metadata or split assumptions are invalid at launch
|
|
44
|
+
- ownership or permission assumptions are wrong and expensive to repair later
|
|
45
|
+
|
|
46
|
+
**Postconditions**
|
|
47
|
+
- the project NFT exists, the initial ruleset is active, accepted terminals are installed, and downstream hooks or splits can begin working immediately
|
|
24
48
|
|
|
25
49
|
## Journey 2: Accept A Payment And Issue The Right Token Exposure
|
|
26
50
|
|
|
27
|
-
**
|
|
51
|
+
**Actor:** payer or integration paying on a user's behalf.
|
|
52
|
+
|
|
53
|
+
**Intent:** settle a payment through the canonical terminal path and issue the correct token exposure.
|
|
28
54
|
|
|
29
|
-
**
|
|
55
|
+
**Preconditions**
|
|
56
|
+
- the project has an active ruleset and a terminal that accepts the payer's asset
|
|
30
57
|
|
|
31
|
-
**Flow**
|
|
58
|
+
**Main Flow**
|
|
32
59
|
1. A payer calls `pay(...)` on `JBMultiTerminal`.
|
|
33
60
|
2. The terminal validates the accounting context, records funds, and asks `JBTerminalStore` to derive issuance from the active ruleset.
|
|
34
61
|
3. `JBController` and `JBTokens` decide whether the beneficiary gets project token credits, ERC-20s, or no issuance because weight is zero.
|
|
35
62
|
4. Any configured pay hooks or data hooks run around the accounting path.
|
|
36
63
|
|
|
37
|
-
**
|
|
64
|
+
**Failure Modes**
|
|
65
|
+
- payments are paused or the token is unsupported for the target accounting context
|
|
66
|
+
- fee-on-transfer behavior or price-feed assumptions break the intended issuance path
|
|
67
|
+
- hooks add side effects the payer or integrator did not account for
|
|
68
|
+
|
|
69
|
+
**Postconditions**
|
|
70
|
+
- treasury balances increase, hooks run in the right order, and the beneficiary receives credits or ERC-20 tokens consistent with the ruleset
|
|
38
71
|
|
|
39
72
|
## Journey 3: Turn Credits Into ERC-20 Tokens Once A Project Wants A Transferable Token
|
|
40
73
|
|
|
41
|
-
**
|
|
74
|
+
**Actor:** holder or operator acting for a holder.
|
|
75
|
+
|
|
76
|
+
**Intent:** convert non-transferable project credits into ERC-20 balances once the project exposes a token.
|
|
42
77
|
|
|
43
|
-
**
|
|
78
|
+
**Preconditions**
|
|
79
|
+
- users already have project token credits
|
|
80
|
+
- the project now wants an ERC-20 representation
|
|
44
81
|
|
|
45
|
-
**Flow**
|
|
82
|
+
**Main Flow**
|
|
46
83
|
1. Deploy or set the project's ERC-20 token through `JBController`.
|
|
47
84
|
2. Holders or operators call `claimTokensFor(...)` to convert credits into ERC-20 balances for a beneficiary.
|
|
48
85
|
3. Future issuance can continue using the same project identity while users now interact with a standard token surface.
|
|
49
86
|
|
|
87
|
+
**Failure Modes**
|
|
88
|
+
- the wrong token is installed for the project
|
|
89
|
+
- integrations assume credits are automatically ERC-20 balances after token installation
|
|
90
|
+
|
|
91
|
+
**Postconditions**
|
|
92
|
+
- the project deploys or installs its ERC-20 token and holders can claim credits into transferable balances
|
|
93
|
+
|
|
50
94
|
## Journey 4: Distribute Treasury Funds Through Governed Paths
|
|
51
95
|
|
|
52
|
-
**
|
|
96
|
+
**Actor:** owner or authorized operator.
|
|
97
|
+
|
|
98
|
+
**Intent:** move value out of the treasury through configured payouts or allowance surfaces.
|
|
53
99
|
|
|
54
|
-
**
|
|
100
|
+
**Preconditions**
|
|
101
|
+
- the project has terminal balances
|
|
102
|
+
- the caller is allowed to use payout or allowance paths
|
|
55
103
|
|
|
56
|
-
**Flow**
|
|
104
|
+
**Main Flow**
|
|
57
105
|
1. Authorized actors call payout or allowance surfaces on the terminal.
|
|
58
106
|
2. `JBFundAccessLimits` bounds how much may leave for the current ruleset cycle.
|
|
59
107
|
3. `JBSplits` fans value out to beneficiaries, projects, hooks, or fee recipients as configured.
|
|
60
108
|
4. `JBTerminalStore` updates balances and fee accounting so later previews and cash outs remain consistent.
|
|
61
109
|
|
|
62
|
-
**Failure
|
|
110
|
+
**Failure Modes**
|
|
111
|
+
- splits or access limits no longer match operator expectations
|
|
112
|
+
- downstream hook execution fails during payout fanout
|
|
113
|
+
- operators assume allowance withdrawals behave exactly like payouts when fee treatment differs
|
|
114
|
+
|
|
115
|
+
**Postconditions**
|
|
116
|
+
- treasury value leaves only through configured limits, recipients, and fee logic instead of arbitrary admin withdrawals
|
|
63
117
|
|
|
64
118
|
## Journey 5: Let Holders Cash Out Against Surplus
|
|
65
119
|
|
|
66
|
-
**
|
|
120
|
+
**Actor:** holder or integrator acting for a holder.
|
|
67
121
|
|
|
68
|
-
**
|
|
122
|
+
**Intent:** exit project-token exposure against available terminal surplus.
|
|
69
123
|
|
|
70
|
-
**
|
|
124
|
+
**Preconditions**
|
|
125
|
+
- a holder owns project token exposure
|
|
126
|
+
- the project has reclaimable surplus in some terminal
|
|
127
|
+
|
|
128
|
+
**Main Flow**
|
|
71
129
|
1. The holder calls `cashOutTokensOf(...)` on the relevant terminal.
|
|
72
130
|
2. `JBTerminalStore` calculates reclaim value using surplus, outstanding token supply, cash-out tax rate, and any pending reserved token effects.
|
|
73
131
|
3. Cash-out hooks can modify behavior or side effects, but the core accounting remains anchored in the terminal store.
|
|
74
132
|
4. Tokens burn and value exits the treasury through the terminal that actually held the asset.
|
|
75
133
|
|
|
76
|
-
**
|
|
134
|
+
**Failure Modes**
|
|
135
|
+
- fee-free or custom hook paths produce different outcomes than the holder expected
|
|
136
|
+
- preview-versus-execution drift appears under volatile routing or multi-terminal liquidity
|
|
137
|
+
- users misread multi-terminal surplus as one homogeneous pool
|
|
138
|
+
|
|
139
|
+
**Postconditions**
|
|
140
|
+
- the holder burns the intended amount of token exposure and receives the correct reclaim amount under the current ruleset
|
|
77
141
|
|
|
78
142
|
## Journey 6: Queue New Rulesets Without Migrating The Project
|
|
79
143
|
|
|
80
|
-
**
|
|
144
|
+
**Actor:** owner or authorized operator.
|
|
145
|
+
|
|
146
|
+
**Intent:** change future project economics without changing the project's identity or existing balances.
|
|
81
147
|
|
|
82
|
-
**
|
|
148
|
+
**Preconditions**
|
|
149
|
+
- the project is live and future economics need to change
|
|
83
150
|
|
|
84
|
-
**Flow**
|
|
151
|
+
**Main Flow**
|
|
85
152
|
1. The owner or an operator with the right permission queues one or more new rulesets through `JBController`.
|
|
86
153
|
2. `JBRulesets` stores the proposed future configuration and any approval hook requirements.
|
|
87
154
|
3. When the active duration elapses, the next approved ruleset becomes live and future pays, payouts, and cash outs follow the new terms.
|
|
88
155
|
4. Existing balances and token history remain intact because only future behavior changed.
|
|
89
156
|
|
|
90
|
-
**Failure
|
|
157
|
+
**Failure Modes**
|
|
158
|
+
- approval-hook requirements are forgotten or misunderstood
|
|
159
|
+
- queued metadata is incompatible with installed hooks
|
|
160
|
+
- teams assume a ruleset change can retroactively fix prior accounting
|
|
161
|
+
|
|
162
|
+
**Postconditions**
|
|
163
|
+
- the next ruleset activates on schedule while the project keeps the same identity, treasury, and downstream integrations
|
|
91
164
|
|
|
92
165
|
## Journey 7: Migrate A Project To New Terminal Or Controller Surfaces Deliberately
|
|
93
166
|
|
|
94
|
-
**
|
|
167
|
+
**Actor:** owner or migration operator.
|
|
168
|
+
|
|
169
|
+
**Intent:** move a live project to new terminal or controller surfaces without corrupting balances, permissions, or routing history.
|
|
95
170
|
|
|
96
|
-
**
|
|
171
|
+
**Preconditions**
|
|
172
|
+
- the project needs to move to a new terminal or controller path
|
|
173
|
+
- the destination surface is understood and intended
|
|
97
174
|
|
|
98
|
-
**Flow**
|
|
175
|
+
**Main Flow**
|
|
99
176
|
1. Confirm the active ruleset permits migration and the destination surface is the intended successor.
|
|
100
177
|
2. Use `JBController.migrate(...)` and the terminal-store migration paths instead of manually repointing addresses.
|
|
101
178
|
3. Recheck directory routing and accepted accounting contexts after migration completes.
|
|
102
179
|
|
|
180
|
+
**Failure Modes**
|
|
181
|
+
- migration targets are wrong or only partially configured
|
|
182
|
+
- operators manually repoint routing without using the protocol's migration surfaces
|
|
183
|
+
|
|
184
|
+
**Postconditions**
|
|
185
|
+
- balances, permissions, and future routing stay coherent after migration
|
|
186
|
+
|
|
103
187
|
## Journey 8: Hand Off Authority Without Handing Out Root Access
|
|
104
188
|
|
|
105
|
-
**
|
|
189
|
+
**Actor:** project owner.
|
|
190
|
+
|
|
191
|
+
**Intent:** delegate project operations narrowly instead of transferring blanket control.
|
|
106
192
|
|
|
107
|
-
**
|
|
193
|
+
**Preconditions**
|
|
194
|
+
- the owner wants operators, delegates, or automation to manage only specific surfaces
|
|
108
195
|
|
|
109
|
-
**Flow**
|
|
196
|
+
**Main Flow**
|
|
110
197
|
1. The owner configures operator permissions in `JBPermissions`.
|
|
111
198
|
2. Downstream calls check those packed permission bits instead of assuming project ownership.
|
|
112
199
|
3. Integrations such as ownable wrappers, hook deployers, and router registries can now respect project-scoped delegation without custom ACL logic.
|
|
113
200
|
|
|
201
|
+
**Failure Modes**
|
|
202
|
+
- operators receive permissions broader than they need
|
|
203
|
+
- auditors assume downstream access still depends only on project ownership
|
|
204
|
+
|
|
205
|
+
**Postconditions**
|
|
206
|
+
- permissions are narrow, auditable, and scoped to the actions the operator actually needs
|
|
207
|
+
|
|
208
|
+
## Trust Boundaries
|
|
209
|
+
|
|
210
|
+
- `JBTerminalStore` is the accounting truth for balances, surplus, fees, and reclaim behavior
|
|
211
|
+
- hooks, approval hooks, pay hooks, and cash-out hooks are trusted extension surfaces, not cosmetic plugins
|
|
212
|
+
- price feeds and directory routing are critical external-context surfaces inherited by many downstream repos
|
|
213
|
+
|
|
114
214
|
## Hand-Offs
|
|
115
215
|
|
|
116
216
|
- Use [nana-permission-ids-v6](../nana-permission-ids-v6/USER_JOURNEYS.md) for the shared permission vocabulary that downstream repos import.
|
package/foundry.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/core-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-core-v6'"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
29
|
+
"@bananapus/permission-ids-v6": "^0.0.17",
|
|
30
30
|
"@chainlink/contracts": "^1.5.0",
|
|
31
31
|
"@openzeppelin/contracts": "^5.6.1",
|
|
32
32
|
"@prb/math": "^4.1.1",
|
|
@@ -21,7 +21,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
21
21
|
| `JBSplits` | Split configurations per project/ruleset/group. Packed storage for gas efficiency. |
|
|
22
22
|
| `JBFundAccessLimits` | Payout limits and surplus allowances per project/ruleset/terminal/token. |
|
|
23
23
|
| `JBPrices` | Price feed registry with project-specific and protocol-wide default feeds. Immutable once set. |
|
|
24
|
-
| `JBERC20` | Cloneable ERC-20 with Votes + Permit.
|
|
24
|
+
| `JBERC20` | Cloneable ERC-20 with Votes + Permit + ERC-1271. Controlled by `JBTokens` via `onlyTokens`. Deployed via `Clones.clone()`. |
|
|
25
25
|
| `JBFeelessAddresses` | Allowlist for fee-exempt addresses. |
|
|
26
26
|
| `JBChainlinkV3PriceFeed` | Chainlink AggregatorV3 price feed with staleness threshold. Rejects negative/zero prices, incomplete rounds (`updatedAt == 0`), and stale answers carried from previous rounds (`answeredInRound < roundId`). |
|
|
27
27
|
| `JBChainlinkV3SequencerPriceFeed` | L2 sequencer-aware Chainlink feed (Optimism/Arbitrum) with grace period after restart. Treats any non-zero sequencer answer as down (`answer != 0`). |
|
package/script/Deploy.s.sol
CHANGED
|
@@ -85,7 +85,8 @@ contract Deploy is Script, Sphinx {
|
|
|
85
85
|
trustedForwarder: TRUSTED_FORWARDER
|
|
86
86
|
});
|
|
87
87
|
JBTokens tokens = new JBTokens{salt: keccak256(abi.encode(CORE_DEPLOYMENT_NONCE))}({
|
|
88
|
-
directory: directory,
|
|
88
|
+
directory: directory,
|
|
89
|
+
token: new JBERC20{salt: keccak256(abi.encode(CORE_DEPLOYMENT_NONCE))}(permissions, projects)
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
new JBFundAccessLimits{salt: keccak256(abi.encode(CORE_DEPLOYMENT_NONCE))}(directory);
|
package/src/JBERC20.sol
CHANGED
|
@@ -1,27 +1,49 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
|
-
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
5
4
|
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
6
5
|
import {ERC20Permit, Nonces} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
|
7
6
|
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
|
|
7
|
+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
8
|
+
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
|
|
8
9
|
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";
|
|
9
10
|
|
|
11
|
+
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
12
|
+
import {JBPermissioned} from "./abstract/JBPermissioned.sol";
|
|
13
|
+
import {IJBPermissions} from "./interfaces/IJBPermissions.sol";
|
|
14
|
+
import {IJBProjects} from "./interfaces/IJBProjects.sol";
|
|
10
15
|
import {IJBToken} from "./interfaces/IJBToken.sol";
|
|
16
|
+
import {IJBTokens} from "./interfaces/IJBTokens.sol";
|
|
11
17
|
|
|
12
18
|
/// @notice An ERC-20 token that can be used by a project in `JBTokens` and `JBController`.
|
|
13
19
|
/// @dev By default, a project uses "credits" to track balances. Once a project sets their `IJBToken` using
|
|
14
20
|
/// `JBController.deployERC20For(...)` or `JBController.setTokenFor(...)`, credits can be redeemed to claim tokens.
|
|
15
21
|
/// @dev `JBController.deployERC20For(...)` deploys a `JBERC20` contract and sets it as the project's token.
|
|
16
|
-
contract JBERC20 is ERC20Votes, ERC20Permit,
|
|
22
|
+
contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken {
|
|
17
23
|
//*********************************************************************//
|
|
18
24
|
// --------------------------- custom errors ------------------------- //
|
|
19
25
|
//*********************************************************************//
|
|
20
26
|
|
|
21
27
|
error JBERC20_AlreadyInitialized();
|
|
28
|
+
error JBERC20_Unauthorized();
|
|
22
29
|
|
|
23
30
|
//*********************************************************************//
|
|
24
|
-
//
|
|
31
|
+
// --------------- public immutable stored properties ---------------- //
|
|
32
|
+
//*********************************************************************//
|
|
33
|
+
|
|
34
|
+
/// @notice The projects contract used to resolve project ownership.
|
|
35
|
+
IJBProjects public immutable PROJECTS;
|
|
36
|
+
|
|
37
|
+
//*********************************************************************//
|
|
38
|
+
// --------------------- public stored properties -------------------- //
|
|
39
|
+
//*********************************************************************//
|
|
40
|
+
|
|
41
|
+
/// @notice The JBTokens contract that owns this token.
|
|
42
|
+
/// @dev Set via `initialize` because JBERC20 is deployed before JBTokens (circular dependency).
|
|
43
|
+
IJBTokens public TOKENS;
|
|
44
|
+
|
|
45
|
+
//*********************************************************************//
|
|
46
|
+
// -------------------- private stored properties -------------------- //
|
|
25
47
|
//*********************************************************************//
|
|
26
48
|
|
|
27
49
|
/// @notice The token's name.
|
|
@@ -38,8 +60,29 @@ contract JBERC20 is ERC20Votes, ERC20Permit, Ownable, IJBToken {
|
|
|
38
60
|
|
|
39
61
|
/// @dev Set `_name` on the implementation contract to prevent it from being initialized directly.
|
|
40
62
|
/// Clones start with empty `_name`, so `initialize(...)` works only on clones.
|
|
41
|
-
|
|
63
|
+
/// @param permissions The permissions contract.
|
|
64
|
+
/// @param projects The projects contract.
|
|
65
|
+
constructor(
|
|
66
|
+
IJBPermissions permissions,
|
|
67
|
+
IJBProjects projects
|
|
68
|
+
)
|
|
69
|
+
ERC20("invalid", "invalid")
|
|
70
|
+
ERC20Permit("JBToken")
|
|
71
|
+
JBPermissioned(permissions)
|
|
72
|
+
{
|
|
42
73
|
_name = "invalid";
|
|
74
|
+
PROJECTS = projects;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//*********************************************************************//
|
|
78
|
+
// --------------------------- modifiers ---------------------------- //
|
|
79
|
+
//*********************************************************************//
|
|
80
|
+
|
|
81
|
+
/// @notice Only the JBTokens contract can call this function.
|
|
82
|
+
// forge-lint: disable-next-line(unwrapped-modifier-logic)
|
|
83
|
+
modifier onlyTokens() {
|
|
84
|
+
if (msg.sender != address(TOKENS)) revert JBERC20_Unauthorized();
|
|
85
|
+
_;
|
|
43
86
|
}
|
|
44
87
|
|
|
45
88
|
//*********************************************************************//
|
|
@@ -47,51 +90,32 @@ contract JBERC20 is ERC20Votes, ERC20Permit, Ownable, IJBToken {
|
|
|
47
90
|
//*********************************************************************//
|
|
48
91
|
|
|
49
92
|
/// @notice Burn some outstanding tokens.
|
|
50
|
-
/// @dev Can only be called by
|
|
93
|
+
/// @dev Can only be called by the JBTokens contract.
|
|
51
94
|
/// @param account The address to burn tokens from.
|
|
52
95
|
/// @param amount The amount of tokens to burn, as a fixed point number with 18 decimals.
|
|
53
|
-
function burn(address account, uint256 amount) external override
|
|
96
|
+
function burn(address account, uint256 amount) external override onlyTokens {
|
|
54
97
|
return _burn({account: account, value: amount});
|
|
55
98
|
}
|
|
56
99
|
|
|
57
100
|
/// @notice Mints more of this token.
|
|
58
|
-
/// @dev Can only be called by
|
|
101
|
+
/// @dev Can only be called by the JBTokens contract.
|
|
59
102
|
/// @param account The address to mint the new tokens to.
|
|
60
103
|
/// @param amount The amount of tokens to mint, as a fixed point number with 18 decimals.
|
|
61
|
-
function mint(address account, uint256 amount) external override
|
|
104
|
+
function mint(address account, uint256 amount) external override onlyTokens {
|
|
62
105
|
return _mint({account: account, value: amount});
|
|
63
106
|
}
|
|
64
107
|
|
|
65
108
|
/// @notice Sets the token's name and symbol.
|
|
66
|
-
/// @dev Can only be called by
|
|
109
|
+
/// @dev Can only be called by the JBTokens contract.
|
|
67
110
|
/// @param name_ The new name.
|
|
68
111
|
/// @param symbol_ The new symbol.
|
|
69
|
-
function setMetadata(string memory name_, string memory symbol_) external override
|
|
70
|
-
_name = name_;
|
|
71
|
-
_symbol = symbol_;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
//*********************************************************************//
|
|
75
|
-
// ----------------------- public transactions ----------------------- //
|
|
76
|
-
//*********************************************************************//
|
|
77
|
-
|
|
78
|
-
/// @notice Initializes the token.
|
|
79
|
-
/// @param name_ The token's name.
|
|
80
|
-
/// @param symbol_ The token's symbol.
|
|
81
|
-
/// @param owner The token contract's owner.
|
|
82
|
-
function initialize(string memory name_, string memory symbol_, address owner) public override {
|
|
83
|
-
// Prevent re-initialization by reverting if a name is already set or if the provided name is empty.
|
|
84
|
-
if (bytes(_name).length != 0 || bytes(name_).length == 0) revert JBERC20_AlreadyInitialized();
|
|
85
|
-
|
|
112
|
+
function setMetadata(string memory name_, string memory symbol_) external override onlyTokens {
|
|
86
113
|
_name = name_;
|
|
87
114
|
_symbol = symbol_;
|
|
88
|
-
|
|
89
|
-
// Transfer ownership to the owner.
|
|
90
|
-
_transferOwnership(owner);
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
//*********************************************************************//
|
|
94
|
-
//
|
|
118
|
+
// ----------------------- external views ---------------------------- //
|
|
95
119
|
//*********************************************************************//
|
|
96
120
|
|
|
97
121
|
/// @notice This token can only be added to a project when its created by the `JBTokens` contract.
|
|
@@ -99,6 +123,35 @@ contract JBERC20 is ERC20Votes, ERC20Permit, Ownable, IJBToken {
|
|
|
99
123
|
return false;
|
|
100
124
|
}
|
|
101
125
|
|
|
126
|
+
/// @notice Validates a signature on behalf of this token contract (ERC-1271).
|
|
127
|
+
/// @dev Allows the project owner or an operator with `SIGN_FOR_ERC20` permission to sign messages on behalf of
|
|
128
|
+
/// this token. Useful for Etherscan contract verification and other off-chain signature flows.
|
|
129
|
+
/// @param hash The hash of the data being signed.
|
|
130
|
+
/// @param signature The signature to validate.
|
|
131
|
+
/// @return magicValue `0x1626ba7e` if the signature is valid, `0xffffffff` otherwise.
|
|
132
|
+
function isValidSignature(bytes32 hash, bytes memory signature) external view override returns (bytes4 magicValue) {
|
|
133
|
+
// Recover the signer from the signature. Return invalid if recovery fails.
|
|
134
|
+
// slither-disable-next-line unused-return
|
|
135
|
+
(address signer, ECDSA.RecoverError error,) = ECDSA.tryRecover(hash, signature);
|
|
136
|
+
if (error != ECDSA.RecoverError.NoError) return 0xffffffff;
|
|
137
|
+
|
|
138
|
+
// Get the project ID this token belongs to.
|
|
139
|
+
uint256 projectId = TOKENS.projectIdOf(IJBToken(address(this)));
|
|
140
|
+
|
|
141
|
+
// Get the project owner (the NFT holder).
|
|
142
|
+
address projectOwner = PROJECTS.ownerOf(projectId);
|
|
143
|
+
|
|
144
|
+
// Valid if the signer is the project owner or has the SIGN_FOR_ERC20 permission.
|
|
145
|
+
if (_hasPermissionFrom({
|
|
146
|
+
operator: signer,
|
|
147
|
+
account: projectOwner,
|
|
148
|
+
projectId: projectId,
|
|
149
|
+
permissionId: JBPermissionIds.SIGN_FOR_ERC20
|
|
150
|
+
})) return IERC1271.isValidSignature.selector;
|
|
151
|
+
|
|
152
|
+
return 0xffffffff;
|
|
153
|
+
}
|
|
154
|
+
|
|
102
155
|
//*********************************************************************//
|
|
103
156
|
// -------------------------- public views --------------------------- //
|
|
104
157
|
//*********************************************************************//
|
|
@@ -137,6 +190,23 @@ contract JBERC20 is ERC20Votes, ERC20Permit, Ownable, IJBToken {
|
|
|
137
190
|
return super.totalSupply();
|
|
138
191
|
}
|
|
139
192
|
|
|
193
|
+
//*********************************************************************//
|
|
194
|
+
// ----------------------- public transactions ----------------------- //
|
|
195
|
+
//*********************************************************************//
|
|
196
|
+
|
|
197
|
+
/// @notice Initializes the token.
|
|
198
|
+
/// @param name_ The token's name.
|
|
199
|
+
/// @param symbol_ The token's symbol.
|
|
200
|
+
/// @param tokens The JBTokens contract that manages this token.
|
|
201
|
+
function initialize(string memory name_, string memory symbol_, address tokens) public override {
|
|
202
|
+
// Prevent re-initialization by reverting if a name is already set or if the provided name is empty.
|
|
203
|
+
if (bytes(_name).length != 0 || bytes(name_).length == 0) revert JBERC20_AlreadyInitialized();
|
|
204
|
+
|
|
205
|
+
_name = name_;
|
|
206
|
+
_symbol = symbol_;
|
|
207
|
+
TOKENS = IJBTokens(tokens);
|
|
208
|
+
}
|
|
209
|
+
|
|
140
210
|
//*********************************************************************//
|
|
141
211
|
// ---------------------- internal transactions ---------------------- //
|
|
142
212
|
//*********************************************************************//
|
package/src/JBTerminalStore.sol
CHANGED
|
@@ -599,6 +599,8 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
599
599
|
JBRuleset memory ruleset = RULESETS.currentOf(projectId);
|
|
600
600
|
|
|
601
601
|
// Return the amount of surplus terminal tokens that would be reclaimed.
|
|
602
|
+
// NOTE: This view does not run the data hook, so it cannot reflect a cross-chain totalSupply override.
|
|
603
|
+
// For accurate omnichain estimates, use the data hook or simulate recordCashOutFor.
|
|
602
604
|
return JBCashOuts.cashOutFrom({
|
|
603
605
|
surplus: surplus,
|
|
604
606
|
cashOutCount: cashOutCount,
|
|
@@ -814,6 +816,8 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
814
816
|
if (cashOutCount > totalSupply) return 0;
|
|
815
817
|
|
|
816
818
|
// Return the amount of surplus terminal tokens that would be reclaimed.
|
|
819
|
+
// NOTE: This view does not run the data hook, so it cannot reflect a cross-chain totalSupply override.
|
|
820
|
+
// For accurate omnichain estimates, use the data hook or simulate recordCashOutFor.
|
|
817
821
|
return JBCashOuts.cashOutFrom({
|
|
818
822
|
surplus: currentSurplus,
|
|
819
823
|
cashOutCount: cashOutCount,
|
|
@@ -877,6 +881,54 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
877
881
|
});
|
|
878
882
|
}
|
|
879
883
|
|
|
884
|
+
/// @notice Calls the data hook, validates noop specifications, and computes the bonding curve reclaim amount.
|
|
885
|
+
/// @dev Extracted from `_computeCashOutFrom` to keep it under the EVM stack depth limit (16 slots).
|
|
886
|
+
/// @param ruleset The current ruleset (used to resolve the data hook address).
|
|
887
|
+
/// @param context The fully-populated cash out context to forward to the data hook.
|
|
888
|
+
/// @param surplus The locally available surplus (used as a cap — can't reclaim more than what's on-chain here).
|
|
889
|
+
/// @return reclaimAmount The amount of surplus tokens reclaimable after the bonding curve and cap.
|
|
890
|
+
/// @return cashOutTaxRate The cash out tax rate returned by the data hook.
|
|
891
|
+
/// @return hookSpecifications The hook specifications returned by the data hook.
|
|
892
|
+
function _cashOutWithDataHook(
|
|
893
|
+
JBRuleset memory ruleset,
|
|
894
|
+
JBBeforeCashOutRecordedContext memory context,
|
|
895
|
+
uint256 surplus
|
|
896
|
+
)
|
|
897
|
+
internal
|
|
898
|
+
view
|
|
899
|
+
returns (uint256 reclaimAmount, uint256 cashOutTaxRate, JBCashOutHookSpecification[] memory hookSpecifications)
|
|
900
|
+
{
|
|
901
|
+
// Ask the data hook for the effective bonding curve parameters and any hook specifications.
|
|
902
|
+
uint256 effectiveCashOutCount;
|
|
903
|
+
uint256 effectiveTotalSupply;
|
|
904
|
+
uint256 effectiveSurplusValue;
|
|
905
|
+
(cashOutTaxRate, effectiveCashOutCount, effectiveTotalSupply, effectiveSurplusValue, hookSpecifications) =
|
|
906
|
+
IJBRulesetDataHook(ruleset.dataHook()).beforeCashOutRecordedWith(context);
|
|
907
|
+
|
|
908
|
+
// Noop specifications are informational only, so they can't also request forwarded funds.
|
|
909
|
+
for (uint256 i; i < hookSpecifications.length;) {
|
|
910
|
+
if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
|
|
911
|
+
revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
|
|
912
|
+
}
|
|
913
|
+
unchecked {
|
|
914
|
+
++i;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Apply the bonding curve to calculate how much of the surplus is reclaimable.
|
|
919
|
+
if (surplus != 0) {
|
|
920
|
+
reclaimAmount = JBCashOuts.cashOutFrom({
|
|
921
|
+
surplus: effectiveSurplusValue,
|
|
922
|
+
cashOutCount: effectiveCashOutCount,
|
|
923
|
+
totalSupply: effectiveTotalSupply,
|
|
924
|
+
cashOutTaxRate: cashOutTaxRate
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
// Cap at local surplus — can't reclaim more than what's locally available.
|
|
928
|
+
if (reclaimAmount > surplus) reclaimAmount = surplus;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
880
932
|
/// @notice Computes cash out results without writing state.
|
|
881
933
|
/// @param terminal The terminal recording the cash out.
|
|
882
934
|
/// @param holder The account that is cashing out tokens.
|
|
@@ -934,8 +986,6 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
934
986
|
// The terminal still burns the caller-supplied cashOutCount after pricing completes.
|
|
935
987
|
// Project owners MUST audit their data hooks with the same rigor as the terminal.
|
|
936
988
|
|
|
937
|
-
uint256 effectiveCashOutCount = cashOutCount;
|
|
938
|
-
|
|
939
989
|
// If the ruleset has a data hook which is enabled for cash outs, use it to derive a claim amount and memo.
|
|
940
990
|
if (ruleset.useDataHookForCashOut() && ruleset.dataHook() != address(0)) {
|
|
941
991
|
// Build the cash out context field-by-field — the struct has 11 fields, too many for a literal.
|
|
@@ -957,30 +1007,21 @@ contract JBTerminalStore is IJBTerminalStore {
|
|
|
957
1007
|
context.beneficiaryIsFeeless = beneficiaryIsFeeless;
|
|
958
1008
|
context.metadata = metadata;
|
|
959
1009
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
// Noop specifications are informational only, so they can't also request forwarded funds.
|
|
964
|
-
for (uint256 i; i < hookSpecifications.length;) {
|
|
965
|
-
if (hookSpecifications[i].noop && hookSpecifications[i].amount != 0) {
|
|
966
|
-
revert JBTerminalStore_NoopHookSpecHasAmount(hookSpecifications[i].amount);
|
|
967
|
-
}
|
|
968
|
-
unchecked {
|
|
969
|
-
++i;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
1010
|
+
// Hook call + bonding curve in a helper to stay under the stack depth limit.
|
|
1011
|
+
(reclaimAmount, cashOutTaxRate, hookSpecifications) =
|
|
1012
|
+
_cashOutWithDataHook({ruleset: ruleset, context: context, surplus: surplus});
|
|
972
1013
|
} else {
|
|
973
1014
|
cashOutTaxRate = ruleset.cashOutTaxRate();
|
|
974
|
-
}
|
|
975
1015
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1016
|
+
// Apply the bonding curve to calculate how much of the surplus is reclaimable.
|
|
1017
|
+
if (surplus != 0) {
|
|
1018
|
+
reclaimAmount = JBCashOuts.cashOutFrom({
|
|
1019
|
+
surplus: surplus,
|
|
1020
|
+
cashOutCount: cashOutCount,
|
|
1021
|
+
totalSupply: effectiveTotalSupply,
|
|
1022
|
+
cashOutTaxRate: cashOutTaxRate
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
984
1025
|
}
|
|
985
1026
|
}
|
|
986
1027
|
|
package/src/JBTokens.sol
CHANGED
|
@@ -228,7 +228,7 @@ contract JBTokens is JBControlled, IJBTokens {
|
|
|
228
228
|
});
|
|
229
229
|
|
|
230
230
|
// Initialize the token.
|
|
231
|
-
token.initialize({name: name, symbol: symbol,
|
|
231
|
+
token.initialize({name: name, symbol: symbol, tokens: address(this)});
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
/// @notice Mint (create) new tokens or credits.
|