@bananapus/ownable-v6 0.0.19 → 0.0.21
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 +21 -21
- package/ARCHITECTURE.md +19 -17
- package/AUDIT_INSTRUCTIONS.md +11 -10
- package/README.md +19 -19
- package/SKILLS.md +5 -5
- package/USER_JOURNEYS.md +5 -7
- package/package.json +3 -3
- package/src/JBOwnableOverrides.sol +11 -0
- package/test/regression/RootPermissionBypassesPermissionIdZero.t.sol +87 -0
package/ADMINISTRATION.md
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
## Purpose
|
|
13
13
|
|
|
14
|
-
`nana-ownable-v6` does not
|
|
14
|
+
`nana-ownable-v6` does not add a new admin surface by itself. It defines how ownership is resolved for other repos. The important question is how a contract's `owner()` is determined and how delegated permission IDs behave across ownership transfers.
|
|
15
15
|
|
|
16
16
|
## Control Model
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
18
|
+
- ownership can be address-based or project-based
|
|
19
|
+
- delegated operator checks run through `JBPermissions`
|
|
20
|
+
- transfer and renounce semantics are part of the primitive
|
|
21
|
+
- delegated permission resets on ownership transfer
|
|
22
22
|
|
|
23
23
|
## Roles
|
|
24
24
|
|
|
@@ -40,34 +40,34 @@ The meaningful control surfaces are inherited by downstream contracts:
|
|
|
40
40
|
|
|
41
41
|
## Immutable And One-Way
|
|
42
42
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
43
|
+
- project ownership changes dynamically with project NFT transfers
|
|
44
|
+
- delegated permission ID resets on ownership transfer
|
|
45
|
+
- renouncing ownership is final unless the inheriting contract adds a separate recovery path
|
|
46
46
|
|
|
47
47
|
## Operational Notes
|
|
48
48
|
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
49
|
+
- treat project-based ownership as live routing, not a snapshot
|
|
50
|
+
- do not assume an operator permission survives ownership transfer
|
|
51
|
+
- treat `setPermissionId(...)` as a real authority change because it rewires which delegated permission bit counts as owner access
|
|
52
|
+
- review the inheriting contract, not just this primitive, to understand the full admin surface
|
|
53
53
|
|
|
54
54
|
## Machine Notes
|
|
55
55
|
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
56
|
+
- do not conclude authority from this repo alone; follow the inheriting contract's `onlyOwner` surfaces
|
|
57
|
+
- treat ownership transfer as potentially changing both the owner identity and the usable delegated permission ID
|
|
58
|
+
- if the current permission ID is undocumented, inspect `jbOwner.permissionId` before reasoning about delegated owner access
|
|
59
|
+
- if a downstream repo uses project-based ownership, re-evaluate owner resolution after every project NFT transfer
|
|
60
60
|
|
|
61
61
|
## Recovery
|
|
62
62
|
|
|
63
|
-
-
|
|
64
|
-
-
|
|
63
|
+
- this primitive has no protocol-wide recovery surface
|
|
64
|
+
- if ownership was transferred to the wrong project or address, recovery depends on the inheriting contract still recognizing the current owner
|
|
65
65
|
|
|
66
66
|
## Admin Boundaries
|
|
67
67
|
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
68
|
+
- this repo does not create a new permission namespace
|
|
69
|
+
- it cannot make an inheriting contract safer than that contract's own privileged functions
|
|
70
|
+
- it cannot preserve delegated operators across ownership transfer by default
|
|
71
71
|
|
|
72
72
|
## Source Map
|
|
73
73
|
|
package/ARCHITECTURE.md
CHANGED
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
## System Overview
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This repo is an ownership primitive, not a policy layer. `JBOwnable` gives downstream repos a familiar inheritance surface. `JBOwnableOverrides` implements dynamic owner resolution, ownership transfer, renounce behavior, and delegated permission checks.
|
|
10
|
+
|
|
11
|
+
Ownership can follow the current holder of a Juicebox project NFT instead of staying fixed to one address.
|
|
10
12
|
|
|
11
13
|
## Core Invariants
|
|
12
14
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
15
|
+
- project-owned contracts must resolve the owner dynamically from the current project NFT holder
|
|
16
|
+
- the delegated permission ID resets on ownership transfer
|
|
17
|
+
- pointing ownership at an unminted project can temporarily lock the contract until that project exists
|
|
18
|
+
- an invalid or otherwise unresolvable project NFT effectively renounces ownership
|
|
19
|
+
- this repo should stay a drop-in primitive, not grow product-specific access rules
|
|
18
20
|
|
|
19
21
|
## Modules
|
|
20
22
|
|
|
@@ -27,9 +29,9 @@ The repo is an ownership primitive, not a policy layer. `JBOwnable` exposes a fa
|
|
|
27
29
|
|
|
28
30
|
## Trust Boundaries
|
|
29
31
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
32
|
+
- ownership resolution depends on `JBProjects` and `JBPermissions` from `nana-core-v6`
|
|
33
|
+
- this repo does not create a new permission namespace
|
|
34
|
+
- inheriting contracts may add policy on top, but the resolution semantics here are infrastructure-level
|
|
33
35
|
|
|
34
36
|
## Critical Flows
|
|
35
37
|
|
|
@@ -45,20 +47,20 @@ onlyOwner modifier
|
|
|
45
47
|
|
|
46
48
|
## Accounting Model
|
|
47
49
|
|
|
48
|
-
No treasury accounting lives here. The
|
|
50
|
+
No treasury accounting lives here. The important state is ownership resolution data and delegated permission ID.
|
|
49
51
|
|
|
50
52
|
## Security Model
|
|
51
53
|
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
54
|
+
- ownership resolution edge cases matter more than surface API shape
|
|
55
|
+
- permission delegation is simple but security-sensitive because it composes with a global permission registry
|
|
56
|
+
- unresolvable project ownership is intentionally fail-closed
|
|
55
57
|
|
|
56
58
|
## Safe Change Guide
|
|
57
59
|
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
60
|
+
- be conservative with transfer and renounce semantics
|
|
61
|
+
- if event emission or transfer behavior changes, inspect deployer wrappers and inheriting repos
|
|
62
|
+
- if project-based ownership semantics change, re-check unminted-project and unresolvable-project behavior explicitly
|
|
63
|
+
- do not make delegated permission IDs sticky across ownership transfers
|
|
62
64
|
|
|
63
65
|
## Canonical Checks
|
|
64
66
|
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Audit Instructions
|
|
2
2
|
|
|
3
|
-
This repo provides ownership helpers that can follow Juicebox project NFTs instead of a fixed EOA. It is a small repo with
|
|
3
|
+
This repo provides ownership helpers that can follow Juicebox project NFTs instead of a fixed EOA. It is a small repo with outsized privilege impact.
|
|
4
4
|
|
|
5
5
|
## Audit Objective
|
|
6
6
|
|
|
7
7
|
Find issues that:
|
|
8
|
+
|
|
8
9
|
- let unauthorized actors satisfy owner checks
|
|
9
10
|
- break ownership updates when a project NFT moves, burns, or locks
|
|
10
11
|
- let override logic produce a different owner than the project system intends
|
|
@@ -13,6 +14,7 @@ Find issues that:
|
|
|
13
14
|
## Scope
|
|
14
15
|
|
|
15
16
|
In scope:
|
|
17
|
+
|
|
16
18
|
- `src/JBOwnable.sol`
|
|
17
19
|
- `src/JBOwnableOverrides.sol`
|
|
18
20
|
- `src/interfaces/`
|
|
@@ -25,7 +27,8 @@ In scope:
|
|
|
25
27
|
|
|
26
28
|
## Security Model
|
|
27
29
|
|
|
28
|
-
These contracts abstract
|
|
30
|
+
These contracts abstract "owner" as a project-based identity. Downstream repos use them to:
|
|
31
|
+
|
|
29
32
|
- treat a Juicebox project owner as contract owner
|
|
30
33
|
- apply per-project override rules
|
|
31
34
|
- keep admin power aligned with project NFT ownership instead of a static address
|
|
@@ -45,14 +48,12 @@ These contracts abstract “owner” as a project-based identity. Downstream rep
|
|
|
45
48
|
|
|
46
49
|
## Critical Invariants
|
|
47
50
|
|
|
48
|
-
1. Owner resolution is correct
|
|
49
|
-
For any supported mode, `owner()`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
3. Override precedence is coherent
|
|
55
|
-
Overrides must not silently supersede project ownership in cases the design does not permit.
|
|
51
|
+
1. Owner resolution is correct.
|
|
52
|
+
For any supported mode, `owner()` and owner checks must resolve to the intended authority and no one else.
|
|
53
|
+
2. Burn and lock behavior is safe.
|
|
54
|
+
If project ownership is intentionally burned or locked, the helper must not accidentally reopen control or brick valid admin paths.
|
|
55
|
+
3. Override precedence is coherent.
|
|
56
|
+
Overrides must not silently supersede project ownership in cases the design does not permit.
|
|
56
57
|
|
|
57
58
|
## Attack Surfaces
|
|
58
59
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Juicebox Ownable
|
|
2
2
|
|
|
3
|
-
`@bananapus/ownable-v6` is an ownership helper for contracts that should be controlled by a Juicebox project
|
|
3
|
+
`@bananapus/ownable-v6` is an ownership helper for contracts that should be controlled by a Juicebox project instead of a fixed wallet. It keeps the familiar `Ownable` shape while letting ownership follow a project NFT and optional delegated permissions.
|
|
4
4
|
|
|
5
5
|
Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
|
|
6
6
|
User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
|
|
@@ -11,32 +11,32 @@ Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
|
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
14
|
-
This package extends the standard ownership model in three
|
|
14
|
+
This package extends the standard ownership model in three ways:
|
|
15
15
|
|
|
16
16
|
- ownership can point to a Juicebox project ID instead of an address
|
|
17
|
-
- `owner()`
|
|
18
|
-
- delegated operators can satisfy `onlyOwner` through a
|
|
17
|
+
- `owner()` can resolve dynamically to the current holder of that project NFT
|
|
18
|
+
- delegated operators can satisfy `onlyOwner` through a configured `JBPermissions` permission ID
|
|
19
19
|
|
|
20
|
-
For contracts that are already
|
|
20
|
+
For contracts that are already meant to be owned by a project, this avoids manual ownership transfers when the project NFT changes hands.
|
|
21
21
|
|
|
22
|
-
Use this repo when ownership should follow a Juicebox project. Do not use it if plain single-address ownership is
|
|
22
|
+
Use this repo when ownership should follow a Juicebox project. Do not use it if plain single-address ownership is enough. Standard `Ownable` is simpler.
|
|
23
23
|
|
|
24
|
-
If
|
|
24
|
+
If the issue is in project ownership itself, start in `nana-core-v6` and `JBProjects`. This repo matters when another contract wants its admin surface to follow that project ownership.
|
|
25
25
|
|
|
26
26
|
## Key Contracts
|
|
27
27
|
|
|
28
28
|
| Contract | Role |
|
|
29
29
|
| --- | --- |
|
|
30
|
-
| `JBOwnable` | Concrete contract to inherit when you want Juicebox-aware ownership with
|
|
31
|
-
| `JBOwnableOverrides` | Abstract base that holds
|
|
30
|
+
| `JBOwnable` | Concrete contract to inherit when you want Juicebox-aware ownership with a standard `onlyOwner` interface. |
|
|
31
|
+
| `JBOwnableOverrides` | Abstract base that holds owner resolution and delegated-permission logic. |
|
|
32
32
|
| `IJBOwnable` | Interface for queries, transfers, permission ID changes, and events. |
|
|
33
33
|
|
|
34
34
|
## Mental Model
|
|
35
35
|
|
|
36
|
-
This package is a
|
|
36
|
+
This package is a small ownership adapter:
|
|
37
37
|
|
|
38
38
|
1. resolve who the effective owner is
|
|
39
|
-
2. optionally
|
|
39
|
+
2. optionally allow a delegated permission to satisfy `onlyOwner`
|
|
40
40
|
3. preserve an `Ownable`-like interface for downstream contracts
|
|
41
41
|
|
|
42
42
|
## Read These Files First
|
|
@@ -47,16 +47,16 @@ This package is a thin ownership adapter:
|
|
|
47
47
|
|
|
48
48
|
## Integration Traps
|
|
49
49
|
|
|
50
|
-
- ownership may resolve to a project NFT holder
|
|
51
|
-
- `owner()` can resolve to `address(0)` if the referenced project NFT is
|
|
50
|
+
- ownership may resolve to a project NFT holder instead of a fixed address, so caching `owner()` off-chain can go stale
|
|
51
|
+
- `owner()` can resolve to `address(0)` if the referenced project NFT is invalid or unreadable, which effectively renounces the contract
|
|
52
52
|
- delegated operator access depends on a chosen permission ID, not on a generic admin role
|
|
53
53
|
- ownership transfer and permission-ID updates are part of the security model, not just convenience helpers
|
|
54
54
|
|
|
55
55
|
## Where State Lives
|
|
56
56
|
|
|
57
|
-
- effective ownership configuration
|
|
58
|
-
- downstream contract state
|
|
59
|
-
- project ownership truth
|
|
57
|
+
- effective ownership configuration: `JBOwnableOverrides`
|
|
58
|
+
- downstream contract state: the inheriting contract
|
|
59
|
+
- project ownership truth: `nana-core-v6` when the owner target is a Juicebox project
|
|
60
60
|
|
|
61
61
|
## High-Signal Tests
|
|
62
62
|
|
|
@@ -94,9 +94,9 @@ test/
|
|
|
94
94
|
## Risks And Notes
|
|
95
95
|
|
|
96
96
|
- if ownership is tied to a project NFT and that NFT becomes unreachable, the contract is effectively locked
|
|
97
|
-
- delegated access depends on a chosen permission ID, so
|
|
98
|
-
- permission IDs reset on ownership transfer, which is safer by default but easy to miss
|
|
99
|
-
- transferring ownership to a project validates that the project exists, but later project
|
|
97
|
+
- delegated access depends on a chosen permission ID, so bad permission selection is an operational risk
|
|
98
|
+
- permission IDs reset on ownership transfer, which is safer by default but easy to miss
|
|
99
|
+
- transferring ownership to a project validates that the project exists at transfer time, but later project invalidation can still collapse effective ownership to `address(0)`
|
|
100
100
|
|
|
101
101
|
## For AI Agents
|
|
102
102
|
|
package/SKILLS.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Use This File For
|
|
4
4
|
|
|
5
|
-
- Use this file when the task involves project-based ownership, delegated `onlyOwner` permissions, or
|
|
6
|
-
- Start here, then decide whether the question is about owner resolution, permission delegation, or ownership transfer semantics.
|
|
5
|
+
- Use this file when the task involves project-based ownership, delegated `onlyOwner` permissions, or ownership that should follow a Juicebox project NFT instead of a fixed wallet.
|
|
6
|
+
- Start here, then decide whether the question is about owner resolution, permission delegation, or ownership transfer semantics.
|
|
7
7
|
|
|
8
8
|
## Read This Next
|
|
9
9
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
27
27
|
|
|
28
|
-
Ownership adapter for contracts that should follow Juicebox project ownership instead of a fixed address, with optional delegated permission IDs
|
|
28
|
+
Ownership adapter for contracts that should follow Juicebox project ownership instead of a fixed address, with optional delegated permission IDs on top of the familiar `Ownable` pattern.
|
|
29
29
|
|
|
30
30
|
## Reference Files
|
|
31
31
|
|
|
@@ -36,6 +36,6 @@ Ownership adapter for contracts that should follow Juicebox project ownership in
|
|
|
36
36
|
|
|
37
37
|
- Start in [`src/JBOwnableOverrides.sol`](./src/JBOwnableOverrides.sol) when the question is about who the effective owner is or why `onlyOwner` passed or failed.
|
|
38
38
|
- Treat ownership transfer and delegated permission resets as security-sensitive.
|
|
39
|
-
- Project-based ownership can intentionally become unusable if it points at an unminted or invalid project.
|
|
39
|
+
- Project-based ownership can intentionally become unusable if it points at an unminted or invalid project.
|
|
40
40
|
- Unminted or unexpectedly transferred project NFTs can change the effective owner surface. Check the project lifecycle, not just this adapter.
|
|
41
|
-
- When a bug looks like project ownership itself, confirm whether the real source is upstream in `nana-core-v6` rather than this adapter
|
|
41
|
+
- When a bug looks like project ownership itself, confirm whether the real source is upstream in `nana-core-v6` rather than this adapter.
|
package/USER_JOURNEYS.md
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Repo Purpose
|
|
4
4
|
|
|
5
|
-
This repo adapts `Ownable`-style control to Juicebox project ownership and project-scoped operator permissions.
|
|
6
|
-
It is an ownership adapter. It does not replace the underlying ownership or permission registries in
|
|
7
|
-
[nana-core-v6](../nana-core-v6/USER_JOURNEYS.md).
|
|
5
|
+
This repo adapts `Ownable`-style control to Juicebox project ownership and project-scoped operator permissions. It is an ownership adapter. It does not replace the underlying ownership or permission registries in [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md).
|
|
8
6
|
|
|
9
7
|
## Primary Actors
|
|
10
8
|
|
|
@@ -31,7 +29,7 @@ It is an ownership adapter. It does not replace the underlying ownership or perm
|
|
|
31
29
|
**Main Flow**
|
|
32
30
|
1. Inherit `JBOwnable` or `JBOwnableOverrides`.
|
|
33
31
|
2. Initialize ownership with the relevant project ID and `JBProjects` reference.
|
|
34
|
-
3. Let `owner()` resolve through the current project NFT holder
|
|
32
|
+
3. Let `owner()` resolve through the current project NFT holder instead of a fixed address.
|
|
35
33
|
|
|
36
34
|
**Failure Modes**
|
|
37
35
|
- the contract assumes ordinary `Ownable` transfer semantics after adopting project-based ownership
|
|
@@ -59,7 +57,7 @@ It is an ownership adapter. It does not replace the underlying ownership or perm
|
|
|
59
57
|
**Failure Modes**
|
|
60
58
|
- teams grant a broader permission than intended
|
|
61
59
|
- downstream reviewers forget that `onlyOwner` may resolve through permissions instead of direct ownership
|
|
62
|
-
- operators
|
|
60
|
+
- operators keep stale permissions after governance changes
|
|
63
61
|
|
|
64
62
|
**Postconditions**
|
|
65
63
|
- the chosen operator can satisfy `onlyOwner` without receiving direct ownership of the project or contract
|
|
@@ -99,11 +97,11 @@ It is an ownership adapter. It does not replace the underlying ownership or perm
|
|
|
99
97
|
**Main Flow**
|
|
100
98
|
1. Use `transferOwnership(...)` for an address owner or `transferOwnershipToProject(...)` for a project owner.
|
|
101
99
|
2. Re-establish delegated permission policy if the new owner still wants operators.
|
|
102
|
-
3. Renounce
|
|
100
|
+
3. Renounce only when permanent admin loss is intentional.
|
|
103
101
|
|
|
104
102
|
**Failure Modes**
|
|
105
103
|
- ownership is burned even though the downstream contract still needs administration
|
|
106
|
-
- teams forget that
|
|
104
|
+
- teams forget that delegated permissions reset across ownership changes
|
|
107
105
|
|
|
108
106
|
**Postconditions**
|
|
109
107
|
- control moves to the chosen address or project, or is intentionally removed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/ownable-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"node": ">=20.0.0"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@bananapus/core-v6": "^0.0.
|
|
14
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
13
|
+
"@bananapus/core-v6": "^0.0.36",
|
|
14
|
+
"@bananapus/permission-ids-v6": "^0.0.19",
|
|
15
15
|
"@openzeppelin/contracts": "^5.6.1"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
@@ -134,6 +134,17 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// When permissionId is 0 (direct-owner-only mode), bypass the permission system entirely.
|
|
138
|
+
// This ensures ROOT operators cannot act as owner when delegation is disabled.
|
|
139
|
+
if (ownerInfo.permissionId == 0) {
|
|
140
|
+
if (_msgSender() != resolvedOwner) {
|
|
141
|
+
revert JBPermissioned.JBPermissioned_Unauthorized({
|
|
142
|
+
account: resolvedOwner, sender: _msgSender(), projectId: ownerInfo.projectId, permissionId: 0
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
137
148
|
_requirePermissionFrom({
|
|
138
149
|
account: resolvedOwner, projectId: ownerInfo.projectId, permissionId: ownerInfo.permissionId
|
|
139
150
|
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
|
|
6
|
+
import {MockOwnable} from "../mocks/MockOwnable.sol";
|
|
7
|
+
|
|
8
|
+
import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
9
|
+
import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
|
|
10
|
+
import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
|
|
11
|
+
import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
|
|
12
|
+
|
|
13
|
+
contract RootPermissionBypassesPermissionIdZeroTest is Test {
|
|
14
|
+
JBProjects internal projects;
|
|
15
|
+
JBPermissions internal permissions;
|
|
16
|
+
|
|
17
|
+
address internal alice = makeAddr("alice");
|
|
18
|
+
address internal operator = makeAddr("operator");
|
|
19
|
+
|
|
20
|
+
function setUp() public {
|
|
21
|
+
permissions = new JBPermissions(address(0));
|
|
22
|
+
projects = new JBProjects(address(this), address(0), address(0));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// @notice After M-39 fix: ROOT operator is rejected when permissionId=0 (direct-owner-only mode).
|
|
26
|
+
function test_rootPermissionRejectedWhenPermissionIdIsZero() public {
|
|
27
|
+
uint256 projectId = projects.createFor(alice);
|
|
28
|
+
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
|
|
29
|
+
|
|
30
|
+
// Grant ROOT permission (id=1) to operator.
|
|
31
|
+
uint8[] memory permissionIds = new uint8[](1);
|
|
32
|
+
permissionIds[0] = 1;
|
|
33
|
+
|
|
34
|
+
vm.prank(alice);
|
|
35
|
+
permissions.setPermissionsFor(
|
|
36
|
+
alice, JBPermissionsData({operator: operator, projectId: uint56(projectId), permissionIds: permissionIds})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
(, uint88 storedProjectId, uint8 permissionId) = ownable.jbOwner();
|
|
40
|
+
assertEq(storedProjectId, projectId);
|
|
41
|
+
assertEq(permissionId, 0, "expected direct-owner-only mode");
|
|
42
|
+
|
|
43
|
+
// Operator should be rejected when permissionId=0.
|
|
44
|
+
vm.prank(operator);
|
|
45
|
+
vm.expectRevert(
|
|
46
|
+
abi.encodeWithSelector(JBPermissioned.JBPermissioned_Unauthorized.selector, alice, operator, projectId, 0)
|
|
47
|
+
);
|
|
48
|
+
ownable.protectedMethod();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// @notice Direct owner still works when permissionId=0.
|
|
52
|
+
function test_directOwnerStillWorksWithPermissionIdZero() public {
|
|
53
|
+
uint256 projectId = projects.createFor(alice);
|
|
54
|
+
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
|
|
55
|
+
|
|
56
|
+
(, uint88 storedProjectId, uint8 permissionId) = ownable.jbOwner();
|
|
57
|
+
assertEq(storedProjectId, projectId);
|
|
58
|
+
assertEq(permissionId, 0);
|
|
59
|
+
|
|
60
|
+
// Alice (project owner) should still be able to call the protected method.
|
|
61
|
+
vm.prank(alice);
|
|
62
|
+
ownable.protectedMethod();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// @notice Non-zero permissionId still delegates correctly via the permission system.
|
|
66
|
+
function test_delegatedOperatorWorksWhenPermissionIdNonZero() public {
|
|
67
|
+
uint256 projectId = projects.createFor(alice);
|
|
68
|
+
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
|
|
69
|
+
|
|
70
|
+
// Set permissionId to 42 (non-zero = delegation enabled).
|
|
71
|
+
vm.prank(alice);
|
|
72
|
+
ownable.setPermissionId(42);
|
|
73
|
+
|
|
74
|
+
// Grant permission 42 to operator.
|
|
75
|
+
uint8[] memory permissionIds = new uint8[](1);
|
|
76
|
+
permissionIds[0] = 42;
|
|
77
|
+
|
|
78
|
+
vm.prank(alice);
|
|
79
|
+
permissions.setPermissionsFor(
|
|
80
|
+
alice, JBPermissionsData({operator: operator, projectId: uint56(projectId), permissionIds: permissionIds})
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Operator should succeed with matching permissionId.
|
|
84
|
+
vm.prank(operator);
|
|
85
|
+
ownable.protectedMethod();
|
|
86
|
+
}
|
|
87
|
+
}
|