@bananapus/core-v6 0.0.10 → 0.0.12
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 +327 -0
- package/ARCHITECTURE.md +115 -0
- package/RISKS.md +68 -0
- package/SKILLS.md +5 -0
- package/STYLE_GUIDE.md +465 -0
- package/foundry.toml +1 -1
- package/package.json +2 -2
- package/test/formal/BondingCurveProperties.t.sol +7 -4
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Administration
|
|
2
|
+
|
|
3
|
+
Admin privileges and their scope in nana-core-v6.
|
|
4
|
+
|
|
5
|
+
## Roles
|
|
6
|
+
|
|
7
|
+
### Project Owner
|
|
8
|
+
|
|
9
|
+
- **How assigned:** Holds the ERC-721 NFT minted by `JBProjects.createFor()`. Transferable via standard ERC-721 transfer.
|
|
10
|
+
- **Scope:** Controls a single project. The owner's address is `PROJECTS.ownerOf(projectId)`. Operators can be granted subsets of the owner's permissions via `JBPermissions`.
|
|
11
|
+
|
|
12
|
+
### Controller
|
|
13
|
+
|
|
14
|
+
- **How assigned:** Set via `JBDirectory.setControllerOf()`. The controller is stored as `controllerOf[projectId]` in JBDirectory. Typically a `JBController` instance.
|
|
15
|
+
- **Scope:** Manages a single project's rulesets, tokens, splits, and fund access limits. Contracts gated by `onlyControllerOf(projectId)` (from `JBControlled`) only accept calls from the address registered as the project's controller in the directory.
|
|
16
|
+
|
|
17
|
+
### Terminal
|
|
18
|
+
|
|
19
|
+
- **How assigned:** Set via `JBDirectory.setTerminalsOf()`. Stored in the directory's `_terminalsOf[projectId]` array.
|
|
20
|
+
- **Scope:** Manages fund inflows and outflows for a single project. Terminal identity is verified via `DIRECTORY.isTerminalOf()`. Terminals interact with `JBTerminalStore` using `msg.sender`-scoped bookkeeping -- each terminal can only modify its own balances.
|
|
21
|
+
|
|
22
|
+
### Operator
|
|
23
|
+
|
|
24
|
+
- **How assigned:** Granted permissions by any address via `JBPermissions.setPermissionsFor()`. Permissions are stored as a packed `uint256` bitmap per `(operator, account, projectId)` tuple.
|
|
25
|
+
- **Scope:** Can execute specific functions on behalf of the account that granted them permissions, scoped to a specific project ID (or all projects if `projectId = 0` wildcard is used).
|
|
26
|
+
|
|
27
|
+
### ROOT Permission Holder
|
|
28
|
+
|
|
29
|
+
- **How assigned:** Granted the ROOT permission (ID 1) by an account via `JBPermissions`. ROOT on a specific project grants all permissions for that project. ROOT cannot be granted on the wildcard project ID (0) to prevent unlimited cross-project access.
|
|
30
|
+
- **Scope:** ROOT holders for a project can do anything the project owner can do for that project. ROOT holders can also grant non-ROOT permissions to other operators for the same project but cannot grant ROOT to others or set wildcard permissions.
|
|
31
|
+
|
|
32
|
+
### Directory Owner
|
|
33
|
+
|
|
34
|
+
- **How assigned:** Set at JBDirectory deployment via the Ownable constructor. Transferable via `Ownable.transferOwnership()`.
|
|
35
|
+
- **Scope:** Protocol-wide. Controls which addresses are allowed to set a project's first controller (`isAllowedToSetFirstController`).
|
|
36
|
+
|
|
37
|
+
### JBProjects Owner
|
|
38
|
+
|
|
39
|
+
- **How assigned:** Set at JBProjects deployment via the Ownable constructor. Transferable via `Ownable.transferOwnership()`.
|
|
40
|
+
- **Scope:** Protocol-wide. Can set the `tokenUriResolver` that resolves project NFT metadata URIs.
|
|
41
|
+
|
|
42
|
+
### JBPrices Owner
|
|
43
|
+
|
|
44
|
+
- **How assigned:** Set at JBPrices deployment via the Ownable constructor. Transferable via `Ownable.transferOwnership()`.
|
|
45
|
+
- **Scope:** Protocol-wide. Can add default price feeds (project ID 0) that apply to all projects as fallback.
|
|
46
|
+
|
|
47
|
+
### JBFeelessAddresses Owner
|
|
48
|
+
|
|
49
|
+
- **How assigned:** Set at JBFeelessAddresses deployment via the Ownable constructor. Transferable via `Ownable.transferOwnership()`.
|
|
50
|
+
- **Scope:** Protocol-wide. Controls which addresses are exempt from protocol fees.
|
|
51
|
+
|
|
52
|
+
### JBERC20 Owner
|
|
53
|
+
|
|
54
|
+
- **How assigned:** Set to the `JBTokens` contract address when a project's ERC-20 is deployed or initialized.
|
|
55
|
+
- **Scope:** Single token contract. Only the owner (JBTokens) can mint and burn tokens.
|
|
56
|
+
|
|
57
|
+
### Omnichain Ruleset Operator
|
|
58
|
+
|
|
59
|
+
- **How assigned:** Immutable address set in the `JBController` constructor as `OMNICHAIN_RULESET_OPERATOR`.
|
|
60
|
+
- **Scope:** Can call `launchRulesetsFor` and `queueRulesetsOf` on any project, bypassing owner permission checks. This allows the omnichain deployer to synchronize rulesets across chains.
|
|
61
|
+
|
|
62
|
+
### Data Hook
|
|
63
|
+
|
|
64
|
+
- **How assigned:** Specified in a ruleset's metadata as the `dataHook` address. Active only during the ruleset it is configured for.
|
|
65
|
+
- **Scope:** Can mint tokens via `JBController.mintTokensOf()` when the data hook reports `hasMintPermissionFor()` returns true. Also controls pay/cashout hook specifications returned from `JBTerminalStore`.
|
|
66
|
+
|
|
67
|
+
### Fee Beneficiary (Project ID 1)
|
|
68
|
+
|
|
69
|
+
- **How assigned:** Hardcoded as `_FEE_BENEFICIARY_PROJECT_ID = 1` in JBMultiTerminal.
|
|
70
|
+
- **Scope:** Receives all protocol fees (2.5%) from payouts, surplus allowance usage, and cash outs with non-zero tax rates. This is the first project created during deployment.
|
|
71
|
+
|
|
72
|
+
## Privileged Functions
|
|
73
|
+
|
|
74
|
+
### JBPermissions
|
|
75
|
+
|
|
76
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
77
|
+
|----------|--------------|---------------|-------|-------------|
|
|
78
|
+
| `setPermissionsFor` | Account owner, or ROOT operator for that account+project | ROOT (1) | Per account + project | Sets the permission bitmap for an operator. ROOT operators can set non-ROOT permissions for the same project but cannot grant ROOT or set wildcard project permissions. |
|
|
79
|
+
|
|
80
|
+
### JBProjects
|
|
81
|
+
|
|
82
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
83
|
+
|----------|--------------|---------------|-------|-------------|
|
|
84
|
+
| `setTokenUriResolver` | Contract owner | N/A (onlyOwner) | Protocol-wide | Sets the contract that resolves token URIs for all project NFTs. |
|
|
85
|
+
| `createFor` | Anyone | N/A | N/A | Creates a new project NFT. No permission required -- anyone can create a project for any owner. |
|
|
86
|
+
|
|
87
|
+
### JBDirectory
|
|
88
|
+
|
|
89
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
90
|
+
|----------|--------------|---------------|-------|-------------|
|
|
91
|
+
| `setControllerOf` | Project owner or operator, OR an `isAllowedToSetFirstController` address (for first controller only) | SET_CONTROLLER (14) | Per project | Sets or migrates a project's controller. Also requires the current ruleset's `allowSetController` flag to be true (unless setting the first controller). Triggers migration lifecycle hooks on both old and new controllers. |
|
|
92
|
+
| `setIsAllowedToSetFirstController` | Contract owner | N/A (onlyOwner) | Protocol-wide | Adds or removes an address from the allowlist of addresses that can set a project's first controller. |
|
|
93
|
+
| `setPrimaryTerminalOf` | Project owner or operator | SET_PRIMARY_TERMINAL (16) | Per project | Sets which terminal is the default for a given token. Adds the terminal to the project if not already present. |
|
|
94
|
+
| `setTerminalsOf` | Project owner, operator, or the project's controller | SET_TERMINALS (15) | Per project | Replaces the entire list of terminals for a project. If the caller is not the controller, the ruleset must have `allowSetTerminals` enabled. |
|
|
95
|
+
|
|
96
|
+
### JBController
|
|
97
|
+
|
|
98
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
99
|
+
|----------|--------------|---------------|-------|-------------|
|
|
100
|
+
| `addPriceFeed` | Project owner or operator | ADD_PRICE_FEED (19) | Per project | Adds a price feed for a project. Requires the ruleset's `allowAddPriceFeed` flag. Price feeds are immutable once set. |
|
|
101
|
+
| `burnTokensOf` | Token holder, operator with BURN_TOKENS, or a project terminal | BURN_TOKENS (11) | Per project | Burns tokens or credits from a holder's balance. Terminals can burn without explicit permission (for cash outs). |
|
|
102
|
+
| `claimTokensFor` | Credit holder or operator | CLAIM_TOKENS (12) | Per project | Redeems internal credits for ERC-20 tokens. |
|
|
103
|
+
| `deployERC20For` | Project owner or operator | DEPLOY_ERC20 (8) | Per project | Deploys a new ERC-20 token contract for the project. Can only be called once per project. |
|
|
104
|
+
| `launchProjectFor` | Anyone | N/A | N/A | Creates a new project, sets this controller, configures terminals, and queues initial rulesets. No permission required. |
|
|
105
|
+
| `launchRulesetsFor` | Project owner, operator, or OMNICHAIN_RULESET_OPERATOR | LAUNCH_RULESETS (3) + SET_TERMINALS (15) | Per project | Queues initial rulesets and configures terminals for an existing project that has no rulesets yet. Also sets this contract as the project's controller. |
|
|
106
|
+
| `mintTokensOf` | Project owner, operator, terminal, or data hook (with mint permission) | MINT_TOKENS (10) | Per project | Mints new tokens. If the caller is not a terminal or data hook, the ruleset must have `allowOwnerMinting` enabled. |
|
|
107
|
+
| `queueRulesetsOf` | Project owner, operator, or OMNICHAIN_RULESET_OPERATOR | QUEUE_RULESETS (2) | Per project | Queues new rulesets at the end of the project's ruleset queue. |
|
|
108
|
+
| `sendReservedTokensToSplitsOf` | Anyone | N/A | Per project | Distributes accumulated reserved tokens to the project's reserved token split group. No permission required -- anyone can trigger this. |
|
|
109
|
+
| `setSplitGroupsOf` | Project owner or operator | SET_SPLIT_GROUPS (18) | Per project | Sets split groups for a project. Must preserve any currently locked splits. |
|
|
110
|
+
| `setTokenFor` | Project owner or operator | SET_TOKEN (9) | Per project | Assigns an external ERC-20 token to the project. Requires the ruleset's `allowSetCustomToken` flag. Can only be called once (before any token is set). |
|
|
111
|
+
| `setUriOf` | Project owner or operator | SET_PROJECT_URI (7) | Per project | Updates the project's metadata URI. |
|
|
112
|
+
| `transferCreditsFrom` | Credit holder or operator | TRANSFER_CREDITS (13) | Per project | Transfers internal token credits between addresses. Requires the ruleset's `pauseCreditTransfers` flag to be false. |
|
|
113
|
+
| `migrate` | JBDirectory only | N/A (msg.sender == DIRECTORY) | Per project | Called by the directory during controller migration. Reverts if there are pending reserved tokens. |
|
|
114
|
+
| `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. |
|
|
115
|
+
| `afterReceiveMigrationFrom` | JBDirectory only | N/A (msg.sender == DIRECTORY) | Per project | Called after migration completes. Currently a no-op. |
|
|
116
|
+
| `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. |
|
|
117
|
+
|
|
118
|
+
### JBMultiTerminal
|
|
119
|
+
|
|
120
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
121
|
+
|----------|--------------|---------------|-------|-------------|
|
|
122
|
+
| `addAccountingContextsFor` | Project owner, operator, or the project's controller | ADD_ACCOUNTING_CONTEXTS (20) | Per project | Adds tokens that the terminal will accept for a project. Requires the ruleset's `allowAddAccountingContext` flag (if a ruleset exists). |
|
|
123
|
+
| `cashOutTokensOf` | Token holder or operator | CASH_OUT_TOKENS (4) | Per project | Cashes out project tokens for a share of the project's surplus. Fees are charged unless the beneficiary is feeless or the cash out tax rate is zero. |
|
|
124
|
+
| `migrateBalanceOf` | Project owner or operator | MIGRATE_TERMINAL (6) | Per project | Migrates a project's balance from this terminal to another. The destination terminal must accept the same token. The ruleset must have `allowTerminalMigration` enabled (checked in JBTerminalStore). |
|
|
125
|
+
| `sendPayoutsOf` | Anyone (unless `ownerMustSendPayouts` is set) | SEND_PAYOUTS (5) if `ownerMustSendPayouts` | Per project | Sends payouts to the project's payout split group up to the payout limit. Anyone can call unless the ruleset has `ownerMustSendPayouts` enabled, which requires the project owner or an operator with SEND_PAYOUTS permission. |
|
|
126
|
+
| `useAllowanceOf` | Project owner or operator | USE_ALLOWANCE (17) | Per project | Withdraws funds from the project's surplus up to the surplus allowance. Fees are charged unless the owner or beneficiary is feeless. |
|
|
127
|
+
| `pay` | Anyone | N/A | N/A | Pays a project with tokens. No permission required. |
|
|
128
|
+
| `addToBalanceOf` | Anyone | N/A | N/A | Adds funds to a project's balance without minting tokens. Can optionally return held fees. No permission required. |
|
|
129
|
+
| `processHeldFeesOf` | Anyone | N/A | Per project | Processes held fees that have passed their 28-day unlock period. No permission required. |
|
|
130
|
+
| `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. |
|
|
131
|
+
| `executeProcessFee` | Self only | N/A (msg.sender == address(this)) | Internal | Processes a fee payment to the fee beneficiary project. Called internally via try-catch. |
|
|
132
|
+
| `executeTransferTo` | Self only | N/A (msg.sender == address(this)) | Internal | Transfers tokens to an address. Called internally via try-catch during payout leftover distribution. |
|
|
133
|
+
|
|
134
|
+
### JBTokens
|
|
135
|
+
|
|
136
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
137
|
+
|----------|--------------|---------------|-------|-------------|
|
|
138
|
+
| `burnFrom` | Project's controller | N/A (onlyControllerOf) | Per project | Burns tokens and/or credits from a holder. Credits are burned first, then ERC-20 tokens. |
|
|
139
|
+
| `claimTokensFor` | Project's controller | N/A (onlyControllerOf) | Per project | Converts credits to ERC-20 tokens for a holder. |
|
|
140
|
+
| `deployERC20For` | Project's controller | N/A (onlyControllerOf) | Per project | Deploys a cloned JBERC20 token for the project. |
|
|
141
|
+
| `mintFor` | Project's controller | N/A (onlyControllerOf) | Per project | Mints new tokens (ERC-20 if deployed) or credits for a holder. |
|
|
142
|
+
| `setTokenFor` | Project's controller | N/A (onlyControllerOf) | Per project | Sets an external ERC-20 token for the project. Cannot be changed once set. |
|
|
143
|
+
| `transferCreditsFrom` | Project's controller | N/A (onlyControllerOf) | Per project | Transfers credits between addresses. |
|
|
144
|
+
|
|
145
|
+
### JBSplits
|
|
146
|
+
|
|
147
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
148
|
+
|----------|--------------|---------------|-------|-------------|
|
|
149
|
+
| `setSplitGroupsOf` | Project's controller, OR the address whose first 160 bits match the group ID (for self-namespaced splits) | N/A (onlyControllerOf or msg.sender namespace) | Per project | Sets split groups for a project/ruleset. Must preserve any currently locked splits. Percentage total per group must not exceed 100%. |
|
|
150
|
+
|
|
151
|
+
### JBFundAccessLimits
|
|
152
|
+
|
|
153
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
154
|
+
|----------|--------------|---------------|-------|-------------|
|
|
155
|
+
| `setFundAccessLimitsFor` | Project's controller | N/A (onlyControllerOf) | Per project | Sets payout limits and surplus allowances for a project's ruleset. Limits must be in strictly increasing currency order. |
|
|
156
|
+
|
|
157
|
+
### JBRulesets
|
|
158
|
+
|
|
159
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
160
|
+
|----------|--------------|---------------|-------|-------------|
|
|
161
|
+
| `queueFor` | Project's controller | N/A (onlyControllerOf) | Per project | Queues a new ruleset with specified duration, weight, weight cut percent, approval hook, and metadata. |
|
|
162
|
+
| `updateRulesetWeightCache` | Anyone | N/A | Per project | Updates the cached weight for a ruleset to avoid excessive iteration. No permission required -- anyone can call this maintenance function. |
|
|
163
|
+
|
|
164
|
+
### JBPrices
|
|
165
|
+
|
|
166
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
167
|
+
|----------|--------------|---------------|-------|-------------|
|
|
168
|
+
| `addPriceFeedFor` | Contract owner (for project ID 0 defaults) or project's controller (for project-specific feeds) | N/A (onlyOwner or onlyControllerOf) | Protocol-wide or per project | Adds an immutable price feed for a currency pair. Default feeds (project 0) apply as fallback for all projects. |
|
|
169
|
+
|
|
170
|
+
### JBFeelessAddresses
|
|
171
|
+
|
|
172
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
173
|
+
|----------|--------------|---------------|-------|-------------|
|
|
174
|
+
| `setFeelessAddress` | Contract owner | N/A (onlyOwner) | Protocol-wide | Marks an address as feeless or not. Feeless addresses do not incur the 2.5% protocol fee on payouts, surplus allowance usage, or cash outs. |
|
|
175
|
+
|
|
176
|
+
### JBERC20
|
|
177
|
+
|
|
178
|
+
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
179
|
+
|----------|--------------|---------------|-------|-------------|
|
|
180
|
+
| `mint` | Contract owner (JBTokens) | N/A (onlyOwner) | Per token | Mints new tokens to an address. |
|
|
181
|
+
| `burn` | Contract owner (JBTokens) | N/A (onlyOwner) | Per token | Burns tokens from an address. |
|
|
182
|
+
| `initialize` | Anyone (once) | N/A | Per token | Initializes the token name, symbol, and owner. Can only be called once. |
|
|
183
|
+
|
|
184
|
+
### JBTerminalStore
|
|
185
|
+
|
|
186
|
+
JBTerminalStore has no explicit access control modifiers. Instead, it uses `msg.sender`-scoped storage -- each terminal can only read and write its own balance slots. The functions are designed to be called by terminal contracts:
|
|
187
|
+
|
|
188
|
+
| Function | Implicit Caller | What It Does |
|
|
189
|
+
|----------|----------------|-------------|
|
|
190
|
+
| `recordAddedBalanceFor` | Any address (terminal) | Increments the caller's recorded balance for a project/token. |
|
|
191
|
+
| `recordCashOutFor` | Any address (terminal) | Records a cash out, calculating reclaim amounts via bonding curve. Decrements the caller's balance. |
|
|
192
|
+
| `recordPaymentFrom` | Any address (terminal) | Records a payment, calculating token issuance via weight. Increments the caller's balance. |
|
|
193
|
+
| `recordPayoutFor` | Any address (terminal) | Records a payout against payout limits. Decrements the caller's balance. |
|
|
194
|
+
| `recordTerminalMigration` | Any address (terminal) | Records a full balance migration. Requires the ruleset's `allowTerminalMigration` flag. Zeros out the caller's balance. |
|
|
195
|
+
| `recordUsedAllowanceOf` | Any address (terminal) | Records surplus allowance usage. Decrements the caller's balance. |
|
|
196
|
+
|
|
197
|
+
## Permission System
|
|
198
|
+
|
|
199
|
+
JBPermissions implements a 256-bit packed permission bitmap system:
|
|
200
|
+
|
|
201
|
+
- **256 permission slots**: Each bit in a `uint256` represents one permission. Bit 0 is reserved (cannot be set). Bits 1-255 are available for permission IDs.
|
|
202
|
+
- **ROOT permission (ID 1)**: Acts as a superuser for the granted project. A ROOT operator passes all permission checks for that project via `includeRoot: true`.
|
|
203
|
+
- **Wildcard project ID (0)**: Granting a permission on project ID 0 makes it apply to all projects for that account. ROOT cannot be granted on the wildcard project ID to prevent unlimited cross-project access.
|
|
204
|
+
- **Operator delegation**: Any address can grant any other address specific permissions. The `_requirePermissionFrom` check passes if `msg.sender == account` OR if the sender has the required permission (with ROOT fallback and wildcard fallback).
|
|
205
|
+
- **Override pattern**: `_requirePermissionAllowingOverrideFrom` adds an `alsoGrantAccessIf` boolean that bypasses the permission check entirely when true. This is used to allow terminals, controllers, and data hooks to call privileged functions without explicit operator permissions.
|
|
206
|
+
|
|
207
|
+
### Permission IDs Used in nana-core-v6
|
|
208
|
+
|
|
209
|
+
| ID | Name | Function It Gates |
|
|
210
|
+
|----|------|-------------------|
|
|
211
|
+
| 1 | ROOT | All permissions. Also allows setting non-ROOT permissions for others on the same project. |
|
|
212
|
+
| 2 | QUEUE_RULESETS | `JBController.queueRulesetsOf` |
|
|
213
|
+
| 3 | LAUNCH_RULESETS | `JBController.launchRulesetsFor` |
|
|
214
|
+
| 4 | CASH_OUT_TOKENS | `JBMultiTerminal.cashOutTokensOf` |
|
|
215
|
+
| 5 | SEND_PAYOUTS | `JBMultiTerminal.sendPayoutsOf` (only when `ownerMustSendPayouts` is set) |
|
|
216
|
+
| 6 | MIGRATE_TERMINAL | `JBMultiTerminal.migrateBalanceOf` |
|
|
217
|
+
| 7 | SET_PROJECT_URI | `JBController.setUriOf` |
|
|
218
|
+
| 8 | DEPLOY_ERC20 | `JBController.deployERC20For` |
|
|
219
|
+
| 9 | SET_TOKEN | `JBController.setTokenFor` |
|
|
220
|
+
| 10 | MINT_TOKENS | `JBController.mintTokensOf` |
|
|
221
|
+
| 11 | BURN_TOKENS | `JBController.burnTokensOf` |
|
|
222
|
+
| 12 | CLAIM_TOKENS | `JBController.claimTokensFor` |
|
|
223
|
+
| 13 | TRANSFER_CREDITS | `JBController.transferCreditsFrom` |
|
|
224
|
+
| 14 | SET_CONTROLLER | `JBDirectory.setControllerOf` |
|
|
225
|
+
| 15 | SET_TERMINALS | `JBDirectory.setTerminalsOf` |
|
|
226
|
+
| 16 | SET_PRIMARY_TERMINAL | `JBDirectory.setPrimaryTerminalOf` |
|
|
227
|
+
| 17 | USE_ALLOWANCE | `JBMultiTerminal.useAllowanceOf` |
|
|
228
|
+
| 18 | SET_SPLIT_GROUPS | `JBController.setSplitGroupsOf` |
|
|
229
|
+
| 19 | ADD_PRICE_FEED | `JBController.addPriceFeed` |
|
|
230
|
+
| 20 | ADD_ACCOUNTING_CONTEXTS | `JBMultiTerminal.addAccountingContextsFor` |
|
|
231
|
+
|
|
232
|
+
## Immutable Configuration
|
|
233
|
+
|
|
234
|
+
The following values are set at deploy time and cannot be changed:
|
|
235
|
+
|
|
236
|
+
### JBController
|
|
237
|
+
- `DIRECTORY` -- the directory contract
|
|
238
|
+
- `FUND_ACCESS_LIMITS` -- the fund access limits contract
|
|
239
|
+
- `PERMISSIONS` -- the permissions contract (from JBPermissioned)
|
|
240
|
+
- `PRICES` -- the prices contract
|
|
241
|
+
- `PROJECTS` -- the projects NFT contract
|
|
242
|
+
- `RULESETS` -- the rulesets contract
|
|
243
|
+
- `SPLITS` -- the splits contract
|
|
244
|
+
- `TOKENS` -- the tokens contract
|
|
245
|
+
- `OMNICHAIN_RULESET_OPERATOR` -- the address allowed to launch/queue rulesets for any project
|
|
246
|
+
|
|
247
|
+
### JBMultiTerminal
|
|
248
|
+
- `DIRECTORY` -- derived from STORE.DIRECTORY()
|
|
249
|
+
- `FEELESS_ADDRESSES` -- the feeless addresses registry
|
|
250
|
+
- `FEE` -- hardcoded at 25 (2.5% of MAX_FEE=1000)
|
|
251
|
+
- `PERMISSIONS` -- the permissions contract
|
|
252
|
+
- `PERMIT2` -- the Uniswap Permit2 contract
|
|
253
|
+
- `PROJECTS` -- the projects NFT contract
|
|
254
|
+
- `RULESETS` -- derived from STORE.RULESETS()
|
|
255
|
+
- `SPLITS` -- the splits contract
|
|
256
|
+
- `STORE` -- the terminal store contract
|
|
257
|
+
- `TOKENS` -- the tokens contract
|
|
258
|
+
- `_FEE_BENEFICIARY_PROJECT_ID` -- hardcoded as 1
|
|
259
|
+
- `_FEE_HOLDING_SECONDS` -- hardcoded as 2,419,200 (28 days)
|
|
260
|
+
|
|
261
|
+
### JBDirectory
|
|
262
|
+
- `PERMISSIONS` -- the permissions contract
|
|
263
|
+
- `PROJECTS` -- the projects NFT contract
|
|
264
|
+
|
|
265
|
+
### JBPrices
|
|
266
|
+
- `DIRECTORY` -- the directory contract
|
|
267
|
+
- `PERMISSIONS` -- the permissions contract
|
|
268
|
+
- `PROJECTS` -- the projects NFT contract
|
|
269
|
+
- Price feeds are immutable once set (cannot be replaced or removed)
|
|
270
|
+
|
|
271
|
+
### JBTokens
|
|
272
|
+
- `DIRECTORY` -- the directory contract
|
|
273
|
+
- `TOKEN` -- the JBERC20 implementation used for cloning
|
|
274
|
+
- Project token assignments are immutable once set (cannot be changed to a different token)
|
|
275
|
+
|
|
276
|
+
### JBRulesets
|
|
277
|
+
- `DIRECTORY` -- the directory contract
|
|
278
|
+
|
|
279
|
+
### JBSplits
|
|
280
|
+
- `DIRECTORY` -- the directory contract
|
|
281
|
+
|
|
282
|
+
### JBFundAccessLimits
|
|
283
|
+
- `DIRECTORY` -- the directory contract
|
|
284
|
+
|
|
285
|
+
### JBTerminalStore
|
|
286
|
+
- `DIRECTORY` -- the directory contract
|
|
287
|
+
- `PRICES` -- the prices contract
|
|
288
|
+
- `RULESETS` -- the rulesets contract
|
|
289
|
+
|
|
290
|
+
### JBPermissions
|
|
291
|
+
- Trusted forwarder for ERC-2771 meta-transactions
|
|
292
|
+
|
|
293
|
+
## Ruleset Flags That Gate Admin Actions
|
|
294
|
+
|
|
295
|
+
Several admin functions are further gated by boolean flags in the active ruleset's metadata. These flags are set when the ruleset is queued and cannot be changed until a new ruleset takes effect:
|
|
296
|
+
|
|
297
|
+
| Flag | What It Gates |
|
|
298
|
+
|------|--------------|
|
|
299
|
+
| `allowOwnerMinting` | Whether the project owner/operator can call `mintTokensOf`. Terminals and data hooks can always mint regardless. |
|
|
300
|
+
| `allowSetCustomToken` | Whether `setTokenFor` can be called. |
|
|
301
|
+
| `allowTerminalMigration` | Whether `migrateBalanceOf` / `recordTerminalMigration` can proceed. |
|
|
302
|
+
| `allowSetTerminals` | Whether `setTerminalsOf` can be called by non-controller callers. |
|
|
303
|
+
| `allowSetController` | Whether `setControllerOf` can be called (checked via `IJBDirectoryAccessControl`). |
|
|
304
|
+
| `allowAddAccountingContext` | Whether `addAccountingContextsFor` can add new token contexts. |
|
|
305
|
+
| `allowAddPriceFeed` | Whether `addPriceFeed` can add new price feeds. |
|
|
306
|
+
| `ownerMustSendPayouts` | Whether `sendPayoutsOf` requires SEND_PAYOUTS permission (otherwise anyone can call it). |
|
|
307
|
+
| `pausePay` | Whether payments are paused (checked in JBTerminalStore). |
|
|
308
|
+
| `pauseCreditTransfers` | Whether credit transfers are paused. |
|
|
309
|
+
| `holdFees` | Whether fees are held (deferred for 28 days) instead of being processed immediately. |
|
|
310
|
+
|
|
311
|
+
## Admin Boundaries
|
|
312
|
+
|
|
313
|
+
What admins CANNOT do:
|
|
314
|
+
|
|
315
|
+
- **Project owners cannot access other projects' funds.** All permission checks are scoped to a specific `projectId`. A project owner's permissions only apply to their own project.
|
|
316
|
+
- **Controllers cannot bypass terminal accounting.** JBTerminalStore uses `msg.sender`-scoped balances. A controller cannot directly manipulate a terminal's recorded balances -- only the terminal itself can update its own balance slots.
|
|
317
|
+
- **Terminals cannot mint tokens without controller involvement.** Token minting goes through the controller's `mintTokensOf`, which enforces ruleset rules (reserved percent, `allowOwnerMinting`).
|
|
318
|
+
- **No single admin can change the fee rate.** The fee (2.5%) is hardcoded as a constant in JBMultiTerminal. Changing it requires deploying a new terminal contract.
|
|
319
|
+
- **No admin can change the fee beneficiary.** Project ID 1 is hardcoded as the fee recipient. This cannot be changed.
|
|
320
|
+
- **Price feeds cannot be replaced.** Once a price feed is set for a currency pair (in either direction), it is permanent for that project ID. Recovery requires deploying a new JBPrices contract.
|
|
321
|
+
- **Token assignments are one-way.** Once a project's ERC-20 token is set (via `deployERC20For` or `setTokenFor`), it cannot be changed to a different token.
|
|
322
|
+
- **Locked splits cannot be removed.** Splits with a `lockedUntil` timestamp in the future must be preserved (with the same or extended lock) when updating split groups.
|
|
323
|
+
- **Operators cannot escalate to ROOT via ROOT.** A ROOT operator can set permissions for other operators on the same project but cannot grant ROOT to them or set permissions on the wildcard project ID.
|
|
324
|
+
- **The directory owner cannot set controllers directly.** The directory owner can only manage the `isAllowedToSetFirstController` allowlist. They cannot set or change a project's controller.
|
|
325
|
+
- **Ruleset approval hooks can block changes.** If a ruleset specifies an approval hook, queued rulesets must be approved by that hook before they take effect. A rejected ruleset falls back to the previous approved ruleset's cycling behavior.
|
|
326
|
+
- **Reserved tokens must be distributed before controller migration.** The `migrate` function reverts if `pendingReservedTokenBalanceOf[projectId] > 0`, preventing loss of reserved tokens during migration.
|
|
327
|
+
- **The JBTerminalStore has no admin functions.** It has no owner, no permission checks, and no special roles. Access is controlled entirely by the msg.sender-scoped storage pattern -- callers can only affect their own balance slots.
|
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# nana-core-v6 — Architecture
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Core protocol for Juicebox V6. Provides programmable treasuries with configurable governance, bonding-curve cash outs, split-based payouts, and a compositional hook system.
|
|
6
|
+
|
|
7
|
+
## Contract Map
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── JBMultiTerminal.sol — Multi-token payment terminal (pay, cash out, payouts, fees)
|
|
12
|
+
├── JBController.sol — Orchestrator (project lifecycle, rulesets, token minting, reserved tokens)
|
|
13
|
+
├── JBTerminalStore.sol — Bookkeeping (balances, payout limits, surplus, bonding curve math)
|
|
14
|
+
├── JBRulesets.sol — Ruleset lifecycle (linked-list, weight decay, approval hooks)
|
|
15
|
+
├── JBDirectory.sol — Routes projects to terminals and controllers
|
|
16
|
+
├── JBTokens.sol — Dual token system (internal credits + ERC-20)
|
|
17
|
+
├── JBSplits.sol — Packed split storage with lock enforcement
|
|
18
|
+
├── JBFundAccessLimits.sol — Payout limits and surplus allowances
|
|
19
|
+
├── JBPrices.sol — Price feeds with project-specific + default fallback
|
|
20
|
+
├── JBPermissions.sol — 256-bit packed permission system
|
|
21
|
+
├── JBProjects.sol — ERC-721 project ownership
|
|
22
|
+
├── JBERC20.sol — Cloneable ERC20Votes+Permit token
|
|
23
|
+
├── JBFeelessAddresses.sol — Fee-exempt address registry
|
|
24
|
+
├── JBDeadline.sol — Approval hook requiring minimum delay
|
|
25
|
+
├── JBChainlinkV3PriceFeed.sol — Chainlink v3 price feed with staleness check
|
|
26
|
+
├── JBChainlinkV3SequencerPriceFeed.sol — L2 sequencer-aware price feed
|
|
27
|
+
├── abstract/
|
|
28
|
+
│ ├── JBPermissioned.sol — Base for permission-checked contracts
|
|
29
|
+
│ └── JBControlled.sol — Base for controller-gated contracts
|
|
30
|
+
└── libraries/
|
|
31
|
+
├── JBCashOuts.sol — Bonding curve math
|
|
32
|
+
├── JBFees.sol — Fee calculation (forward/backward)
|
|
33
|
+
├── JBRulesetMetadataResolver.sol — Bit-packed metadata (256 bits)
|
|
34
|
+
├── JBMetadataResolver.sol — Variable-length key-value metadata
|
|
35
|
+
├── JBFixedPointNumber.sol — Decimal adjustment
|
|
36
|
+
├── JBConstants.sol — Protocol constants
|
|
37
|
+
└── JBSplitGroupIds.sol — Split group ID constants
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Key Data Flows
|
|
41
|
+
|
|
42
|
+
### Payment Flow
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
User -> JBMultiTerminal.pay()
|
|
46
|
+
-> JBTerminalStore.recordPaymentFrom()
|
|
47
|
+
-> Read current ruleset
|
|
48
|
+
-> [Optional] Data hook overrides weight
|
|
49
|
+
-> Calculate token count from weight
|
|
50
|
+
-> Update balance
|
|
51
|
+
-> JBController.mintTokensOf()
|
|
52
|
+
-> Calculate reserved tokens
|
|
53
|
+
-> Mint beneficiary tokens
|
|
54
|
+
-> Accumulate pendingReservedTokenBalanceOf
|
|
55
|
+
-> [Optional] Pay hooks execute
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Cash Out Flow
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Holder -> JBMultiTerminal.cashOutTokensOf()
|
|
62
|
+
-> JBTerminalStore.recordCashOutFor()
|
|
63
|
+
-> Calculate surplus (all terminals, converted via JBPrices)
|
|
64
|
+
-> Get totalSupply (including pending reserved)
|
|
65
|
+
-> [Optional] Data hook overrides parameters
|
|
66
|
+
-> JBCashOuts.cashOutFrom() — bonding curve
|
|
67
|
+
-> Deduct balance
|
|
68
|
+
-> JBController.burnTokensOf()
|
|
69
|
+
-> Transfer reclaimed tokens to beneficiary
|
|
70
|
+
-> [Optional] Cash out hooks execute
|
|
71
|
+
-> Take fees (2.5% to project #1)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Payout Flow
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Owner -> JBMultiTerminal.sendPayoutsOf()
|
|
78
|
+
-> JBTerminalStore.recordPayoutFor()
|
|
79
|
+
-> Deduct balance, check payout limits
|
|
80
|
+
-> Distribute to splits (JBSplits)
|
|
81
|
+
-> Split to project -> pay project's terminal
|
|
82
|
+
-> Split to address -> direct transfer
|
|
83
|
+
-> Split to hook -> IJBSplitHook.processSplitWith()
|
|
84
|
+
-> Take fees on non-feeless payouts
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Extension Points
|
|
88
|
+
|
|
89
|
+
| Extension Point | Interface | Called By |
|
|
90
|
+
|----------------|-----------|-----------|
|
|
91
|
+
| Data Hook (pay) | `IJBRulesetDataHook.beforePayRecordedWith` | JBTerminalStore |
|
|
92
|
+
| Data Hook (cashout) | `IJBRulesetDataHook.beforeCashOutRecordedWith` | JBTerminalStore |
|
|
93
|
+
| Pay Hook | `IJBPayHook.afterPayRecordedWith` | JBMultiTerminal |
|
|
94
|
+
| Cash Out Hook | `IJBCashOutHook.afterCashOutRecordedWith` | JBMultiTerminal |
|
|
95
|
+
| Split Hook | `IJBSplitHook.processSplitWith` | JBMultiTerminal |
|
|
96
|
+
| Approval Hook | `IJBRulesetApprovalHook.approvalStatusOf` | JBRulesets |
|
|
97
|
+
|
|
98
|
+
## Dependencies
|
|
99
|
+
|
|
100
|
+
- `@bananapus/permission-ids-v6` — Permission ID constants
|
|
101
|
+
- `@openzeppelin/contracts` — ERC-721, ERC-20, ERC2771, Clones
|
|
102
|
+
- `@prb/math` — Fixed-point math (mulDiv)
|
|
103
|
+
- `@chainlink/contracts` — Price feed interfaces
|
|
104
|
+
- `@uniswap/permit2` — Permit2 token approvals
|
|
105
|
+
|
|
106
|
+
## Key Constants
|
|
107
|
+
|
|
108
|
+
- FEE = 25 (2.5%), MAX_FEE = 1000
|
|
109
|
+
- MAX_RESERVED_PERCENT = 10,000 (basis points)
|
|
110
|
+
- MAX_CASH_OUT_TAX_RATE = 10,000
|
|
111
|
+
- MAX_WEIGHT_CUT_PERCENT = 1,000,000,000 (9 decimals)
|
|
112
|
+
- SPLITS_TOTAL_PERCENT = 1,000,000,000
|
|
113
|
+
- NATIVE_TOKEN = 0x000000000000000000000000000000000000EEEe
|
|
114
|
+
- Fee holding: 28 days (2,419,200 seconds)
|
|
115
|
+
- Fee beneficiary: project ID 1
|
package/RISKS.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# nana-core-v6 — Risks
|
|
2
|
+
|
|
3
|
+
## Trust Assumptions
|
|
4
|
+
|
|
5
|
+
### What You Trust
|
|
6
|
+
|
|
7
|
+
1. **Shared Infrastructure** — All projects share the same JBMultiTerminal and JBController instances. A bug in these contracts affects every project.
|
|
8
|
+
2. **Project Owner** — The ERC-721 holder can queue new rulesets, set terminals, configure splits, and delegate permissions. A malicious or compromised owner can fundamentally change project economics.
|
|
9
|
+
3. **Data Hooks** — If a ruleset specifies a data hook, that hook has absolute control over token minting weights and cash out parameters. A malicious data hook can drain the entire project treasury.
|
|
10
|
+
4. **Approval Hooks** — Can approve or reject ruleset transitions. A reverting approval hook doesn't freeze the project (try-catch fallback), but a malicious one could allow unexpected transitions.
|
|
11
|
+
5. **Price Feeds** — Surplus calculations depend on Chainlink price feeds. Stale or manipulated feeds affect cash out values and payout calculations. Staleness causes reverts (DoS), not fund loss.
|
|
12
|
+
6. **Fee Project (#1)** — 2.5% fees go to project #1. If project #1's terminal is misconfigured, fees are returned to the originating project's balance (not lost).
|
|
13
|
+
|
|
14
|
+
### What You Do NOT Need to Trust
|
|
15
|
+
|
|
16
|
+
- **Other projects** — Each project's balance is isolated by terminal address in JBTerminalStore
|
|
17
|
+
- **Token holders** — Can only cash out proportional to the bonding curve
|
|
18
|
+
- **Permit2** — Optional; projects work without it
|
|
19
|
+
|
|
20
|
+
## Known Risks
|
|
21
|
+
|
|
22
|
+
### By Design
|
|
23
|
+
|
|
24
|
+
| Risk | Description | Mitigation |
|
|
25
|
+
|------|-------------|------------|
|
|
26
|
+
| Data hook omnipotence | Data hooks override bonding curve parameters | Only use audited, trusted data hooks |
|
|
27
|
+
| Last-holder advantage | Last token holder redeems remaining surplus at 1:1 | Bonding curve math; inherent to the design |
|
|
28
|
+
| Pending reserved inflation | Pending reserved tokens dilute cash out values | Call `sendReservedTokensToSplitsOf` regularly |
|
|
29
|
+
| No reentrancy guard | Protocol relies on CEI ordering, not mutex | State updates before all external calls |
|
|
30
|
+
| Weight cache requirement | Projects with >20k cycles need progressive cache updates | Anyone can call `updateRulesetWeightCache` |
|
|
31
|
+
|
|
32
|
+
### Operational
|
|
33
|
+
|
|
34
|
+
| Risk | Description | Mitigation |
|
|
35
|
+
|------|-------------|------------|
|
|
36
|
+
| Price feed DoS | Stale/reverting price feed blocks multi-currency operations | Monitor feed health; single-currency projects unaffected |
|
|
37
|
+
| Split gas exhaustion | Very large split arrays (100+) may exceed block gas | Keep split count reasonable (<50) |
|
|
38
|
+
| Held fee growth | Held fees array grows without cleanup | `_nextHeldFeeIndexOf` pointer skips processed entries |
|
|
39
|
+
|
|
40
|
+
## Reentrancy Analysis
|
|
41
|
+
|
|
42
|
+
No `ReentrancyGuard`. Relies on state ordering (checks-effects-interactions):
|
|
43
|
+
|
|
44
|
+
| Function | State Updated Before External Call | Risk |
|
|
45
|
+
|----------|-----------------------------------|------|
|
|
46
|
+
| `_cashOutTokensOf` | Store balance deducted, tokens burned BEFORE transfer | LOW |
|
|
47
|
+
| `_pay` | Store balance added, tokens minted BEFORE pay hooks | LOW |
|
|
48
|
+
| `executePayout` | Payout limit recorded BEFORE split hook calls | LOW |
|
|
49
|
+
| `processHeldFeesOf` | Index updated BEFORE fee processing | LOW |
|
|
50
|
+
| `_sendReservedTokensToSplitsOf` | Pending balance zeroed BEFORE minting | LOW |
|
|
51
|
+
|
|
52
|
+
**Key defense:** `JBTerminalStore_InadequateTerminalStoreBalance` revert prevents extracting more than available balance regardless of reentrancy.
|
|
53
|
+
|
|
54
|
+
## Permission Security
|
|
55
|
+
|
|
56
|
+
- ROOT (ID 1) grants all permissions but cannot be set for wildcard `projectId = 0`
|
|
57
|
+
- ROOT operators cannot grant ROOT to other addresses
|
|
58
|
+
- Permission 0 is reserved and cannot be set
|
|
59
|
+
- All permission checks support ERC-2771 meta-transactions
|
|
60
|
+
|
|
61
|
+
## Proven Invariants
|
|
62
|
+
|
|
63
|
+
1. No flash-loan profit (12 attack vectors tested)
|
|
64
|
+
2. Terminal balance >= sum of recorded project balances
|
|
65
|
+
3. Total inflows >= total outflows
|
|
66
|
+
4. Fee project balance monotonically increases
|
|
67
|
+
5. Token supply = creditSupply + erc20.totalSupply()
|
|
68
|
+
6. Current ruleset always exists after launch
|
package/SKILLS.md
CHANGED
|
@@ -246,6 +246,11 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
|
|
|
246
246
|
- `IJBDirectoryAccessControl` has `setControllerAllowed()` and `setTerminalsAllowed()` -- NOT `setControllerAllowedFor()`
|
|
247
247
|
- Price feeds are immutable once set in `JBPrices` -- they cannot be replaced or removed
|
|
248
248
|
- `JBFundAccessLimits` requires payout limits and surplus allowances to be in strictly increasing currency order to prevent duplicates
|
|
249
|
+
- **Empty `fundAccessLimitGroups` = zero payouts, NOT unlimited.** If a ruleset's `fundAccessLimitGroups` array is empty (or has no entry for the terminal/token), `payoutLimitsOf()` returns an empty array → cumulative limit is 0 → `sendPayoutsOf()` reverts on any amount. To allow unlimited payouts, explicitly set a payout limit with `amount: type(uint224).max`.
|
|
250
|
+
- **`groupId` (uint256) vs `currency` (uint32) are different types for the same address.** `JBSplitGroup.groupId` is `uint256(uint160(tokenAddress))` while `JBAccountingContext.currency` is `uint32(uint160(tokenAddress))`. These truncate differently — only `NATIVE_TOKEN` (0x000000000000000000000000000000000000EEEe) matches by coincidence. Don't confuse them.
|
|
251
|
+
- **`JBAccountingContext.currency` is NOT `baseCurrency` — by design.** `baseCurrency` in ruleset metadata uses abstract real-world values (1 = ETH, 2 = USD) so rulesets are portable across chains — `baseCurrency=2` means "issue X tokens per USD" whether on Ethereum, Base, or Arbitrum. `JBAccountingContext.currency` uses token-derived values (`uint32(uint160(tokenAddress))`) because terminals track specific tokens at specific addresses — e.g. NATIVE_TOKEN = 61166, USDC on Ethereum = 909516616, USDC on Base = 3169378579. `JBPrices` mediates between the two: it converts token-derived currencies to/from abstract currencies (e.g. USDC token → USD concept, NATIVE_TOKEN → ETH concept) so that payout limits denominated in USD work correctly regardless of which token the terminal holds. The separation is what makes cross-chain consistency possible: same ruleset, different terminal accounting per chain.
|
|
252
|
+
- **Don't queue multiple identical rulesets.** A ruleset with a `duration` automatically cycles — no need to queue copies. Queue multiple rulesets only when configuration actually changes between periods (e.g. different weight, splits, or limits).
|
|
253
|
+
- **`NATIVE_TOKEN` represents a different token on each chain.** `NATIVE_TOKEN` (`0x000000000000000000000000000000000000EEEe`) is the token received via `msg.value` — ETH on Ethereum/Base/Optimism/Arbitrum, CELO on Celo, etc. Its currency is `uint32(uint160(NATIVE_TOKEN))` = 61166. A `JBMatchingPriceFeed` (returns 1:1) is deployed for `ETH:NATIVE_TOKEN` on ETH-native chains so that `baseCurrency=ETH` resolves correctly to the native token. On non-ETH-native chains, a different price feed would be needed.
|
|
249
254
|
|
|
250
255
|
## Example Integration
|
|
251
256
|
|
package/STYLE_GUIDE.md
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# Style Guide
|
|
2
|
+
|
|
3
|
+
How we write Solidity and organize repos across the Juicebox V6 ecosystem. `nana-core-v6` is the gold standard — when in doubt, match what it does.
|
|
4
|
+
|
|
5
|
+
## File Organization
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── Contract.sol # Main contracts in root
|
|
10
|
+
├── abstract/ # Base contracts (JBPermissioned, JBControlled)
|
|
11
|
+
├── enums/ # One enum per file
|
|
12
|
+
├── interfaces/ # One interface per file, prefixed with I
|
|
13
|
+
├── libraries/ # Pure/view logic, prefixed with JB
|
|
14
|
+
├── periphery/ # Utility contracts (deadlines, price feeds)
|
|
15
|
+
└── structs/ # One struct per file, prefixed with JB
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
One contract/interface/struct/enum per file. Name the file after the type it contains.
|
|
19
|
+
|
|
20
|
+
**Structs, enums, libraries, and interfaces always go in their subdirectories** (`src/structs/`, `src/enums/`, `src/libraries/`, `src/interfaces/`) — never inline in contract files or placed in `src/` root. This keeps type definitions discoverable and import paths consistent across repos.
|
|
21
|
+
|
|
22
|
+
## Pragma Versions
|
|
23
|
+
|
|
24
|
+
```solidity
|
|
25
|
+
// Contracts — pin to exact version
|
|
26
|
+
pragma solidity 0.8.26;
|
|
27
|
+
|
|
28
|
+
// Interfaces, structs, enums — caret for forward compatibility
|
|
29
|
+
pragma solidity ^0.8.0;
|
|
30
|
+
|
|
31
|
+
// Libraries — caret, may use newer features
|
|
32
|
+
pragma solidity ^0.8.17;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Imports
|
|
36
|
+
|
|
37
|
+
Named imports only. Grouped by source, alphabetized within each group:
|
|
38
|
+
|
|
39
|
+
```solidity
|
|
40
|
+
// External packages (alphabetized)
|
|
41
|
+
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
|
|
42
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
43
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
44
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
45
|
+
|
|
46
|
+
// Local: abstract contracts
|
|
47
|
+
import {JBPermissioned} from "./abstract/JBPermissioned.sol";
|
|
48
|
+
|
|
49
|
+
// Local: interfaces (alphabetized)
|
|
50
|
+
import {IJBController} from "./interfaces/IJBController.sol";
|
|
51
|
+
import {IJBDirectory} from "./interfaces/IJBDirectory.sol";
|
|
52
|
+
import {IJBMultiTerminal} from "./interfaces/IJBMultiTerminal.sol";
|
|
53
|
+
|
|
54
|
+
// Local: libraries (alphabetized)
|
|
55
|
+
import {JBConstants} from "./libraries/JBConstants.sol";
|
|
56
|
+
import {JBFees} from "./libraries/JBFees.sol";
|
|
57
|
+
|
|
58
|
+
// Local: structs (alphabetized)
|
|
59
|
+
import {JBAccountingContext} from "./structs/JBAccountingContext.sol";
|
|
60
|
+
import {JBSplit} from "./structs/JBSplit.sol";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Contract Structure
|
|
64
|
+
|
|
65
|
+
Section banners divide the contract into a fixed ordering. Every contract with 50+ lines uses these banners:
|
|
66
|
+
|
|
67
|
+
```solidity
|
|
68
|
+
/// @notice One-line description.
|
|
69
|
+
contract JBExample is JBPermissioned, IJBExample {
|
|
70
|
+
// A library that does X.
|
|
71
|
+
using SomeLib for SomeType;
|
|
72
|
+
|
|
73
|
+
//*********************************************************************//
|
|
74
|
+
// --------------------------- custom errors ------------------------- //
|
|
75
|
+
//*********************************************************************//
|
|
76
|
+
|
|
77
|
+
error JBExample_SomethingFailed(uint256 amount);
|
|
78
|
+
|
|
79
|
+
//*********************************************************************//
|
|
80
|
+
// ------------------------- public constants ------------------------ //
|
|
81
|
+
//*********************************************************************//
|
|
82
|
+
|
|
83
|
+
uint256 public constant override FEE = 25;
|
|
84
|
+
|
|
85
|
+
//*********************************************************************//
|
|
86
|
+
// ----------------------- internal constants ------------------------ //
|
|
87
|
+
//*********************************************************************//
|
|
88
|
+
|
|
89
|
+
uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
|
|
90
|
+
|
|
91
|
+
//*********************************************************************//
|
|
92
|
+
// --------------- public immutable stored properties ---------------- //
|
|
93
|
+
//*********************************************************************//
|
|
94
|
+
|
|
95
|
+
IJBDirectory public immutable override DIRECTORY;
|
|
96
|
+
|
|
97
|
+
//*********************************************************************//
|
|
98
|
+
// --------------------- public stored properties -------------------- //
|
|
99
|
+
//*********************************************************************//
|
|
100
|
+
|
|
101
|
+
//*********************************************************************//
|
|
102
|
+
// -------------------- internal stored properties ------------------- //
|
|
103
|
+
//*********************************************************************//
|
|
104
|
+
|
|
105
|
+
//*********************************************************************//
|
|
106
|
+
// -------------------------- constructor ---------------------------- //
|
|
107
|
+
//*********************************************************************//
|
|
108
|
+
|
|
109
|
+
//*********************************************************************//
|
|
110
|
+
// ---------------------- receive / fallback ------------------------- //
|
|
111
|
+
//*********************************************************************//
|
|
112
|
+
|
|
113
|
+
//*********************************************************************//
|
|
114
|
+
// --------------------------- modifiers ----------------------------- //
|
|
115
|
+
//*********************************************************************//
|
|
116
|
+
|
|
117
|
+
//*********************************************************************//
|
|
118
|
+
// ---------------------- external transactions ---------------------- //
|
|
119
|
+
//*********************************************************************//
|
|
120
|
+
|
|
121
|
+
//*********************************************************************//
|
|
122
|
+
// ----------------------- external views ---------------------------- //
|
|
123
|
+
//*********************************************************************//
|
|
124
|
+
|
|
125
|
+
//*********************************************************************//
|
|
126
|
+
// ----------------------- public transactions ----------------------- //
|
|
127
|
+
//*********************************************************************//
|
|
128
|
+
|
|
129
|
+
//*********************************************************************//
|
|
130
|
+
// ----------------------- internal helpers -------------------------- //
|
|
131
|
+
//*********************************************************************//
|
|
132
|
+
|
|
133
|
+
//*********************************************************************//
|
|
134
|
+
// ----------------------- internal views ---------------------------- //
|
|
135
|
+
//*********************************************************************//
|
|
136
|
+
|
|
137
|
+
//*********************************************************************//
|
|
138
|
+
// ----------------------- private helpers --------------------------- //
|
|
139
|
+
//*********************************************************************//
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Section order:**
|
|
144
|
+
1. `using` declarations
|
|
145
|
+
2. Custom errors
|
|
146
|
+
3. Public constants
|
|
147
|
+
4. Internal constants
|
|
148
|
+
5. Public immutable stored properties
|
|
149
|
+
6. Internal immutable stored properties
|
|
150
|
+
7. Public stored properties
|
|
151
|
+
8. Internal stored properties
|
|
152
|
+
9. Constructor
|
|
153
|
+
10. `receive` / `fallback`
|
|
154
|
+
11. Modifiers
|
|
155
|
+
12. External transactions
|
|
156
|
+
13. External views
|
|
157
|
+
14. Public transactions
|
|
158
|
+
15. Internal helpers
|
|
159
|
+
16. Internal views
|
|
160
|
+
17. Private helpers
|
|
161
|
+
|
|
162
|
+
Functions are alphabetized within each section.
|
|
163
|
+
|
|
164
|
+
**Events:** Events are declared in interfaces only, never in implementation contracts. Implementations inherit events from their interface and emit them unqualified. This keeps the ABI definition in one place and allows tests to use interface-qualified event expectations (e.g., `emit IJBController.LaunchProject(...)`).
|
|
165
|
+
|
|
166
|
+
## Interface Structure
|
|
167
|
+
|
|
168
|
+
```solidity
|
|
169
|
+
/// @notice One-line description.
|
|
170
|
+
interface IJBExample is IJBBase {
|
|
171
|
+
// Events (with full NatSpec)
|
|
172
|
+
|
|
173
|
+
/// @notice Emitted when X happens.
|
|
174
|
+
/// @param projectId The ID of the project.
|
|
175
|
+
/// @param amount The amount transferred.
|
|
176
|
+
event SomethingHappened(uint256 indexed projectId, uint256 amount);
|
|
177
|
+
|
|
178
|
+
// Views (alphabetized)
|
|
179
|
+
|
|
180
|
+
/// @notice The directory of terminals and controllers.
|
|
181
|
+
function DIRECTORY() external view returns (IJBDirectory);
|
|
182
|
+
|
|
183
|
+
// State-changing functions (alphabetized)
|
|
184
|
+
|
|
185
|
+
/// @notice Does the thing.
|
|
186
|
+
/// @param projectId The ID of the project.
|
|
187
|
+
/// @return result The result.
|
|
188
|
+
function doThing(uint256 projectId) external returns (uint256 result);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Rules:**
|
|
193
|
+
- Events first, then views, then state-changing functions
|
|
194
|
+
- No custom errors in interfaces — errors belong in the implementing contract
|
|
195
|
+
- Full NatSpec on every event, function, and parameter
|
|
196
|
+
- Alphabetized within each group
|
|
197
|
+
|
|
198
|
+
## Naming
|
|
199
|
+
|
|
200
|
+
| Thing | Convention | Example |
|
|
201
|
+
|-------|-----------|---------|
|
|
202
|
+
| Contract | PascalCase | `JBMultiTerminal` |
|
|
203
|
+
| Interface | `I` + PascalCase | `IJBMultiTerminal` |
|
|
204
|
+
| Library | PascalCase | `JBCashOuts` |
|
|
205
|
+
| Struct | PascalCase | `JBRulesetConfig` |
|
|
206
|
+
| Enum | PascalCase | `JBApprovalStatus` |
|
|
207
|
+
| Enum value | PascalCase | `ApprovalExpected` |
|
|
208
|
+
| Error | `ContractName_ErrorName` | `JBMultiTerminal_FeeTerminalNotFound` |
|
|
209
|
+
| Public constant | `ALL_CAPS` | `FEE`, `MAX_FEE` |
|
|
210
|
+
| Internal constant | `_ALL_CAPS` | `_FEE_HOLDING_SECONDS` |
|
|
211
|
+
| Public immutable | `ALL_CAPS` | `DIRECTORY`, `PERMISSIONS` |
|
|
212
|
+
| Public/external function | `camelCase` | `cashOutTokensOf` |
|
|
213
|
+
| Internal/private function | `_camelCase` | `_processFee` |
|
|
214
|
+
| Internal storage | `_camelCase` | `_accountingContextForTokenOf` |
|
|
215
|
+
| Function parameter | `camelCase` | `projectId`, `cashOutCount` |
|
|
216
|
+
|
|
217
|
+
## NatSpec
|
|
218
|
+
|
|
219
|
+
**Contracts:**
|
|
220
|
+
```solidity
|
|
221
|
+
/// @notice One-line description of what the contract does.
|
|
222
|
+
contract JBExample is IJBExample {
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Functions:**
|
|
226
|
+
```solidity
|
|
227
|
+
/// @notice Records funds being added to a project's balance.
|
|
228
|
+
/// @param projectId The ID of the project which funds are being added to.
|
|
229
|
+
/// @param token The token being added.
|
|
230
|
+
/// @param amount The amount added, as a fixed point number with the same decimals as the terminal.
|
|
231
|
+
/// @return surplus The new surplus after adding.
|
|
232
|
+
function recordAddedBalanceFor(
|
|
233
|
+
uint256 projectId,
|
|
234
|
+
address token,
|
|
235
|
+
uint256 amount
|
|
236
|
+
) external override returns (uint256 surplus) {
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Structs:**
|
|
240
|
+
```solidity
|
|
241
|
+
/// @custom:member duration The number of seconds the ruleset lasts for. 0 means it never expires.
|
|
242
|
+
/// @custom:member weight How many tokens to mint per unit paid (18 decimals).
|
|
243
|
+
/// @custom:member weightCutPercent How much weight decays each cycle (9 decimals).
|
|
244
|
+
struct JBRulesetConfig {
|
|
245
|
+
uint32 duration;
|
|
246
|
+
uint112 weight;
|
|
247
|
+
uint32 weightCutPercent;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Mappings:**
|
|
252
|
+
```solidity
|
|
253
|
+
/// @notice Context describing how a token is accounted for by a project.
|
|
254
|
+
/// @custom:param projectId The ID of the project.
|
|
255
|
+
/// @custom:param token The address of the token.
|
|
256
|
+
mapping(uint256 projectId => mapping(address token => JBAccountingContext)) internal _accountingContextForTokenOf;
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Numbers
|
|
260
|
+
|
|
261
|
+
Use underscores for thousands separators:
|
|
262
|
+
|
|
263
|
+
```solidity
|
|
264
|
+
uint256 internal constant _FEE_HOLDING_SECONDS = 2_419_200; // 28 days
|
|
265
|
+
uint32 public constant MAX_WEIGHT_CUT_PERCENT = 1_000_000_000;
|
|
266
|
+
uint256 public constant MAX_RESERVED_PERCENT = 10_000;
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Function Calls
|
|
270
|
+
|
|
271
|
+
Use named parameters for readability when calling functions with 3+ arguments:
|
|
272
|
+
|
|
273
|
+
```solidity
|
|
274
|
+
PERMISSIONS.hasPermission({
|
|
275
|
+
operator: sender,
|
|
276
|
+
account: account,
|
|
277
|
+
projectId: projectId,
|
|
278
|
+
permissionId: permissionId,
|
|
279
|
+
includeRoot: true,
|
|
280
|
+
includeWildcardProjectId: true
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Multiline Signatures
|
|
285
|
+
|
|
286
|
+
```solidity
|
|
287
|
+
function recordCashOutFor(
|
|
288
|
+
address holder,
|
|
289
|
+
uint256 projectId,
|
|
290
|
+
uint256 cashOutCount,
|
|
291
|
+
JBAccountingContext calldata accountingContext
|
|
292
|
+
)
|
|
293
|
+
external
|
|
294
|
+
override
|
|
295
|
+
returns (
|
|
296
|
+
JBRuleset memory ruleset,
|
|
297
|
+
uint256 reclaimAmount,
|
|
298
|
+
JBCashOutHookSpecification[] memory hookSpecifications
|
|
299
|
+
)
|
|
300
|
+
{
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Modifiers and return types go on their own indented lines.
|
|
304
|
+
|
|
305
|
+
## Error Handling
|
|
306
|
+
|
|
307
|
+
- Validate inputs with explicit `revert` + custom error
|
|
308
|
+
- Use `try-catch` only for external calls to untrusted contracts (hooks, fee processing)
|
|
309
|
+
- Always include relevant context in error parameters
|
|
310
|
+
|
|
311
|
+
```solidity
|
|
312
|
+
// Direct validation
|
|
313
|
+
if (amount > limit) revert JBTerminalStore_InadequateControllerPayoutLimit(amount, limit);
|
|
314
|
+
|
|
315
|
+
// External call to untrusted hook
|
|
316
|
+
try hook.afterPayRecordedWith(context) {} catch (bytes memory reason) {
|
|
317
|
+
emit HookAfterPayReverted(hook, context, reason, _msgSender());
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## DevOps
|
|
324
|
+
|
|
325
|
+
### foundry.toml
|
|
326
|
+
|
|
327
|
+
Standard config for `@bananapus/core-v6`:
|
|
328
|
+
|
|
329
|
+
```toml
|
|
330
|
+
[profile.default]
|
|
331
|
+
solc = '0.8.26'
|
|
332
|
+
evm_version = 'cancun'
|
|
333
|
+
optimizer_runs = 200
|
|
334
|
+
libs = ["node_modules", "lib"]
|
|
335
|
+
fs_permissions = [{ access = "read-write", path = "./"}]
|
|
336
|
+
|
|
337
|
+
[profile.ci_sizes]
|
|
338
|
+
optimizer_runs = 200
|
|
339
|
+
|
|
340
|
+
[fuzz]
|
|
341
|
+
runs = 4096
|
|
342
|
+
|
|
343
|
+
[invariant]
|
|
344
|
+
runs = 1024
|
|
345
|
+
depth = 100
|
|
346
|
+
fail_on_revert = false
|
|
347
|
+
|
|
348
|
+
[fmt]
|
|
349
|
+
number_underscore = "thousands"
|
|
350
|
+
multiline_func_header = "all"
|
|
351
|
+
wrap_comments = true
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
This is the standard config with no deviations.
|
|
355
|
+
|
|
356
|
+
### CI Workflows
|
|
357
|
+
|
|
358
|
+
Every repo has at minimum `test.yml` and `lint.yml`:
|
|
359
|
+
|
|
360
|
+
**test.yml:**
|
|
361
|
+
```yaml
|
|
362
|
+
name: test
|
|
363
|
+
on:
|
|
364
|
+
pull_request:
|
|
365
|
+
branches: [main]
|
|
366
|
+
push:
|
|
367
|
+
branches: [main]
|
|
368
|
+
jobs:
|
|
369
|
+
forge-test:
|
|
370
|
+
runs-on: ubuntu-latest
|
|
371
|
+
steps:
|
|
372
|
+
- uses: actions/checkout@v4
|
|
373
|
+
with:
|
|
374
|
+
submodules: recursive
|
|
375
|
+
- uses: actions/setup-node@v4
|
|
376
|
+
with:
|
|
377
|
+
node-version: 22.4.x
|
|
378
|
+
- name: Install npm dependencies
|
|
379
|
+
run: npm install --omit=dev
|
|
380
|
+
- name: Install Foundry
|
|
381
|
+
uses: foundry-rs/foundry-toolchain@v1
|
|
382
|
+
- name: Run tests
|
|
383
|
+
run: forge test --fail-fast --summary --detailed --skip "*/script/**"
|
|
384
|
+
- name: Check contract sizes
|
|
385
|
+
run: FOUNDRY_PROFILE=ci_sizes forge build --sizes --skip "*/test/**" --skip "*/script/**" --skip SphinxUtils
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**lint.yml:**
|
|
389
|
+
```yaml
|
|
390
|
+
name: lint
|
|
391
|
+
on:
|
|
392
|
+
pull_request:
|
|
393
|
+
branches: [main]
|
|
394
|
+
push:
|
|
395
|
+
branches: [main]
|
|
396
|
+
jobs:
|
|
397
|
+
forge-fmt:
|
|
398
|
+
runs-on: ubuntu-latest
|
|
399
|
+
steps:
|
|
400
|
+
- uses: actions/checkout@v4
|
|
401
|
+
- name: Install Foundry
|
|
402
|
+
uses: foundry-rs/foundry-toolchain@v1
|
|
403
|
+
- name: Check formatting
|
|
404
|
+
run: forge fmt --check
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### package.json
|
|
408
|
+
|
|
409
|
+
```json
|
|
410
|
+
{
|
|
411
|
+
"name": "@bananapus/core-v6",
|
|
412
|
+
"version": "x.x.x",
|
|
413
|
+
"license": "MIT",
|
|
414
|
+
"repository": { "type": "git", "url": "git+https://github.com/Bananapus/nana-core-v6.git" },
|
|
415
|
+
"engines": { "node": ">=20.0.0" },
|
|
416
|
+
"scripts": {
|
|
417
|
+
"test": "forge test",
|
|
418
|
+
"coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary"
|
|
419
|
+
},
|
|
420
|
+
"dependencies": { ... },
|
|
421
|
+
"devDependencies": {
|
|
422
|
+
"@sphinx-labs/plugins": "^0.33.2"
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Scoping:** `@bananapus/` for Bananapus repos, `@rev-net/` for revnet, `@croptop/` for croptop, `@bannynet/` for banny, `@ballkidz/` for defifa.
|
|
428
|
+
|
|
429
|
+
### remappings.txt
|
|
430
|
+
|
|
431
|
+
Every repo has a `remappings.txt`. Minimal content:
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
@sphinx-labs/contracts/=lib/sphinx/packages/contracts/contracts/foundry
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Additional mappings as needed for repo-specific dependencies.
|
|
438
|
+
|
|
439
|
+
### Formatting
|
|
440
|
+
|
|
441
|
+
Run `forge fmt` before committing. The `[fmt]` config in `foundry.toml` enforces:
|
|
442
|
+
- Thousands separators on numbers (`1_000_000`)
|
|
443
|
+
- Multiline function headers when multiple parameters
|
|
444
|
+
- Wrapped comments at reasonable width
|
|
445
|
+
|
|
446
|
+
CI checks formatting via `forge fmt --check`.
|
|
447
|
+
|
|
448
|
+
### Branching
|
|
449
|
+
|
|
450
|
+
- `main` is the primary branch
|
|
451
|
+
- Feature branches for PRs
|
|
452
|
+
- All PRs trigger test + lint workflows
|
|
453
|
+
- Submodule checkout with `--recursive` in CI
|
|
454
|
+
|
|
455
|
+
### Dependencies
|
|
456
|
+
|
|
457
|
+
- Solidity dependencies via npm (`node_modules/`)
|
|
458
|
+
- `forge-std` as a git submodule in `lib/`
|
|
459
|
+
- Sphinx plugins as a devDependency
|
|
460
|
+
- Cross-repo references use `file:../sibling-repo` in local development
|
|
461
|
+
- Published versions use semver ranges (`^0.0.x`) for npm
|
|
462
|
+
|
|
463
|
+
### Contract Size Checks
|
|
464
|
+
|
|
465
|
+
CI runs `FOUNDRY_PROFILE=ci_sizes forge build --sizes` to catch contracts approaching the 24KB limit. The `ci_sizes` profile uses `optimizer_runs = 200` for realistic size measurement even when the default profile has different optimizer settings.
|
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.12",
|
|
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.6",
|
|
30
30
|
"@chainlink/contracts": "^1.3.0",
|
|
31
31
|
"@openzeppelin/contracts": "^5.2.0",
|
|
32
32
|
"@prb/math": "^4.1.0",
|
|
@@ -198,8 +198,8 @@ contract BondingCurveProperties is Test {
|
|
|
198
198
|
if (firstResult + secondResult > singleResult) {
|
|
199
199
|
// The excess should be tiny relative to the result
|
|
200
200
|
uint256 excess = (firstResult + secondResult) - singleResult;
|
|
201
|
-
// Allow up to
|
|
202
|
-
assert(excess * 10_000 <= singleResult);
|
|
201
|
+
// Allow up to 2 wei absolute (for small values) or 0.01% relative
|
|
202
|
+
assert(excess <= 2 || excess * 10_000 <= singleResult);
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -229,10 +229,13 @@ contract BondingCurveProperties is Test {
|
|
|
229
229
|
uint256 secondResult = JBCashOuts.cashOutFrom(remainingSurplus, b, remainingSupply, cashOutTaxRate);
|
|
230
230
|
|
|
231
231
|
// NOTE: Strict subadditivity violated due to mulDiv rounding.
|
|
232
|
-
// Verify the weaker property: excess bounded by rounding tolerance (< 0.01%).
|
|
232
|
+
// Verify the weaker property: excess bounded by rounding tolerance (<=2 wei or < 0.01%).
|
|
233
233
|
if (firstResult + secondResult > singleResult) {
|
|
234
234
|
uint256 excess = (firstResult + secondResult) - singleResult;
|
|
235
|
-
|
|
235
|
+
assertTrue(
|
|
236
|
+
excess <= 2 || excess * 10_000 <= singleResult,
|
|
237
|
+
"No-arbitrage: rounding excess should be <= 2 wei or < 0.01%"
|
|
238
|
+
);
|
|
236
239
|
}
|
|
237
240
|
}
|
|
238
241
|
|