@bananapus/ownable-v6 0.0.21 → 0.0.23
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/foundry.toml +1 -1
- package/package.json +13 -4
- package/src/JBOwnableOverrides.sol +19 -2
- package/ADMINISTRATION.md +0 -79
- package/ARCHITECTURE.md +0 -92
- package/AUDIT_INSTRUCTIONS.md +0 -69
- package/RISKS.md +0 -51
- package/SKILLS.md +0 -41
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -117
- package/slither-ci.config.json +0 -10
- package/test/CodexUnmintedProjectHijack.t.sol +0 -45
- package/test/Ownable.t.sol +0 -383
- package/test/OwnableAttacks.t.sol +0 -190
- package/test/OwnableEdgeCases.t.sol +0 -437
- package/test/OwnableInvariantTests.sol +0 -48
- package/test/handlers/OwnableHandler.sol +0 -75
- package/test/regression/BurnLockProtection.t.sol +0 -112
- package/test/regression/RootPermissionBypassesPermissionIdZero.t.sol +0 -87
- package/test/regression/ZeroAddressValidation.t.sol +0 -67
package/foundry.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/ownable-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/Bananapus/nana-ownable-v6"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"foundry.lock",
|
|
12
|
+
"foundry.toml",
|
|
13
|
+
"references/",
|
|
14
|
+
"remappings.txt",
|
|
15
|
+
"src/",
|
|
16
|
+
"test/mocks/"
|
|
17
|
+
],
|
|
9
18
|
"engines": {
|
|
10
19
|
"node": ">=20.0.0"
|
|
11
20
|
},
|
|
12
21
|
"dependencies": {
|
|
13
|
-
"@bananapus/core-v6": "
|
|
14
|
-
"@bananapus/permission-ids-v6": "
|
|
15
|
-
"@openzeppelin/contracts": "
|
|
22
|
+
"@bananapus/core-v6": "0.0.38",
|
|
23
|
+
"@bananapus/permission-ids-v6": "0.0.22",
|
|
24
|
+
"@openzeppelin/contracts": "5.6.1"
|
|
16
25
|
},
|
|
17
26
|
"scripts": {
|
|
18
27
|
"test": "forge test",
|
|
@@ -36,6 +36,14 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
36
36
|
/// @notice This contract's owner information.
|
|
37
37
|
JBOwner public override jbOwner;
|
|
38
38
|
|
|
39
|
+
//*********************************************************************//
|
|
40
|
+
// -------------------- internal stored properties ------------------- //
|
|
41
|
+
//*********************************************************************//
|
|
42
|
+
|
|
43
|
+
/// @notice The resolved owner address at the time permissionId was last set.
|
|
44
|
+
/// @dev Used to detect stale permissions after ownership changes (e.g., NFT transfer).
|
|
45
|
+
address internal _permissionOwner;
|
|
46
|
+
|
|
39
47
|
//*********************************************************************//
|
|
40
48
|
// -------------------------- constructor ---------------------------- //
|
|
41
49
|
//*********************************************************************//
|
|
@@ -134,9 +142,16 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
134
142
|
}
|
|
135
143
|
}
|
|
136
144
|
|
|
145
|
+
// Detect stale permissions: if ownership changed since permissionId was set
|
|
146
|
+
// (e.g., project NFT transferred), treat permissionId as 0 (direct-owner-only).
|
|
147
|
+
uint8 effectivePermissionId = ownerInfo.permissionId;
|
|
148
|
+
if (effectivePermissionId != 0 && resolvedOwner != _permissionOwner) {
|
|
149
|
+
effectivePermissionId = 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
137
152
|
// When permissionId is 0 (direct-owner-only mode), bypass the permission system entirely.
|
|
138
153
|
// This ensures ROOT operators cannot act as owner when delegation is disabled.
|
|
139
|
-
if (
|
|
154
|
+
if (effectivePermissionId == 0) {
|
|
140
155
|
if (_msgSender() != resolvedOwner) {
|
|
141
156
|
revert JBPermissioned.JBPermissioned_Unauthorized({
|
|
142
157
|
account: resolvedOwner, sender: _msgSender(), projectId: ownerInfo.projectId, permissionId: 0
|
|
@@ -146,7 +161,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
146
161
|
}
|
|
147
162
|
|
|
148
163
|
_requirePermissionFrom({
|
|
149
|
-
account: resolvedOwner, projectId: ownerInfo.projectId, permissionId:
|
|
164
|
+
account: resolvedOwner, projectId: ownerInfo.projectId, permissionId: effectivePermissionId
|
|
150
165
|
});
|
|
151
166
|
}
|
|
152
167
|
|
|
@@ -221,6 +236,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
221
236
|
/// @param permissionId The permission ID to use for `onlyOwner`.
|
|
222
237
|
function _setPermissionId(uint8 permissionId) internal virtual {
|
|
223
238
|
jbOwner.permissionId = permissionId;
|
|
239
|
+
_permissionOwner = owner();
|
|
224
240
|
emit PermissionIdChanged({newId: permissionId, caller: _msgSender()});
|
|
225
241
|
}
|
|
226
242
|
|
|
@@ -257,6 +273,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
257
273
|
// Update the stored owner information to the new owner and reset the `permissionId`.
|
|
258
274
|
// This is to prevent permissions clashes for the new user/owner.
|
|
259
275
|
jbOwner = JBOwner({owner: newOwner, projectId: projectId, permissionId: 0});
|
|
276
|
+
_permissionOwner = address(0);
|
|
260
277
|
// Emit a transfer event with the new owner's address.
|
|
261
278
|
_emitTransferEvent({previousOwner: oldOwner, newOwner: newOwner, newProjectId: projectId});
|
|
262
279
|
}
|
package/ADMINISTRATION.md
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# Administration
|
|
2
|
-
|
|
3
|
-
## At A Glance
|
|
4
|
-
|
|
5
|
-
| Item | Details |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| Scope | Ownership resolution primitive used by downstream repos |
|
|
8
|
-
| Control posture | Primitive only; control depends on the inheriting contract |
|
|
9
|
-
| Highest-risk actions | Transferring ownership to the wrong address or project and assuming delegated operators survive transfer |
|
|
10
|
-
| Recovery posture | Recovery depends on the inheriting contract and the still-recognized current owner |
|
|
11
|
-
|
|
12
|
-
## Purpose
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
## Control Model
|
|
17
|
-
|
|
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
|
-
|
|
23
|
-
## Roles
|
|
24
|
-
|
|
25
|
-
| Role | How Assigned | Scope | Notes |
|
|
26
|
-
| --- | --- | --- | --- |
|
|
27
|
-
| Direct owner | Stored owner address | Per contract | Standard `Ownable`-like control |
|
|
28
|
-
| Project owner | Holder of the referenced project NFT | Per contract | Dynamic ownership resolution |
|
|
29
|
-
| Delegated operator | `JBPermissions` grant with the configured permission ID | Per contract and project | Only if the inheriting contract enables it |
|
|
30
|
-
|
|
31
|
-
## Privileged Surfaces
|
|
32
|
-
|
|
33
|
-
The meaningful control surfaces are inherited by downstream contracts:
|
|
34
|
-
|
|
35
|
-
- `setPermissionId(...)`
|
|
36
|
-
- `transferOwnership(...)`
|
|
37
|
-
- `transferOwnershipToProject(...)`
|
|
38
|
-
- `renounceOwnership()`
|
|
39
|
-
- `onlyOwner` checks that resolve either the direct owner or the current project NFT holder
|
|
40
|
-
|
|
41
|
-
## Immutable And One-Way
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
## Operational Notes
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
## Machine Notes
|
|
55
|
-
|
|
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
|
-
|
|
61
|
-
## Recovery
|
|
62
|
-
|
|
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
|
-
|
|
66
|
-
## Admin Boundaries
|
|
67
|
-
|
|
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
|
-
|
|
72
|
-
## Source Map
|
|
73
|
-
|
|
74
|
-
- `src/JBOwnable.sol`
|
|
75
|
-
- `src/JBOwnableOverrides.sol`
|
|
76
|
-
- `src/structs/JBOwner.sol`
|
|
77
|
-
- `test/OwnableInvariantTests.sol`
|
|
78
|
-
- `test/OwnableEdgeCases.t.sol`
|
|
79
|
-
- `test/regression/BurnLockProtection.t.sol`
|
package/ARCHITECTURE.md
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
`nana-ownable-v6` adapts `Ownable` to the Juicebox model. A contract can be owned by an address or by a Juicebox project NFT, and delegated operators can satisfy `onlyOwner` through `JBPermissions`.
|
|
6
|
-
|
|
7
|
-
## System Overview
|
|
8
|
-
|
|
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.
|
|
12
|
-
|
|
13
|
-
## Core Invariants
|
|
14
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
## Modules
|
|
22
|
-
|
|
23
|
-
| Module | Responsibility | Notes |
|
|
24
|
-
| --- | --- | --- |
|
|
25
|
-
| `JBOwnable` | Familiar `onlyOwner` inheritance target | Concrete surface |
|
|
26
|
-
| `JBOwnableOverrides` | Resolution, transfer, renounce, and delegated-permission logic | Core behavior |
|
|
27
|
-
| `JBOwner` | Packed owner state | Shared struct |
|
|
28
|
-
| `IJBOwnable` | Public interface and events | Integration surface |
|
|
29
|
-
|
|
30
|
-
## Trust Boundaries
|
|
31
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
## Critical Flows
|
|
37
|
-
|
|
38
|
-
### Owner Check
|
|
39
|
-
|
|
40
|
-
```text
|
|
41
|
-
onlyOwner modifier
|
|
42
|
-
-> load packed owner state
|
|
43
|
-
-> if project-owned, resolve the current project NFT holder
|
|
44
|
-
-> otherwise use the stored owner address
|
|
45
|
-
-> accept either the resolved owner or an operator with the configured JB permission
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Accounting Model
|
|
49
|
-
|
|
50
|
-
No treasury accounting lives here. The important state is ownership resolution data and delegated permission ID.
|
|
51
|
-
|
|
52
|
-
## Security Model
|
|
53
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
## Safe Change Guide
|
|
59
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
## Canonical Checks
|
|
66
|
-
|
|
67
|
-
- baseline address-owner and project-owner behavior:
|
|
68
|
-
`test/Ownable.t.sol`
|
|
69
|
-
- transfer, renounce, and hostile-call edge cases:
|
|
70
|
-
`test/OwnableEdgeCases.t.sol`
|
|
71
|
-
`test/OwnableAttacks.t.sol`
|
|
72
|
-
- unminted-project and burn-lock safety:
|
|
73
|
-
`test/CodexUnmintedProjectHijack.t.sol`
|
|
74
|
-
`test/regression/BurnLockProtection.t.sol`
|
|
75
|
-
- ownership-state invariants:
|
|
76
|
-
`test/OwnableInvariantTests.sol`
|
|
77
|
-
|
|
78
|
-
## Source Map
|
|
79
|
-
|
|
80
|
-
- `src/JBOwnable.sol`
|
|
81
|
-
- `src/JBOwnableOverrides.sol`
|
|
82
|
-
- `src/structs/JBOwner.sol`
|
|
83
|
-
- `src/interfaces/IJBOwnable.sol`
|
|
84
|
-
- `test/Ownable.t.sol`
|
|
85
|
-
- `test/OwnableEdgeCases.t.sol`
|
|
86
|
-
- `test/OwnableAttacks.t.sol`
|
|
87
|
-
- `test/CodexUnmintedProjectHijack.t.sol`
|
|
88
|
-
- `test/regression/BurnLockProtection.t.sol`
|
|
89
|
-
- `test/regression/ZeroAddressValidation.t.sol`
|
|
90
|
-
- `test/OwnableInvariantTests.sol`
|
|
91
|
-
- `references/runtime.md`
|
|
92
|
-
- `references/operations.md`
|
package/AUDIT_INSTRUCTIONS.md
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# Audit Instructions
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
## Audit Objective
|
|
6
|
-
|
|
7
|
-
Find issues that:
|
|
8
|
-
|
|
9
|
-
- let unauthorized actors satisfy owner checks
|
|
10
|
-
- break ownership updates when a project NFT moves, burns, or locks
|
|
11
|
-
- let override logic produce a different owner than the project system intends
|
|
12
|
-
- leave dependent repos with stale or permanently wrong ownership views
|
|
13
|
-
|
|
14
|
-
## Scope
|
|
15
|
-
|
|
16
|
-
In scope:
|
|
17
|
-
|
|
18
|
-
- `src/JBOwnable.sol`
|
|
19
|
-
- `src/JBOwnableOverrides.sol`
|
|
20
|
-
- `src/interfaces/`
|
|
21
|
-
- `src/structs/`
|
|
22
|
-
|
|
23
|
-
## Start Here
|
|
24
|
-
|
|
25
|
-
1. `src/JBOwnable.sol`
|
|
26
|
-
2. `src/JBOwnableOverrides.sol`
|
|
27
|
-
|
|
28
|
-
## Security Model
|
|
29
|
-
|
|
30
|
-
These contracts abstract "owner" as a project-based identity. Downstream repos use them to:
|
|
31
|
-
|
|
32
|
-
- treat a Juicebox project owner as contract owner
|
|
33
|
-
- apply per-project override rules
|
|
34
|
-
- keep admin power aligned with project NFT ownership instead of a static address
|
|
35
|
-
|
|
36
|
-
## Roles And Privileges
|
|
37
|
-
|
|
38
|
-
| Role | Powers | How constrained |
|
|
39
|
-
|------|--------|-----------------|
|
|
40
|
-
| Project NFT owner | Become the effective contract owner | Should update automatically with NFT transfers |
|
|
41
|
-
| Override authority | Set alternative owner resolution where allowed | Must not outrank project ownership unexpectedly |
|
|
42
|
-
|
|
43
|
-
## Integration Assumptions
|
|
44
|
-
|
|
45
|
-
| Dependency | Assumption | What breaks if wrong |
|
|
46
|
-
|------------|------------|----------------------|
|
|
47
|
-
| Juicebox project ownership | NFT ownership reflects intended authority | Downstream admin checks drift from reality |
|
|
48
|
-
|
|
49
|
-
## Critical Invariants
|
|
50
|
-
|
|
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.
|
|
57
|
-
|
|
58
|
-
## Attack Surfaces
|
|
59
|
-
|
|
60
|
-
- owner resolution after project NFT transfer
|
|
61
|
-
- zero-address, burn, and lock states
|
|
62
|
-
- override configuration and precedence
|
|
63
|
-
- downstream assumptions that cache owner state instead of re-reading it
|
|
64
|
-
|
|
65
|
-
## Verification
|
|
66
|
-
|
|
67
|
-
- `npm install`
|
|
68
|
-
- `forge build`
|
|
69
|
-
- `forge test`
|
package/RISKS.md
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Juicebox Ownable Risk Register
|
|
2
|
-
|
|
3
|
-
This file covers the ownership-model risks in `JBOwnable`: dynamic ownership through project NFTs, delegated owner authority, and mismatches with standard `Ownable` expectations.
|
|
4
|
-
|
|
5
|
-
## How To Use This File
|
|
6
|
-
|
|
7
|
-
- Read `Priority risks` first. Most failures here come from authority-model mistakes, not arithmetic bugs.
|
|
8
|
-
- Use the later sections to understand what changes when ownership follows a project instead of a fixed address.
|
|
9
|
-
- Treat `Invariants to verify` as the minimum proof that owner resolution stays coherent.
|
|
10
|
-
|
|
11
|
-
## Priority Risks
|
|
12
|
-
|
|
13
|
-
| Priority | Risk | Why it matters | Primary controls |
|
|
14
|
-
|----------|------|----------------|------------------|
|
|
15
|
-
| P1 | Misunderstanding dynamic owner resolution | Ownership can move when the project NFT moves or when permissions change, which breaks static `Ownable` assumptions. | Clear docs, careful integration review, and explicit tests around transfer paths. |
|
|
16
|
-
| P1 | Over-broad delegated owner permissions | `JBPermissions` can broaden who effectively acts as owner. Bad configuration expands blast radius quickly. | Permission hygiene and explicit review of delegated grants. |
|
|
17
|
-
| P2 | Tooling assumptions about standard `Ownable` | Some tooling assumes `owner()` maps to one address with no external permission system behind it. | Integration testing and clear documentation of the semantic differences. |
|
|
18
|
-
|
|
19
|
-
## 1. Trust Assumptions
|
|
20
|
-
|
|
21
|
-
- **`JBPermissions` works correctly.** A bug there affects every `JBOwnable` contract that relies on delegated owner access.
|
|
22
|
-
- **`JBProjects` ownership is the source of truth.** When a contract is project-owned, whoever holds the project NFT has owner access.
|
|
23
|
-
- **Delegated permission means owner-equivalent access.** Anyone granted the configured `permissionId` through `JBPermissions` can satisfy owner checks for the scoped contract.
|
|
24
|
-
- **Deployment inputs are intentional.** If `initialProjectIdOwner != 0`, deployers must understand whether that project already exists.
|
|
25
|
-
|
|
26
|
-
## 2. Known Risks
|
|
27
|
-
|
|
28
|
-
- **Project NFT transfer changes contract ownership.** Anyone who acquires the project NFT gains owner access to contracts using that project-owned mode.
|
|
29
|
-
- **Two ownership modes can confuse integrations.** Setting both `newOwner` and `projectId` is disallowed, but integrators still need to check which mode is active.
|
|
30
|
-
- **`renounceOwnership` is final.** Once called, `owner()` resolves to `address(0)` and owner-gated functions stop working permanently unless a downstream contract adds its own recovery path.
|
|
31
|
-
- **Constructor pre-binding can intentionally lock the contract.** If a deployer points ownership at a future project ID, `owner()` resolves to `address(0)` until that project exists.
|
|
32
|
-
- **`PROJECTS == address(0)` breaks project-owned mode.** The constructor defends against this, but wrappers should still treat it as a high-signal deployment surface.
|
|
33
|
-
- **Unminted project ID ownership.** Contracts using `JBOwnableOverrides` can be configured with an `initialProjectIdOwner` that references a project ID not yet minted. The first account to mint that sequential project ID will become the effective owner of the contract. Deployers must ensure the referenced project ID is already minted, or deploy the ownable contract and the project in the same transaction to prevent front-running.
|
|
34
|
-
|
|
35
|
-
## 3. Accepted Behaviors
|
|
36
|
-
|
|
37
|
-
- **Permission ID resets on transfer.** `permissionId` resets to `0` on ownership transfer so old delegated operators do not automatically retain power.
|
|
38
|
-
- **`permissionId = 0` means direct-owner-only mode.** This is a valid configuration, not an error state.
|
|
39
|
-
- **Invalid project ownership resolves fail-closed.** If `ownerOf` cannot resolve, the contract is effectively renounced until ownership becomes readable again.
|
|
40
|
-
- **`transferOwnershipToProject` rejects non-existent projects.** The function checks existence at transfer time.
|
|
41
|
-
- **Constructor pre-binding to a future project ID is supported.** This is useful in controlled deployment flows, but dangerous if the deployer does not control mint sequencing.
|
|
42
|
-
- **Transfer events can temporarily show `address(0)`.** When ownership points to an unminted future project, the transfer event shows `address(0)` until ownership can resolve dynamically.
|
|
43
|
-
|
|
44
|
-
## 4. Invariants To Verify
|
|
45
|
-
|
|
46
|
-
- ownership is always exactly one of: direct address or project NFT holder
|
|
47
|
-
- `_checkOwner()` reverts for all callers when the owner resolves to `address(0)`
|
|
48
|
-
- `permissionId` resets to `0` on every ownership transfer
|
|
49
|
-
- after `renounceOwnership()`, `jbOwner()` returns `(address(0), 0, 0)` and no address can pass `_checkOwner()`
|
|
50
|
-
- `transferOwnershipToProject(projectId)` reverts for all `projectId > PROJECTS.count()` at call time
|
|
51
|
-
- `initialProjectIdOwner != 0` with `PROJECTS == address(0)` always reverts during construction
|
package/SKILLS.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Juicebox Ownable
|
|
2
|
-
|
|
3
|
-
## Use This File For
|
|
4
|
-
|
|
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
|
-
|
|
8
|
-
## Read This Next
|
|
9
|
-
|
|
10
|
-
| If you need... | Open this next |
|
|
11
|
-
|---|---|
|
|
12
|
-
| Repo overview and ownership model | [`README.md`](./README.md), [`ARCHITECTURE.md`](./ARCHITECTURE.md) |
|
|
13
|
-
| Concrete contract | [`src/JBOwnable.sol`](./src/JBOwnable.sol) |
|
|
14
|
-
| Resolution and permission logic | [`src/JBOwnableOverrides.sol`](./src/JBOwnableOverrides.sol), [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/) |
|
|
15
|
-
| Runtime and migration assumptions | [`references/runtime.md`](./references/runtime.md), [`references/operations.md`](./references/operations.md) |
|
|
16
|
-
| Edge, attack, and invariant coverage | [`test/Ownable.t.sol`](./test/Ownable.t.sol), [`test/OwnableEdgeCases.t.sol`](./test/OwnableEdgeCases.t.sol), [`test/OwnableAttacks.t.sol`](./test/OwnableAttacks.t.sol), [`test/OwnableInvariantTests.sol`](./test/OwnableInvariantTests.sol), [`test/CodexUnmintedProjectHijack.t.sol`](./test/CodexUnmintedProjectHijack.t.sol) |
|
|
17
|
-
|
|
18
|
-
## Repo Map
|
|
19
|
-
|
|
20
|
-
| Area | Where to look |
|
|
21
|
-
|---|---|
|
|
22
|
-
| Main contracts | [`src/`](./src/) |
|
|
23
|
-
| Types | [`src/interfaces/`](./src/interfaces/), [`src/structs/`](./src/structs/) |
|
|
24
|
-
| Tests | [`test/`](./test/) |
|
|
25
|
-
|
|
26
|
-
## Purpose
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
## Reference Files
|
|
31
|
-
|
|
32
|
-
- Open [`references/runtime.md`](./references/runtime.md) when you need owner resolution, transfer semantics, or delegated access behavior.
|
|
33
|
-
- Open [`references/operations.md`](./references/operations.md) when you need migration pitfalls, test breadcrumbs, or the common stale assumptions around permission resets.
|
|
34
|
-
|
|
35
|
-
## Working Rules
|
|
36
|
-
|
|
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
|
-
- 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.
|
|
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.
|