@bananapus/permission-ids-v6 0.0.10 → 0.0.11
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 +39 -36
- package/ARCHITECTURE.md +13 -1
- package/AUDIT_INSTRUCTIONS.md +37 -3
- package/CHANGE_LOG.md +13 -0
- package/README.md +12 -3
- package/RISKS.md +3 -0
- package/SKILLS.md +19 -14
- package/STYLE_GUIDE.md +2 -2
- package/USER_JOURNEYS.md +144 -31
- package/foundry.toml +1 -1
- package/package.json +1 -1
- package/src/JBPermissionIds.sol +1 -1
package/ADMINISTRATION.md
CHANGED
|
@@ -14,39 +14,39 @@ All 33 defined permission IDs and what they control:
|
|
|
14
14
|
|
|
15
15
|
| ID | Constant | Used By | What It Controls |
|
|
16
16
|
|----|----------|---------|-----------------|
|
|
17
|
-
| 1 | `ROOT` | All contracts | Grants every permission. See [ROOT Permission](#root-permission). |
|
|
18
|
-
| 2 | `QUEUE_RULESETS` | nana-core | `JBController.queueRulesetsOf` -- queue new rulesets for a project. |
|
|
19
|
-
| 3 | `LAUNCH_RULESETS` | nana-core | `JBController.launchRulesetsFor` -- launch a project's initial rulesets. Also requires `SET_TERMINALS` (ID 15). |
|
|
20
|
-
| 4 | `CASH_OUT_TOKENS` | nana-core | `JBMultiTerminal.cashOutTokensOf` -- redeem tokens for surplus. Checked against the **token holder**, not the project owner. |
|
|
21
|
-
| 5 | `SEND_PAYOUTS` | nana-core | `JBMultiTerminal.sendPayoutsOf` -- distribute payouts to splits. |
|
|
22
|
-
| 6 | `MIGRATE_TERMINAL` | nana-core | `JBMultiTerminal.migrateBalanceOf` -- migrate a project's balance to another terminal. |
|
|
23
|
-
| 7 | `SET_PROJECT_URI` | nana-core | `JBController.setUriOf` -- set project metadata URI. |
|
|
24
|
-
| 8 | `DEPLOY_ERC20` | nana-core | `JBController.deployERC20For` -- deploy a new ERC-20 token for a project. |
|
|
25
|
-
| 9 | `SET_TOKEN` | nana-core | `JBController.setTokenFor` -- set an existing ERC-20 token for a project. |
|
|
26
|
-
| 10 | `MINT_TOKENS` | nana-core | `JBController.mintTokensOf` -- mint new project tokens. Only effective when the current ruleset allows owner minting. |
|
|
27
|
-
| 11 | `BURN_TOKENS` | nana-core | `JBController.burnTokensOf` -- burn tokens. Checked against the **token holder**. |
|
|
28
|
-
| 12 | `CLAIM_TOKENS` | nana-core | `JBController.claimTokensFor` -- claim internal credits as ERC-20. Checked against the **token holder**. |
|
|
29
|
-
| 13 | `TRANSFER_CREDITS` | nana-core | `JBController.transferCreditsFrom` -- transfer internal credits. Checked against the **token holder**. |
|
|
30
|
-
| 14 | `SET_CONTROLLER` | nana-core | `JBDirectory.setControllerOf` -- set a project's controller. |
|
|
31
|
-
| 15 | `SET_TERMINALS` | nana-core | `JBDirectory.setTerminalsOf` -- set a project's terminals. **Warning:** can remove the primary terminal. |
|
|
32
|
-
| 16 | `SET_PRIMARY_TERMINAL` | nana-core | `JBDirectory.setPrimaryTerminalOf` -- set the primary terminal for a token. |
|
|
33
|
-
| 17 | `USE_ALLOWANCE` | nana-core | `JBMultiTerminal.useAllowanceOf` -- spend surplus allowance to an arbitrary address. |
|
|
34
|
-
| 18 | `SET_SPLIT_GROUPS` | nana-core | `JBController.setSplitGroupsOf` -- configure payout and reserved token splits. |
|
|
35
|
-
| 19 | `ADD_PRICE_FEED` | nana-core | `JBController.
|
|
36
|
-
| 20 | `ADD_ACCOUNTING_CONTEXTS` | nana-core | `JBMultiTerminal.addAccountingContextsFor` -- add accepted tokens to a terminal. |
|
|
37
|
-
| 21 | `SET_TOKEN_METADATA` | nana-core | `JBController.setTokenMetadataOf` -- set a project token's name and symbol. |
|
|
38
|
-
| 22 | `ADJUST_721_TIERS` | nana-721-hook | `JB721TiersHook.adjustTiers` -- add or remove NFT tiers. |
|
|
39
|
-
| 23 | `SET_721_METADATA` | nana-721-hook | `JB721TiersHook.setMetadata` -- set NFT metadata URIs. |
|
|
40
|
-
| 24 | `MINT_721` | nana-721-hook | `JB721TiersHook.mintFor` -- manually mint NFTs to a beneficiary. |
|
|
41
|
-
| 25 | `SET_721_DISCOUNT_PERCENT` | nana-721-hook | `JB721TiersHook.setDiscountPercentOf` -- set discount percent on NFT tiers. |
|
|
42
|
-
| 26 | `SET_BUYBACK_TWAP` | nana-buyback-hook | `JBBuybackHook.setTwapWindowOf` -- configure the TWAP oracle window. |
|
|
43
|
-
| 27 | `SET_BUYBACK_POOL` | nana-buyback-hook | `JBBuybackHook.setPoolFor` -- set the Uniswap pool for buybacks. |
|
|
44
|
-
| 28 | `SET_BUYBACK_HOOK` | nana-buyback-hook | `JBBuybackHookRegistry.setHookFor` and `lockHookFor` -- configure and permanently lock the buyback hook. |
|
|
45
|
-
| 29 | `SET_ROUTER_TERMINAL` | nana-router-terminal | `JBRouterTerminalRegistry.setTerminalFor` and `lockTerminalFor` -- configure and permanently lock the router terminal. |
|
|
46
|
-
| 30 | `MAP_SUCKER_TOKEN` | nana-suckers | `JBSucker.mapToken` -- map an ERC-20 to its remote chain counterpart. Immutable once the outbox tree has entries. |
|
|
47
|
-
| 31 | `DEPLOY_SUCKERS` | nana-suckers | `JBSuckerRegistry.deploySuckersFor` -- deploy sucker contracts for cross-chain bridging. |
|
|
48
|
-
| 32 | `SUCKER_SAFETY` | nana-suckers | `JBSucker.enableEmergencyHatchFor` -- enable the emergency hatch to recover stuck tokens. |
|
|
49
|
-
| 33 | `SET_SUCKER_DEPRECATION` | nana-suckers | `JBSucker.setDeprecation` -- set deprecation status (ENABLED, DEPRECATION_PENDING, SENDING_DISABLED, DEPRECATED). |
|
|
17
|
+
| 1 | `ROOT` | All contracts (via `JBPermissions`) | Grants every permission. See [ROOT Permission](#root-permission). |
|
|
18
|
+
| 2 | `QUEUE_RULESETS` | nana-core (`JBController`) | `JBController.queueRulesetsOf` -- queue new rulesets for a project. |
|
|
19
|
+
| 3 | `LAUNCH_RULESETS` | nana-core (`JBController`) | `JBController.launchRulesetsFor` -- launch a project's initial rulesets. Also requires `SET_TERMINALS` (ID 15). |
|
|
20
|
+
| 4 | `CASH_OUT_TOKENS` | nana-core (`JBMultiTerminal`) | `JBMultiTerminal.cashOutTokensOf` -- redeem tokens for surplus. Checked against the **token holder**, not the project owner. |
|
|
21
|
+
| 5 | `SEND_PAYOUTS` | nana-core (`JBMultiTerminal`) | `JBMultiTerminal.sendPayoutsOf` -- distribute payouts to splits. |
|
|
22
|
+
| 6 | `MIGRATE_TERMINAL` | nana-core (`JBMultiTerminal`) | `JBMultiTerminal.migrateBalanceOf` -- migrate a project's balance to another terminal. |
|
|
23
|
+
| 7 | `SET_PROJECT_URI` | nana-core (`JBController`) | `JBController.setUriOf` -- set project metadata URI. |
|
|
24
|
+
| 8 | `DEPLOY_ERC20` | nana-core (`JBController`) | `JBController.deployERC20For` -- deploy a new ERC-20 token for a project. |
|
|
25
|
+
| 9 | `SET_TOKEN` | nana-core (`JBController`) | `JBController.setTokenFor` -- set an existing ERC-20 token for a project. |
|
|
26
|
+
| 10 | `MINT_TOKENS` | nana-core (`JBController`) | `JBController.mintTokensOf` -- mint new project tokens. Only effective when the current ruleset allows owner minting. |
|
|
27
|
+
| 11 | `BURN_TOKENS` | nana-core (`JBController`) | `JBController.burnTokensOf` -- burn tokens. Checked against the **token holder**. |
|
|
28
|
+
| 12 | `CLAIM_TOKENS` | nana-core (`JBController`) | `JBController.claimTokensFor` -- claim internal credits as ERC-20. Checked against the **token holder**. |
|
|
29
|
+
| 13 | `TRANSFER_CREDITS` | nana-core (`JBController`) | `JBController.transferCreditsFrom` -- transfer internal credits. Checked against the **token holder**. |
|
|
30
|
+
| 14 | `SET_CONTROLLER` | nana-core (`JBDirectory`) | `JBDirectory.setControllerOf` -- set a project's controller. |
|
|
31
|
+
| 15 | `SET_TERMINALS` | nana-core (`JBDirectory`) | `JBDirectory.setTerminalsOf` -- set a project's terminals. **Warning:** can remove the primary terminal. |
|
|
32
|
+
| 16 | `SET_PRIMARY_TERMINAL` | nana-core (`JBDirectory`) | `JBDirectory.setPrimaryTerminalOf` -- set the primary terminal for a token. |
|
|
33
|
+
| 17 | `USE_ALLOWANCE` | nana-core (`JBMultiTerminal`) | `JBMultiTerminal.useAllowanceOf` -- spend surplus allowance to an arbitrary address. |
|
|
34
|
+
| 18 | `SET_SPLIT_GROUPS` | nana-core (`JBController`) | `JBController.setSplitGroupsOf` -- configure payout and reserved token splits. |
|
|
35
|
+
| 19 | `ADD_PRICE_FEED` | nana-core (`JBController`) | `JBController.addPriceFeedFor` (which internally calls `JBPrices.addPriceFeedFor`) -- add a price feed for a project. |
|
|
36
|
+
| 20 | `ADD_ACCOUNTING_CONTEXTS` | nana-core (`JBMultiTerminal`) | `JBMultiTerminal.addAccountingContextsFor` -- add accepted tokens to a terminal. |
|
|
37
|
+
| 21 | `SET_TOKEN_METADATA` | nana-core (`JBController`) | `JBController.setTokenMetadataOf` -- set a project token's name and symbol. |
|
|
38
|
+
| 22 | `ADJUST_721_TIERS` | nana-721-hook (`JB721TiersHook`) | `JB721TiersHook.adjustTiers` -- add or remove NFT tiers. |
|
|
39
|
+
| 23 | `SET_721_METADATA` | nana-721-hook (`JB721TiersHook`) | `JB721TiersHook.setMetadata` -- set NFT metadata URIs. |
|
|
40
|
+
| 24 | `MINT_721` | nana-721-hook (`JB721TiersHook`) | `JB721TiersHook.mintFor` -- manually mint NFTs to a beneficiary. |
|
|
41
|
+
| 25 | `SET_721_DISCOUNT_PERCENT` | nana-721-hook (`JB721TiersHook`) | `JB721TiersHook.setDiscountPercentOf` -- set discount percent on NFT tiers. |
|
|
42
|
+
| 26 | `SET_BUYBACK_TWAP` | nana-buyback-hook (`JBBuybackHook`) | `JBBuybackHook.setTwapWindowOf` -- configure the TWAP oracle window. |
|
|
43
|
+
| 27 | `SET_BUYBACK_POOL` | nana-buyback-hook (`JBBuybackHook`) | `JBBuybackHook.setPoolFor` -- set the Uniswap pool for buybacks. |
|
|
44
|
+
| 28 | `SET_BUYBACK_HOOK` | nana-buyback-hook (`JBBuybackHookRegistry`) | `JBBuybackHookRegistry.setHookFor` and `lockHookFor` -- configure and permanently lock the buyback hook. |
|
|
45
|
+
| 29 | `SET_ROUTER_TERMINAL` | nana-router-terminal (`JBRouterTerminalRegistry`) | `JBRouterTerminalRegistry.setTerminalFor` and `lockTerminalFor` -- configure and permanently lock the router terminal. |
|
|
46
|
+
| 30 | `MAP_SUCKER_TOKEN` | nana-suckers (`JBSucker`) | `JBSucker.mapToken` -- map an ERC-20 to its remote chain counterpart. Immutable once the outbox tree has entries. |
|
|
47
|
+
| 31 | `DEPLOY_SUCKERS` | nana-suckers (`JBSuckerRegistry`) | `JBSuckerRegistry.deploySuckersFor` -- deploy sucker contracts for cross-chain bridging. |
|
|
48
|
+
| 32 | `SUCKER_SAFETY` | nana-suckers (`JBSucker`) | `JBSucker.enableEmergencyHatchFor` -- enable the emergency hatch to recover stuck tokens. |
|
|
49
|
+
| 33 | `SET_SUCKER_DEPRECATION` | nana-suckers (`JBSucker`) | `JBSucker.setDeprecation` -- set deprecation status (ENABLED, DEPRECATION_PENDING, SENDING_DISABLED, DEPRECATED). |
|
|
50
50
|
|
|
51
51
|
IDs 0 and 34-255 are unused. ID 0 is reserved and cannot be set. IDs 34-255 are available for future ecosystem extensions.
|
|
52
52
|
|
|
@@ -74,11 +74,14 @@ Permissions are stored in `JBPermissions` as a 256-bit packed integer per (opera
|
|
|
74
74
|
permissionsOf[operator][account][projectId] => uint256 (packed bits)
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
Each bit position corresponds to a permission ID. When a contract checks whether an operator has a permission, it calls `JBPermissions.hasPermission(operator, account, projectId, permissionId)`, which:
|
|
77
|
+
Each bit position corresponds to a permission ID. When a contract checks whether an operator has a permission, it calls `JBPermissions.hasPermission(operator, account, projectId, permissionId, includeRoot, includeWildcardProjectId)`, which:
|
|
78
78
|
|
|
79
79
|
1. Checks whether the operator has ROOT (bit 1) for the specific project -- if so, returns true.
|
|
80
|
-
2.
|
|
81
|
-
3.
|
|
80
|
+
2. If `includeWildcardProjectId`, checks whether the operator has ROOT (bit 1) for the wildcard project (`projectId = 0`) -- if so, returns true.
|
|
81
|
+
3. Checks whether the specific permission bit is set for the specific project -- if so, returns true.
|
|
82
|
+
4. If `includeWildcardProjectId`, checks whether the specific permission bit is set for the wildcard project (`projectId = 0`) -- if so, returns true.
|
|
83
|
+
|
|
84
|
+
Steps 1-2 are skipped if `includeRoot` is false. Steps 2 and 4 are skipped if `includeWildcardProjectId` is false.
|
|
82
85
|
|
|
83
86
|
Contracts that use this system inherit from `JBPermissioned`, which provides the `_requirePermissionFrom(account, projectId, permissionId)` modifier. This modifier passes if the caller is the account itself or has the required permission via `JBPermissions`.
|
|
84
87
|
|
package/ARCHITECTURE.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
Constants library defining permission IDs used throughout the Juicebox V6 ecosystem. These IDs are used with `JBPermissions` to control access to protocol functions.
|
|
6
6
|
|
|
7
|
+
## How Permissions Work
|
|
8
|
+
|
|
9
|
+
These IDs plug into `JBPermissions` in nana-core, which stores permissions as a 256-bit packed `uint256` — one bit per permission ID. Callers check access via `hasPermission(operator, account, projectId, permissionId)`. A project owner can grant any operator a set of permission IDs scoped to a specific project, or use `projectId = 0` as a wildcard to grant permissions across all projects.
|
|
10
|
+
|
|
11
|
+
## Permission Guards
|
|
12
|
+
|
|
13
|
+
`JBPermissions.setPermissionsFor` enforces three guard rules:
|
|
14
|
+
|
|
15
|
+
- **Permission 0 is reserved.** Setting bit 0 always reverts (`JBPermissions_NoZeroPermission`). This prevents accidental misuse of an uninitialized permission ID.
|
|
16
|
+
- **Wildcard scope requires the account itself.** An operator with ROOT on a specific project cannot use `setPermissionsFor` with `projectId = 0` (wildcard). This prevents a single-project ROOT operator from escalating to all-project access. Reverts with `JBPermissions_Unauthorized`.
|
|
17
|
+
- **ROOT operators cannot grant ROOT to others.** Only the account itself can include `ROOT` (ID 1) in a `setPermissionsFor` call. A ROOT operator calling on behalf of the account will revert if the new permission set includes ROOT.
|
|
18
|
+
|
|
7
19
|
## Contract Map
|
|
8
20
|
|
|
9
21
|
```
|
|
@@ -33,7 +45,7 @@ src/
|
|
|
33
45
|
| 16 | `SET_PRIMARY_TERMINAL` | nana-core | `JBDirectory.setPrimaryTerminalOf` |
|
|
34
46
|
| 17 | `USE_ALLOWANCE` | nana-core | `JBMultiTerminal.useAllowanceOf` |
|
|
35
47
|
| 18 | `SET_SPLIT_GROUPS` | nana-core | `JBController.setSplitGroupsOf` |
|
|
36
|
-
| 19 | `ADD_PRICE_FEED` | nana-core | `JBController.
|
|
48
|
+
| 19 | `ADD_PRICE_FEED` | nana-core | `JBController.addPriceFeedFor` |
|
|
37
49
|
| 20 | `ADD_ACCOUNTING_CONTEXTS` | nana-core | `JBMultiTerminal.addAccountingContextsFor` |
|
|
38
50
|
| 21 | `SET_TOKEN_METADATA` | nana-core | `JBController.setTokenMetadataOf` |
|
|
39
51
|
| 22 | `ADJUST_721_TIERS` | nana-721-hook | `JB721TiersHook.adjustTiers` |
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
You are auditing a constants-only library that defines all permission IDs used across the Juicebox V6 ecosystem. The library has no state, no functions, no constructors, and no dependencies. The entire audit surface is the correctness and consistency of 33 `uint8` constants. Read [RISKS.md](./RISKS.md) first -- it documents all known risks and trust assumptions. Then come back here.
|
|
4
4
|
|
|
5
|
+
## Quick Verification
|
|
6
|
+
|
|
7
|
+
Run this one-liner from the repo root to verify all 33 permission IDs are unique, sequential (1-33), and have no gaps or duplicates:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
grep 'constant.*=' src/JBPermissionIds.sol | sed 's/.*= \([0-9]*\).*/\1/' | sort -n | diff - <(seq 1 33) && echo "PASS: All 33 IDs are unique and sequential (1-33)" || echo "FAIL: ID mismatch detected"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If `PASS` is printed, the constants are correctly assigned. If `FAIL` is printed, inspect the diff output to identify which IDs are missing, duplicated, or out of range.
|
|
14
|
+
|
|
15
|
+
## Compiler and Version Info
|
|
16
|
+
|
|
17
|
+
From `foundry.toml`:
|
|
18
|
+
|
|
19
|
+
| Setting | Value |
|
|
20
|
+
|---------|-------|
|
|
21
|
+
| Solidity version | `0.8.28` |
|
|
22
|
+
| EVM target | `cancun` |
|
|
23
|
+
| Optimizer | Enabled, 200 runs |
|
|
24
|
+
| Pragma in source | `^0.8.0` (flexible, compiled with 0.8.28) |
|
|
25
|
+
|
|
26
|
+
Note: The library pragma is `^0.8.0` rather than a fixed version, since consuming contracts may compile it with their own Solidity version. The `foundry.toml` pins `0.8.28` for local builds.
|
|
27
|
+
|
|
28
|
+
## Previous Audit Findings
|
|
29
|
+
|
|
30
|
+
A Nemesis audit (Feynman + State Inconsistency methodology) was conducted on 2026-03-17. Full results are in [`.audit/findings/nemesis-verified.md`](./.audit/findings/nemesis-verified.md).
|
|
31
|
+
|
|
32
|
+
**Result: 0 Critical | 0 High | 0 Medium | 2 Low (comment inaccuracies)**
|
|
33
|
+
|
|
34
|
+
| ID | Severity | Summary | Status |
|
|
35
|
+
|----|----------|---------|--------|
|
|
36
|
+
| NM-001 | LOW | `ADD_PRICE_FEED` comment misidentifies gated contract (`JBPrices.addPriceFeedFor` vs actual `JBController.addPriceFeedFor`) | Fixed |
|
|
37
|
+
| NM-002 | LOW | `SET_TOKEN_METADATA` comment uses wrong function name (`setMetadataOf` vs actual `setTokenMetadataOf`) | Fixed |
|
|
38
|
+
|
|
39
|
+
Both findings were comment-only inaccuracies with no security impact. All 5 invariants (uniqueness, completeness, type consistency, no ID 0, sequential assignment) passed. Cross-repo verification confirmed correct usage across 7 consuming repos.
|
|
40
|
+
|
|
5
41
|
## Scope
|
|
6
42
|
|
|
7
43
|
**In scope:**
|
|
@@ -48,7 +84,7 @@ permissionsOf[operator][account][projectId] => uint256 (one bit per permission I
|
|
|
48
84
|
| 16 | `SET_PRIMARY_TERMINAL` | `JBDirectory.setPrimaryTerminalOf` | Project owner |
|
|
49
85
|
| 17 | `USE_ALLOWANCE` | `JBMultiTerminal.useAllowanceOf` | Project owner |
|
|
50
86
|
| 18 | `SET_SPLIT_GROUPS` | `JBController.setSplitGroupsOf` | Project owner |
|
|
51
|
-
| 19 | `ADD_PRICE_FEED` | `JBController.
|
|
87
|
+
| 19 | `ADD_PRICE_FEED` | `JBController.addPriceFeedFor` (not `JBPrices` directly) | Project owner |
|
|
52
88
|
| 20 | `ADD_ACCOUNTING_CONTEXTS` | `JBMultiTerminal.addAccountingContextsFor` | Project owner |
|
|
53
89
|
| 21 | `SET_TOKEN_METADATA` | `JBController.setTokenMetadataOf` | Project owner |
|
|
54
90
|
| 22 | `ADJUST_721_TIERS` | `JB721TiersHook.adjustTiers` | Hook owner |
|
|
@@ -84,8 +120,6 @@ Each constant's doc comment claims it gates a specific function. Verify against
|
|
|
84
120
|
- **nana-router-terminal-v6**: ID 29 should match permission checks in `JBRouterTerminalRegistry`.
|
|
85
121
|
- **nana-suckers-v6**: IDs 30-33 should match permission checks in `JBSucker` and `JBSuckerRegistry`.
|
|
86
122
|
|
|
87
|
-
Known discrepancy to investigate: **SET_BUYBACK_HOOK (ID 28)** -- the source comment says it gates `JBBuybackHookRegistry.setHookFor` and `lockHookFor`, but those functions may actually check `SET_BUYBACK_POOL` (ID 27) instead. Verify which ID is actually checked in the registry code and whether this mismatch causes any practical issue.
|
|
88
|
-
|
|
89
123
|
### 3. Holder-Scoped vs Owner-Scoped Permissions
|
|
90
124
|
|
|
91
125
|
Four permissions are checked against the **token holder**, not the project owner:
|
package/CHANGE_LOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
This document describes all changes between `nana-permission-ids` (v5) and `nana-permission-ids-v6` (v6).
|
|
4
4
|
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
- **All numeric IDs shifted** — the insertion of `LAUNCH_RULESETS` at ID 3 cascades through every subsequent permission. Any code using hardcoded numeric values will break.
|
|
8
|
+
- **Two permissions split**: `QUEUE_RULESETS` → `QUEUE_RULESETS` + `LAUNCH_RULESETS`; `SUCKER_SAFETY` → `SUCKER_SAFETY` + `SET_SUCKER_DEPRECATION`.
|
|
9
|
+
- **5 new permissions** added: `LAUNCH_RULESETS` (3), `SET_TOKEN_METADATA` (21), `SET_BUYBACK_HOOK` (28), `SET_ROUTER_TERMINAL` (29), `SET_SUCKER_DEPRECATION` (33).
|
|
10
|
+
- **2 swap terminal permissions removed**: `ADD_SWAP_TERMINAL_POOL` and `ADD_SWAP_TERMINAL_TWAP_PARAMS` (swap terminal replaced by router terminal).
|
|
11
|
+
|
|
12
|
+
> **⚠️ WARNING: All numeric permission IDs have shifted.** If your contracts, scripts, or frontends hardcode permission ID numbers (e.g., `permissions.setPermissionsFor(..., 3, ...)` for `CASH_OUT_TOKENS`), they MUST be updated. `CASH_OUT_TOKENS` moved from 3 → 4, `SEND_PAYOUTS` from 4 → 5, and so on. Always reference the named constants from `JBPermissionIds` rather than raw numbers.
|
|
13
|
+
|
|
5
14
|
---
|
|
6
15
|
|
|
7
16
|
## 1. Breaking Changes
|
|
@@ -48,6 +57,8 @@ In v5, `QUEUE_RULESETS` (2) granted permission to call both `JBController.queueR
|
|
|
48
57
|
- `QUEUE_RULESETS` (2) -- only `JBController.queueRulesetsOf`
|
|
49
58
|
- `LAUNCH_RULESETS` (3) -- only `JBController.launchRulesetsFor`
|
|
50
59
|
|
|
60
|
+
> **Cross-repo impact**: `nana-core-v6` (`JBController.launchRulesetsFor`) now requires `LAUNCH_RULESETS` instead of `QUEUE_RULESETS`. `nana-omnichain-deployers-v6` and `revnet-core-v6` both use the new `LAUNCH_RULESETS` permission.
|
|
61
|
+
|
|
51
62
|
### `SUCKER_SAFETY` split into two permissions
|
|
52
63
|
|
|
53
64
|
In v5, `SUCKER_SAFETY` (30) granted permission to call both `BPSucker.enableEmergencyHatchFor` and `BPSucker.setDeprecation`. In v6, these are separate:
|
|
@@ -64,6 +75,8 @@ The following permissions from `nana-swap-terminal` no longer exist in v6:
|
|
|
64
75
|
| `ADD_SWAP_TERMINAL_POOL` | 26 | Was for `JBSwapTerminal.addDefaultPool` |
|
|
65
76
|
| `ADD_SWAP_TERMINAL_TWAP_PARAMS` | 27 | Was for `JBSwapTerminal.addTwapParamsFor` |
|
|
66
77
|
|
|
78
|
+
> **Cross-repo impact**: `nana-router-terminal-v6` (the replacement for swap terminal) uses the new `SET_ROUTER_TERMINAL` (29) permission instead.
|
|
79
|
+
|
|
67
80
|
### Contract prefix rename (suckers)
|
|
68
81
|
|
|
69
82
|
Sucker contract references changed from `BP*` to `JB*`:
|
package/README.md
CHANGED
|
@@ -18,6 +18,15 @@ permissionsOf[operator][account][projectId] => uint256 (packed bits)
|
|
|
18
18
|
|----------|-------------|
|
|
19
19
|
| `JBPermissionIds` | Solidity library with 33 `uint8 internal constant` permission IDs (values 1--33). No state, no functions, no dependencies. Pragma `^0.8.0` for maximum compatibility. |
|
|
20
20
|
|
|
21
|
+
## Repository Layout
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
src/
|
|
25
|
+
└── JBPermissionIds.sol ── 33 uint8 constants (the only source file)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
No tests, interfaces, or deployment scripts -- this repo is a pure constant library.
|
|
29
|
+
|
|
21
30
|
## All permission IDs
|
|
22
31
|
|
|
23
32
|
### Global (ID 1)
|
|
@@ -47,7 +56,7 @@ permissionsOf[operator][account][projectId] => uint256 (packed bits)
|
|
|
47
56
|
| 16 | `SET_PRIMARY_TERMINAL` | `JBDirectory.setPrimaryTerminalOf` | Set a project's primary terminal for a given token. |
|
|
48
57
|
| 17 | `USE_ALLOWANCE` | `JBMultiTerminal.useAllowanceOf` | Use a project's surplus allowance to send funds to an arbitrary address. |
|
|
49
58
|
| 18 | `SET_SPLIT_GROUPS` | `JBController.setSplitGroupsOf` | Set a project's split groups (how payouts and reserved tokens are distributed). |
|
|
50
|
-
| 19 | `ADD_PRICE_FEED` | `JBController.
|
|
59
|
+
| 19 | `ADD_PRICE_FEED` | `JBController.addPriceFeedFor` | Add a price feed for a project. The controller checks this permission before calling `JBPrices.addPriceFeedFor`. |
|
|
51
60
|
| 20 | `ADD_ACCOUNTING_CONTEXTS` | `JBMultiTerminal.addAccountingContextsFor` | Add accounting contexts (accepted tokens) to a terminal for a project. |
|
|
52
61
|
| 21 | `SET_TOKEN_METADATA` | `JBController.setTokenMetadataOf` | Set a project token's name and symbol. Checked against the project owner. |
|
|
53
62
|
|
|
@@ -65,8 +74,8 @@ permissionsOf[operator][account][projectId] => uint256 (packed bits)
|
|
|
65
74
|
| ID | Name | Checked in | Description |
|
|
66
75
|
|----|------|------------|-------------|
|
|
67
76
|
| 26 | `SET_BUYBACK_TWAP` | `JBBuybackHook.setTwapWindowOf` | Set the TWAP (time-weighted average price) oracle window for a project's buyback hook. |
|
|
68
|
-
| 27 | `SET_BUYBACK_POOL` | `JBBuybackHook.setPoolFor`, `JBBuybackHookRegistry.
|
|
69
|
-
| 28 | `SET_BUYBACK_HOOK` |
|
|
77
|
+
| 27 | `SET_BUYBACK_POOL` | `JBBuybackHook.setPoolFor`, `JBBuybackHook.initializePoolFor`, `JBBuybackHookRegistry.setPoolFor`, `JBBuybackHookRegistry.initializePoolFor` | Set the Uniswap pool for a project's buyback hook. |
|
|
78
|
+
| 28 | `SET_BUYBACK_HOOK` | `JBBuybackHookRegistry.setHookFor`, `JBBuybackHookRegistry.lockHookFor` | Set or lock the buyback hook in the registry. Also used by `REVDeployer` as an operator permission grant. |
|
|
70
79
|
|
|
71
80
|
### Router Terminal (ID 29) -- [nana-router-terminal-v6](https://github.com/Bananapus/nana-router-terminal-v6)
|
|
72
81
|
|
package/RISKS.md
CHANGED
|
@@ -9,10 +9,13 @@ Constants-only library defining permission ID values used throughout the Bananap
|
|
|
9
9
|
- **SET_ROUTER_TERMINAL includes lock (ID 29).** Gates both `setTerminalFor` and `lockTerminalFor`. An operator can permanently lock the router terminal.
|
|
10
10
|
- **ID collision risk.** Permission IDs are manually assigned sequential uint8 values. Adding new IDs requires coordination to avoid collision. Library is append-only.
|
|
11
11
|
- **No runtime enforcement.** This library only defines constants. Enforcement happens in consuming contracts. A mismatch between the ID used here and the ID checked in a consumer would silently fail.
|
|
12
|
+
- **High-impact permission IDs (fund-moving).** IDs that control fund flow should receive the most audit scrutiny: `ROOT` (1, grants everything), `CASH_OUT_TOKENS` (4, triggers withdrawals), `SEND_PAYOUTS` (5, triggers payout distribution), `MIGRATE_TERMINAL` (6, moves balances), `SET_TERMINALS` (15, redirects all fund flows), `USE_ALLOWANCE` (17, draws from surplus), `SET_SPLIT_GROUPS` (18, controls where payouts go). Of these, ROOT + SET_TERMINALS + MIGRATE_TERMINAL are the most dangerous — they can redirect all of a project's funds.
|
|
13
|
+
- **Wildcard `projectId=0` semantics.** When a permission is granted with `projectId=0`, it applies to ALL projects. This is used by system contracts (e.g., `REVLoans` gets `USE_ALLOWANCE` with `projectId=0`). A bug in a contract holding wildcard permissions affects every project in the ecosystem, not just one. Only system-level contracts should hold wildcard permissions.
|
|
12
14
|
|
|
13
15
|
## 2. Design Notes
|
|
14
16
|
|
|
15
17
|
- Permission 0 is reserved and cannot be set.
|
|
16
18
|
- IDs are `uint8` (0-255), with 1-33 currently assigned.
|
|
17
19
|
- IDs 34-255 are available for future ecosystem extensions.
|
|
20
|
+
- IDs 34-255 are available for ecosystem extensions. Third-party contracts can define their own permission IDs in this range, but must coordinate to avoid collisions. No on-chain registry exists for custom IDs — collision detection is purely social.
|
|
18
21
|
- This library has zero dependencies -- it is the leaf of the dependency graph.
|
package/SKILLS.md
CHANGED
|
@@ -16,16 +16,7 @@ N/A -- this is a constants-only library with no callable functions.
|
|
|
16
16
|
|
|
17
17
|
## How permissions work
|
|
18
18
|
|
|
19
|
-
Permissions are stored in `JBPermissions`
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
permissionsOf[operator][account][projectId] => uint256 (one bit per permission ID)
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
When a permissioned function is called, the contract checks whether the caller either **is** the account or **has** the required permission ID for that project. The check also considers:
|
|
26
|
-
|
|
27
|
-
1. **ROOT override** -- if the operator has ROOT (ID 1) for the project (or wildcard), all permission checks pass.
|
|
28
|
-
2. **Wildcard project ID** -- permissions granted with `projectId = 0` apply to all projects for that account.
|
|
19
|
+
Permissions are stored as 256-bit packed integers in `JBPermissions`, keyed by `[operator][account][projectId]`. ROOT (ID 1) passes all checks; `projectId = 0` grants cross-project access. See `nana-core-v6/SKILLS.md` for full permissions mechanics.
|
|
29
20
|
|
|
30
21
|
## All Permission IDs
|
|
31
22
|
|
|
@@ -56,7 +47,7 @@ When a permissioned function is called, the contract checks whether the caller e
|
|
|
56
47
|
| 16 | `SET_PRIMARY_TERMINAL` | `JBDirectory.setPrimaryTerminalOf` | Set the primary terminal for a given token. Checked against project owner. |
|
|
57
48
|
| 17 | `USE_ALLOWANCE` | `JBMultiTerminal.useAllowanceOf` | Use surplus allowance to send funds to an arbitrary address. Checked against project owner. |
|
|
58
49
|
| 18 | `SET_SPLIT_GROUPS` | `JBController.setSplitGroupsOf` | Set how payouts and reserved tokens are distributed. Checked against project owner. |
|
|
59
|
-
| 19 | `ADD_PRICE_FEED` | `JBController.
|
|
50
|
+
| 19 | `ADD_PRICE_FEED` | `JBController.addPriceFeedFor` | Add a price feed for a project. The controller checks this permission, then calls `JBPrices.addPriceFeedFor` internally. Checked against project owner. |
|
|
60
51
|
| 20 | `ADD_ACCOUNTING_CONTEXTS` | `JBMultiTerminal.addAccountingContextsFor` | Add accepted token accounting contexts to a terminal. Checked against project owner. |
|
|
61
52
|
| 21 | `SET_TOKEN_METADATA` | `JBController.setTokenMetadataOf` | Set a project token's name and symbol. Checked against project owner. |
|
|
62
53
|
|
|
@@ -74,8 +65,8 @@ When a permissioned function is called, the contract checks whether the caller e
|
|
|
74
65
|
| ID | Name | Checked in | Permission scope |
|
|
75
66
|
|----|------|------------|-----------------|
|
|
76
67
|
| 26 | `SET_BUYBACK_TWAP` | `JBBuybackHook.setTwapWindowOf` | Set the TWAP oracle window duration. Checked against project owner. |
|
|
77
|
-
| 27 | `SET_BUYBACK_POOL` | `JBBuybackHook.setPoolFor`, `
|
|
78
|
-
| 28 | `SET_BUYBACK_HOOK` |
|
|
68
|
+
| 27 | `SET_BUYBACK_POOL` | `JBBuybackHook.setPoolFor`, `JBBuybackHook.initializePoolFor`, `JBBuybackHookRegistry.initializePoolFor` | Set the Uniswap pool for a project's buyback. Checked against project owner. |
|
|
69
|
+
| 28 | `SET_BUYBACK_HOOK` | `JBBuybackHookRegistry.setHookFor`, `JBBuybackHookRegistry.lockHookFor` | Set or lock the buyback hook implementation for a project. Checked against project owner. Also granted by `REVDeployer` as an operator permission. |
|
|
79
70
|
|
|
80
71
|
### nana-router-terminal-v6
|
|
81
72
|
|
|
@@ -92,6 +83,20 @@ When a permissioned function is called, the contract checks whether the caller e
|
|
|
92
83
|
| 32 | `SUCKER_SAFETY` | `JBSucker.enableEmergencyHatchFor` | Enable the emergency hatch to recover stuck tokens. Checked against project owner. |
|
|
93
84
|
| 33 | `SET_SUCKER_DEPRECATION` | `JBSucker.setDeprecation` | Move a sucker through the deprecation lifecycle (ENABLED -> DEPRECATION_PENDING -> SENDING_DISABLED -> DEPRECATED). Checked against project owner. |
|
|
94
85
|
|
|
86
|
+
## Common Permission Bundles
|
|
87
|
+
|
|
88
|
+
Typical combinations when granting operator access via `JBPermissions.setPermissionsFor`:
|
|
89
|
+
|
|
90
|
+
| Bundle | IDs | Use case |
|
|
91
|
+
|--------|-----|----------|
|
|
92
|
+
| **Deployer operator** | `QUEUE_RULESETS` (2), `SET_SPLIT_GROUPS` (18), `SET_FUND_ACCESS_LIMITS` (not in this library -- set via ruleset config) | Operator that manages project rulesets and split configuration on behalf of the owner. |
|
|
93
|
+
| **Token manager** | `MINT_TOKENS` (10), `BURN_TOKENS` (11), `SET_TOKEN_METADATA` (21) | Operator that manages token supply and metadata. |
|
|
94
|
+
| **Treasury manager** | `SEND_PAYOUTS` (5), `USE_ALLOWANCE` (17), `ADD_ACCOUNTING_CONTEXTS` (20) | Operator that manages outflows from the project treasury. |
|
|
95
|
+
| **Project admin** | `SET_PROJECT_URI` (7), `DEPLOY_ERC20` (8), `SET_CONTROLLER` (14), `SET_TERMINALS` (15), `SET_PRIMARY_TERMINAL` (16) | Broad administrative access without ROOT. |
|
|
96
|
+
| **NFT manager** | `ADJUST_721_TIERS` (22), `SET_721_METADATA` (23), `MINT_721` (24), `SET_721_DISCOUNT_PERCENT` (25) | Full control over 721 tier configuration. |
|
|
97
|
+
| **Cross-chain operator** | `DEPLOY_SUCKERS` (31), `MAP_SUCKER_TOKEN` (30), `SET_SUCKER_DEPRECATION` (33) | Manages cross-chain bridging lifecycle. |
|
|
98
|
+
| **Launch bundle** | `LAUNCH_RULESETS` (3), `SET_TERMINALS` (15) | Both are required for `launchRulesetsFor` -- ID 3 alone is insufficient. |
|
|
99
|
+
|
|
95
100
|
## Integration Points
|
|
96
101
|
|
|
97
102
|
| Dependency | Import | Used For |
|
|
@@ -125,7 +130,7 @@ N/A -- no structs or enums. All values are `uint8 internal constant`.
|
|
|
125
130
|
- **SET_TERMINALS (ID 15) can break a project.** Replacing the terminal list without including the current primary terminal will remove it, breaking payments and cashouts until a new primary is set.
|
|
126
131
|
- **LAUNCH_RULESETS (ID 3) requires both IDs 3 and 15.** The function enforces two separate permission checks because it configures terminals in addition to launching rulesets.
|
|
127
132
|
- **Holder-scoped permissions.** IDs 4 (`CASH_OUT_TOKENS`), 11 (`BURN_TOKENS`), 12 (`CLAIM_TOKENS`), and 13 (`TRANSFER_CREDITS`) are checked against the **token holder**, not the project owner. This means a holder grants an operator permission to act on the holder's own tokens.
|
|
128
|
-
- **SET_BUYBACK_HOOK (ID 28)
|
|
133
|
+
- **SET_BUYBACK_POOL (ID 27) vs SET_BUYBACK_HOOK (ID 28) — different scopes.** ID 27 guards pool configuration (`setPoolFor`, `initializePoolFor`). ID 28 guards hook selection (`setHookFor`, `lockHookFor`). `setTwapWindowOf` uses ID 26 (`SET_BUYBACK_TWAP`). Grant all three if the operator needs full buyback management.
|
|
129
134
|
- **ADD_PRICE_FEED (ID 19) is checked on JBController, not JBPrices.** The permission gate is on `JBController.addPriceFeed`, which then calls `JBPrices.addPriceFeedFor` internally.
|
|
130
135
|
- **uint8 range.** IDs are `uint8` (0--255) but the packed storage is `uint256`, so the system supports up to 256 permission bits. Currently 33 are defined (1--33).
|
|
131
136
|
|
package/STYLE_GUIDE.md
CHANGED
|
@@ -21,7 +21,7 @@ One contract/interface/struct/enum per file. Name the file after the type it con
|
|
|
21
21
|
|
|
22
22
|
```solidity
|
|
23
23
|
// Contracts — pin to exact version
|
|
24
|
-
pragma solidity 0.8.
|
|
24
|
+
pragma solidity 0.8.28;
|
|
25
25
|
|
|
26
26
|
// Interfaces, structs, enums — caret for forward compatibility
|
|
27
27
|
pragma solidity ^0.8.0;
|
|
@@ -313,7 +313,7 @@ Standard config across all repos:
|
|
|
313
313
|
|
|
314
314
|
```toml
|
|
315
315
|
[profile.default]
|
|
316
|
-
solc = '0.8.
|
|
316
|
+
solc = '0.8.28'
|
|
317
317
|
evm_version = 'cancun'
|
|
318
318
|
optimizer_runs = 200
|
|
319
319
|
libs = ["node_modules", "lib"]
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
# User Journeys -- nana-permission-ids-v6
|
|
2
2
|
|
|
3
|
-
Since this is a constants-only library with no runtime behavior, these journeys describe how the permission IDs are used by actors across the ecosystem. Each journey shows the constant's role in a concrete access control scenario.
|
|
3
|
+
Since this is a constants-only library with no runtime behavior, these journeys describe how the permission IDs are used by actors across the ecosystem. Each journey shows the constant's role in a concrete access control scenario. All events referenced below are emitted by `JBPermissions` (in nana-core-v6), not by this library.
|
|
4
|
+
|
|
5
|
+
---
|
|
4
6
|
|
|
5
7
|
## Journey 1: Grant an Operator Permission to Queue Rulesets
|
|
6
8
|
|
|
7
|
-
**
|
|
8
|
-
|
|
9
|
+
**Entry point**: `JBPermissions.setPermissionsFor(address account, JBPermissionsData calldata permissionsData)`
|
|
10
|
+
|
|
11
|
+
**Who can call**: The `account` itself, or an existing operator that holds `ROOT` for the same `(account, projectId)` -- provided the new permissions do not include `ROOT` and the `projectId` is not the wildcard (`0`).
|
|
12
|
+
|
|
13
|
+
**Parameters**:
|
|
14
|
+
- `account` -- The address granting permissions (the project owner in this scenario)
|
|
15
|
+
- `permissionsData.operator` -- The address receiving permissions (the trusted operator)
|
|
16
|
+
- `permissionsData.projectId` -- The project ID the permissions are scoped to (e.g. `5`)
|
|
17
|
+
- `permissionsData.permissionIds` -- Array of `uint8` permission IDs to grant (e.g. `[JBPermissionIds.QUEUE_RULESETS]` which is ID 2)
|
|
9
18
|
|
|
10
19
|
### Steps
|
|
11
20
|
|
|
@@ -20,25 +29,36 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
20
29
|
);
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
- Sets bit 2 in `permissionsOf[operatorAddress][projectOwner][5]`
|
|
24
|
-
|
|
25
32
|
2. **Operator calls `JBController.queueRulesetsOf(5, ...)`**
|
|
26
33
|
|
|
27
34
|
- Controller calls `_requirePermissionFrom(projectOwner, 5, JBPermissionIds.QUEUE_RULESETS)`
|
|
28
35
|
- `JBPermissions.hasPermission` checks: does `operatorAddress` have bit 2 set for `(projectOwner, projectId=5)`? Yes.
|
|
29
36
|
- Operation proceeds.
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
**State changes**:
|
|
39
|
+
1. `JBPermissions.permissionsOf[operatorAddress][projectOwner][5]` -- bit 2 set to 1 (packed uint256 bitmap)
|
|
40
|
+
|
|
41
|
+
**Events**: `OperatorPermissionsSet(operator, account, projectId, permissionIds, packed, caller)` -- emitted by `JBPermissions`, not this library.
|
|
32
42
|
|
|
43
|
+
**Edge cases**:
|
|
33
44
|
- The operator can ONLY queue rulesets. They cannot send payouts, set terminals, or perform any other operation unless additional IDs are granted.
|
|
34
45
|
- Granting `QUEUE_RULESETS` with `projectId = 0` (wildcard) would allow the operator to queue rulesets for ALL projects the owner controls.
|
|
46
|
+
- `JBPermissions_NoZeroPermission()` -- reverts if permission ID 0 is included in the array.
|
|
47
|
+
- `JBPermissions_PermissionIdOutOfBounds(permissionId)` -- reverts if any permission ID exceeds 255.
|
|
35
48
|
|
|
36
49
|
---
|
|
37
50
|
|
|
38
51
|
## Journey 2: ROOT Permission Grants Universal Access
|
|
39
52
|
|
|
40
|
-
**
|
|
41
|
-
|
|
53
|
+
**Entry point**: `JBPermissions.setPermissionsFor(address account, JBPermissionsData calldata permissionsData)`
|
|
54
|
+
|
|
55
|
+
**Who can call**: Only the `account` itself can grant `ROOT`. An existing ROOT operator cannot grant ROOT to others (the function enforces this restriction explicitly).
|
|
56
|
+
|
|
57
|
+
**Parameters**:
|
|
58
|
+
- `account` -- The address granting ROOT (must be the caller)
|
|
59
|
+
- `permissionsData.operator` -- The address receiving ROOT (e.g. a trusted multisig)
|
|
60
|
+
- `permissionsData.projectId` -- Must be a specific project ID (not `0`)
|
|
61
|
+
- `permissionsData.permissionIds` -- `[JBPermissionIds.ROOT]` (ID 1)
|
|
42
62
|
|
|
43
63
|
### Steps
|
|
44
64
|
|
|
@@ -53,26 +73,36 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
53
73
|
);
|
|
54
74
|
```
|
|
55
75
|
|
|
56
|
-
- Sets bit 1 in `permissionsOf[multisigAddress][projectOwner][5]`
|
|
57
|
-
|
|
58
76
|
2. **Multisig calls any permissioned function for project 5**
|
|
59
77
|
|
|
60
78
|
- Every `_requirePermissionFrom` check includes `includeRoot: true`
|
|
61
79
|
- ROOT (bit 1) satisfies any permission check for `(projectOwner, projectId=5)`
|
|
62
80
|
- The multisig can queue rulesets, send payouts, set terminals, mint tokens, etc.
|
|
63
81
|
|
|
64
|
-
|
|
82
|
+
**State changes**:
|
|
83
|
+
1. `JBPermissions.permissionsOf[multisigAddress][projectOwner][5]` -- bit 1 set to 1
|
|
84
|
+
|
|
85
|
+
**Events**: `OperatorPermissionsSet(operator, account, projectId, permissionIds, packed, caller)` -- emitted by `JBPermissions`, not this library.
|
|
65
86
|
|
|
66
|
-
|
|
67
|
-
-
|
|
87
|
+
**Edge cases**:
|
|
88
|
+
- `JBPermissions_CantSetRootPermissionForWildcardProject()` -- reverts if ROOT is granted with `projectId = 0`.
|
|
89
|
+
- `JBPermissions_Unauthorized(account, operator, projectId, permissionId)` -- reverts if a non-account caller (even a ROOT operator) tries to grant ROOT to another operator, or tries to set wildcard permissions.
|
|
68
90
|
- ROOT is per-project. Having ROOT for project 5 does not grant access to project 6.
|
|
91
|
+
- A ROOT operator can call `setPermissionsFor` on behalf of the account for non-ROOT, non-wildcard grants only. This prevents permission escalation.
|
|
69
92
|
|
|
70
93
|
---
|
|
71
94
|
|
|
72
95
|
## Journey 3: Token Holder Delegates Cashout Authority
|
|
73
96
|
|
|
74
|
-
**
|
|
75
|
-
|
|
97
|
+
**Entry point**: `JBPermissions.setPermissionsFor(address account, JBPermissionsData calldata permissionsData)`
|
|
98
|
+
|
|
99
|
+
**Who can call**: The token holder (the `account`), or an existing ROOT operator for the holder on the relevant project.
|
|
100
|
+
|
|
101
|
+
**Parameters**:
|
|
102
|
+
- `account` -- The token holder granting permission (NOT the project owner)
|
|
103
|
+
- `permissionsData.operator` -- The bot or delegate receiving cashout permission
|
|
104
|
+
- `permissionsData.projectId` -- The project ID the tokens belong to (e.g. `5`)
|
|
105
|
+
- `permissionsData.permissionIds` -- `[JBPermissionIds.CASH_OUT_TOKENS]` (ID 4)
|
|
76
106
|
|
|
77
107
|
### Steps
|
|
78
108
|
|
|
@@ -94,8 +124,12 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
94
124
|
- Terminal calls `_requirePermissionFrom(tokenHolder, 5, JBPermissionIds.CASH_OUT_TOKENS)`
|
|
95
125
|
- Permission check passes for `botAddress` because it has `CASH_OUT_TOKENS` for `(tokenHolder, 5)`
|
|
96
126
|
|
|
97
|
-
|
|
127
|
+
**State changes**:
|
|
128
|
+
1. `JBPermissions.permissionsOf[botAddress][tokenHolder][5]` -- bit 4 set to 1
|
|
129
|
+
|
|
130
|
+
**Events**: `OperatorPermissionsSet(operator, account, projectId, permissionIds, packed, caller)` -- emitted by `JBPermissions`, not this library.
|
|
98
131
|
|
|
132
|
+
**Edge cases**:
|
|
99
133
|
- `CASH_OUT_TOKENS` (ID 4) is checked against the token holder, not the project owner. This is by design -- only the holder (or their delegates) can cash out the holder's tokens.
|
|
100
134
|
- The project owner CANNOT cash out another holder's tokens (unless the holder explicitly grants them `CASH_OUT_TOKENS`).
|
|
101
135
|
- The same holder-scoped pattern applies to `BURN_TOKENS` (11), `CLAIM_TOKENS` (12), and `TRANSFER_CREDITS` (13).
|
|
@@ -104,8 +138,13 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
104
138
|
|
|
105
139
|
## Journey 4: SET_TERMINALS Can Break a Project
|
|
106
140
|
|
|
107
|
-
**
|
|
108
|
-
|
|
141
|
+
**Entry point**: `JBDirectory.setTerminalsOf(uint256 projectId, IJBTerminal[] calldata terminals)`
|
|
142
|
+
|
|
143
|
+
**Who can call**: The project owner, or an address with the owner's `SET_TERMINALS` (ID 15) permission for that project.
|
|
144
|
+
|
|
145
|
+
**Parameters**:
|
|
146
|
+
- `projectId` -- The project whose terminals are being updated
|
|
147
|
+
- `terminals` -- The new complete list of terminals (replaces the entire existing list)
|
|
109
148
|
|
|
110
149
|
### Steps
|
|
111
150
|
|
|
@@ -122,8 +161,13 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
122
161
|
- All payouts via `sendPayoutsOf()` will fail
|
|
123
162
|
- The project is effectively frozen until a new primary terminal is set
|
|
124
163
|
|
|
125
|
-
|
|
164
|
+
**State changes**:
|
|
165
|
+
1. `JBDirectory._terminalsOf[projectId]` -- replaced with the new terminals array
|
|
166
|
+
2. `JBDirectory._primaryTerminalOf[projectId][token]` -- may be implicitly cleared if the primary terminal is not in the new list
|
|
167
|
+
|
|
168
|
+
**Events**: `SetTerminals(projectId, terminals, caller)` -- emitted by `JBDirectory` (in nana-core-v6), not this library.
|
|
126
169
|
|
|
170
|
+
**Edge cases**:
|
|
127
171
|
- Granting `SET_TERMINALS` to an untrusted operator is dangerous. The operator can remove all terminals, bricking the project.
|
|
128
172
|
- `LAUNCH_RULESETS` (ID 3) also requires `SET_TERMINALS` (ID 15) because the launch function configures terminals. Granting only ID 3 without ID 15 will cause the launch to revert.
|
|
129
173
|
- There is no undo mechanism. Once terminals are set, another `setTerminalsOf` call is needed to restore them.
|
|
@@ -132,8 +176,15 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
132
176
|
|
|
133
177
|
## Journey 5: Locking a Buyback Hook or Router Terminal (Permanent Action)
|
|
134
178
|
|
|
135
|
-
**
|
|
136
|
-
|
|
179
|
+
**Entry point (buyback hook)**:
|
|
180
|
+
- `JBBuybackHookRegistry.setHookFor(uint256 projectId, IJBRulesetDataHook hook)` -- configures the hook
|
|
181
|
+
- `JBBuybackHookRegistry.lockHookFor(uint256 projectId, IJBRulesetDataHook expectedHook)` -- permanently locks the configuration
|
|
182
|
+
|
|
183
|
+
**Who can call**: The project owner, or an address with the owner's `SET_BUYBACK_HOOK` (ID 28) permission for that project. A single permission ID gates both operations.
|
|
184
|
+
|
|
185
|
+
**Parameters**:
|
|
186
|
+
- `projectId` -- The project whose buyback hook is being configured or locked
|
|
187
|
+
- `hook` -- The buyback hook contract address (for `setHookFor` only)
|
|
137
188
|
|
|
138
189
|
### Steps (SET_BUYBACK_HOOK example)
|
|
139
190
|
|
|
@@ -142,13 +193,19 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
142
193
|
- Configures the buyback hook for project 5
|
|
143
194
|
- This is a reversible operation (can be called again with a different hook)
|
|
144
195
|
|
|
145
|
-
2. **Same operator calls `JBBuybackHookRegistry.lockHookFor(5)`**
|
|
196
|
+
2. **Same operator calls `JBBuybackHookRegistry.lockHookFor(5, hookAddress)`**
|
|
146
197
|
|
|
198
|
+
- The `expectedHook` parameter is a race-condition guard: the call reverts with `JBBuybackHookRegistry_HookMismatch` if the on-chain hook differs from `expectedHook` at execution time
|
|
147
199
|
- Permanently locks the hook configuration for project 5
|
|
148
200
|
- No one can change the hook after locking -- not even the project owner
|
|
149
201
|
|
|
150
|
-
|
|
202
|
+
**State changes**:
|
|
203
|
+
1. `JBBuybackHookRegistry._hookOf[projectId]` -- set to the hook address (step 1)
|
|
204
|
+
2. `JBBuybackHookRegistry.hasLockedHook[projectId]` -- set to `true` (step 2, irreversible)
|
|
205
|
+
|
|
206
|
+
**Events**: Events are emitted by `JBBuybackHookRegistry` (in nana-buyback-hook-v6), not this library.
|
|
151
207
|
|
|
208
|
+
**Edge cases**:
|
|
152
209
|
- A single permission ID (28 or 29) gates BOTH the "set" and "lock" operations. Granting the permission implicitly trusts the operator to potentially lock the configuration.
|
|
153
210
|
- The locking is permanent (no unlock mechanism in the registry).
|
|
154
211
|
- Project owners should only grant SET_BUYBACK_HOOK (28) or SET_ROUTER_TERMINAL (29) to operators they trust not to lock prematurely.
|
|
@@ -157,31 +214,87 @@ Since this is a constants-only library with no runtime behavior, these journeys
|
|
|
157
214
|
|
|
158
215
|
## Journey 6: Cross-Repo Permission Usage (nana-suckers)
|
|
159
216
|
|
|
160
|
-
**
|
|
161
|
-
|
|
217
|
+
**Entry point**: Multiple functions across `JBSuckerRegistry` and `JBSucker`, each gated by a separate permission ID.
|
|
218
|
+
|
|
219
|
+
**Who can call**: The project owner, or an address with the corresponding permission for that project. Each sucker permission is independent.
|
|
220
|
+
|
|
221
|
+
**Parameters** (vary by operation):
|
|
222
|
+
- `projectId` -- The project managing cross-chain infrastructure
|
|
223
|
+
- `configurations` -- Sucker deployment configurations (for `deploySuckersFor`)
|
|
224
|
+
- `map` -- A `JBTokenMapping` struct (for `mapToken`)
|
|
225
|
+
- `tokens` -- Token addresses (`address[]`) for emergency recovery (for `enableEmergencyHatchFor`)
|
|
226
|
+
- `timestamp` -- The timestamp after which the sucker is deprecated (for `setDeprecation`)
|
|
162
227
|
|
|
163
228
|
### Steps
|
|
164
229
|
|
|
165
|
-
1. **Deploy suckers: `JBSuckerRegistry.deploySuckersFor(5,
|
|
230
|
+
1. **Deploy suckers: `JBSuckerRegistry.deploySuckersFor(5, salt, configurations)`**
|
|
166
231
|
- Permission: `DEPLOY_SUCKERS` (ID 31)
|
|
167
232
|
- Creates sucker contracts for cross-chain bridging
|
|
168
233
|
|
|
169
|
-
2. **Map tokens: `JBSucker.mapToken(
|
|
234
|
+
2. **Map tokens: `JBSucker.mapToken(map)`** where `map` is a `JBTokenMapping` struct
|
|
170
235
|
- Permission: `MAP_SUCKER_TOKEN` (ID 30)
|
|
171
236
|
- Maps a local ERC-20 to its remote chain counterpart
|
|
172
237
|
- CAUTION: once the outbox merkle tree has entries, the mapping is immutable (can only be disabled, not remapped)
|
|
173
238
|
|
|
174
|
-
3. **Enable emergency hatch: `JBSucker.enableEmergencyHatchFor(
|
|
239
|
+
3. **Enable emergency hatch: `JBSucker.enableEmergencyHatchFor(tokens)`**
|
|
175
240
|
- Permission: `SUCKER_SAFETY` (ID 32)
|
|
176
241
|
- Allows recovery of stuck tokens via the emergency hatch
|
|
177
242
|
|
|
178
|
-
4. **Deprecate sucker: `JBSucker.setDeprecation(
|
|
243
|
+
4. **Deprecate sucker: `JBSucker.setDeprecation(timestamp)`**
|
|
179
244
|
- Permission: `SET_SUCKER_DEPRECATION` (ID 33)
|
|
180
|
-
-
|
|
245
|
+
- The timestamp after which the sucker is deprecated.
|
|
246
|
+
|
|
247
|
+
**State changes** (per step):
|
|
248
|
+
1. `JBSuckerRegistry` -- deploys new sucker contracts, registers them for the project
|
|
249
|
+
2. `JBSucker._remoteTokenFor[localToken]` -- set to the remote token mapping (immutable once outbox tree populated)
|
|
250
|
+
3. `JBSucker._remoteTokenFor[token].enabled` -- set to `false` (bridging disabled for this token)
|
|
251
|
+
4. `JBSucker._remoteTokenFor[token].emergencyHatch` -- set to `true`
|
|
252
|
+
5. `JBSucker.deprecatedAfter` -- set to `timestamp` (the time after which the sucker is deprecated, or `0` to cancel a pending deprecation)
|
|
181
253
|
|
|
182
|
-
|
|
254
|
+
**Events**: Events are emitted by `JBSuckerRegistry` and `JBSucker` (in nana-suckers-v6), not this library.
|
|
183
255
|
|
|
256
|
+
**Edge cases**:
|
|
184
257
|
- Each sucker permission is independent. Having `DEPLOY_SUCKERS` does not grant `MAP_SUCKER_TOKEN` or `SUCKER_SAFETY`.
|
|
185
258
|
- `MAP_SUCKER_TOKEN` is especially sensitive because token mappings become immutable once the outbox tree has entries.
|
|
186
259
|
- `SUCKER_SAFETY` should be granted sparingly -- the emergency hatch is a last-resort recovery mechanism.
|
|
187
260
|
- All four sucker permissions are checked against the project owner, not a token holder.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Permission ID Reference
|
|
265
|
+
|
|
266
|
+
| ID | Constant | Gated function(s) | Scope |
|
|
267
|
+
|----|----------|-------------------|-------|
|
|
268
|
+
| 1 | `ROOT` | All permissioned functions | Project owner |
|
|
269
|
+
| 2 | `QUEUE_RULESETS` | `JBController.queueRulesetsOf` | Project owner |
|
|
270
|
+
| 3 | `LAUNCH_RULESETS` | `JBController.launchRulesetsFor` | Project owner |
|
|
271
|
+
| 4 | `CASH_OUT_TOKENS` | `JBMultiTerminal.cashOutTokensOf` | Token holder |
|
|
272
|
+
| 5 | `SEND_PAYOUTS` | `JBMultiTerminal.sendPayoutsOf` | Project owner |
|
|
273
|
+
| 6 | `MIGRATE_TERMINAL` | `JBMultiTerminal.migrateBalanceOf` | Project owner |
|
|
274
|
+
| 7 | `SET_PROJECT_URI` | `JBController.setUriOf` | Project owner |
|
|
275
|
+
| 8 | `DEPLOY_ERC20` | `JBController.deployERC20For` | Project owner |
|
|
276
|
+
| 9 | `SET_TOKEN` | `JBController.setTokenFor` | Project owner |
|
|
277
|
+
| 10 | `MINT_TOKENS` | `JBController.mintTokensOf` | Project owner |
|
|
278
|
+
| 11 | `BURN_TOKENS` | `JBController.burnTokensOf` | Token holder |
|
|
279
|
+
| 12 | `CLAIM_TOKENS` | `JBController.claimTokensFor` | Token holder |
|
|
280
|
+
| 13 | `TRANSFER_CREDITS` | `JBController.transferCreditsFrom` | Token holder |
|
|
281
|
+
| 14 | `SET_CONTROLLER` | `JBDirectory.setControllerOf` | Project owner |
|
|
282
|
+
| 15 | `SET_TERMINALS` | `JBDirectory.setTerminalsOf` | Project owner |
|
|
283
|
+
| 16 | `SET_PRIMARY_TERMINAL` | `JBDirectory.setPrimaryTerminalOf` | Project owner |
|
|
284
|
+
| 17 | `USE_ALLOWANCE` | `JBMultiTerminal.useAllowanceOf` | Project owner |
|
|
285
|
+
| 18 | `SET_SPLIT_GROUPS` | `JBController.setSplitGroupsOf` | Project owner |
|
|
286
|
+
| 19 | `ADD_PRICE_FEED` | `JBController.addPriceFeedFor` | Project owner |
|
|
287
|
+
| 20 | `ADD_ACCOUNTING_CONTEXTS` | `JBMultiTerminal.addAccountingContextsFor` | Project owner |
|
|
288
|
+
| 21 | `SET_TOKEN_METADATA` | `JBController.setTokenMetadataOf` | Project owner |
|
|
289
|
+
| 22 | `ADJUST_721_TIERS` | `JB721TiersHook.adjustTiers` | Project owner |
|
|
290
|
+
| 23 | `SET_721_METADATA` | `JB721TiersHook.setMetadata` | Project owner |
|
|
291
|
+
| 24 | `MINT_721` | `JB721TiersHook.mintFor` | Project owner |
|
|
292
|
+
| 25 | `SET_721_DISCOUNT_PERCENT` | `JB721TiersHook.setDiscountPercentOf` | Project owner |
|
|
293
|
+
| 26 | `SET_BUYBACK_TWAP` | `JBBuybackHook.setTwapWindowOf` | Project owner |
|
|
294
|
+
| 27 | `SET_BUYBACK_POOL` | `JBBuybackHook.setPoolFor` | Project owner |
|
|
295
|
+
| 28 | `SET_BUYBACK_HOOK` | `JBBuybackHookRegistry.setHookFor` + `lockHookFor` | Project owner |
|
|
296
|
+
| 29 | `SET_ROUTER_TERMINAL` | `JBRouterTerminalRegistry.setTerminalFor` + `lockTerminalFor` | Project owner |
|
|
297
|
+
| 30 | `MAP_SUCKER_TOKEN` | `JBSucker.mapToken` | Project owner |
|
|
298
|
+
| 31 | `DEPLOY_SUCKERS` | `JBSuckerRegistry.deploySuckersFor` | Project owner |
|
|
299
|
+
| 32 | `SUCKER_SAFETY` | `JBSucker.enableEmergencyHatchFor` | Project owner |
|
|
300
|
+
| 33 | `SET_SUCKER_DEPRECATION` | `JBSucker.setDeprecation` | Project owner |
|
package/foundry.toml
CHANGED
package/package.json
CHANGED
package/src/JBPermissionIds.sol
CHANGED
|
@@ -28,7 +28,7 @@ library JBPermissionIds {
|
|
|
28
28
|
uint8 internal constant SET_PRIMARY_TERMINAL = 16; // Permission to call `JBDirectory.setPrimaryTerminalOf`.
|
|
29
29
|
uint8 internal constant USE_ALLOWANCE = 17; // Permission to call `JBMultiTerminal.useAllowanceOf`.
|
|
30
30
|
uint8 internal constant SET_SPLIT_GROUPS = 18; // Permission to call `JBController.setSplitGroupsOf`.
|
|
31
|
-
uint8 internal constant ADD_PRICE_FEED = 19; // Permission to call `JBController.
|
|
31
|
+
uint8 internal constant ADD_PRICE_FEED = 19; // Permission to call `JBController.addPriceFeedFor`.
|
|
32
32
|
uint8 internal constant ADD_ACCOUNTING_CONTEXTS = 20; // Permission to call
|
|
33
33
|
// `JBMultiTerminal.addAccountingContextsFor`.
|
|
34
34
|
uint8 internal constant SET_TOKEN_METADATA = 21; // Permission to call
|