@bananapus/ownable-v6 0.0.34 → 0.0.37
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/README.md +13 -9
- package/package.json +3 -3
- package/references/operations.md +2 -2
- package/references/runtime.md +4 -4
- package/src/JBOwnable.sol +2 -6
- package/src/JBOwnableOverrides.sol +20 -16
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
- [STYLE_GUIDE.md](./STYLE_GUIDE.md) — code style conventions
|
|
14
14
|
- [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — guidance for auditors
|
|
15
15
|
- [CHANGELOG.md](./CHANGELOG.md) — release notes
|
|
16
|
+
- [references/runtime.md](./references/runtime.md) — owner-resolution, transfer, and delegation behavior by surface
|
|
17
|
+
- [references/operations.md](./references/operations.md) — change checklist and common failure modes
|
|
16
18
|
|
|
17
19
|
## Overview
|
|
18
20
|
|
|
@@ -29,7 +31,7 @@ Use this repo when ownership should follow a Juicebox project. Do not use it if
|
|
|
29
31
|
|
|
30
32
|
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.
|
|
31
33
|
|
|
32
|
-
## Key
|
|
34
|
+
## Key contracts
|
|
33
35
|
|
|
34
36
|
| Contract | Role |
|
|
35
37
|
| --- | --- |
|
|
@@ -37,7 +39,7 @@ If the issue is in project ownership itself, start in `nana-core-v6` and `JBProj
|
|
|
37
39
|
| `JBOwnableOverrides` | Abstract base that holds owner resolution and delegated-permission logic. |
|
|
38
40
|
| `IJBOwnable` | Interface for queries, transfers, permission ID changes, and events. |
|
|
39
41
|
|
|
40
|
-
## Mental
|
|
42
|
+
## Mental model
|
|
41
43
|
|
|
42
44
|
This package is a small ownership adapter:
|
|
43
45
|
|
|
@@ -45,13 +47,13 @@ This package is a small ownership adapter:
|
|
|
45
47
|
2. optionally allow a delegated permission to satisfy `onlyOwner` when the contract is project-owned
|
|
46
48
|
3. preserve an `Ownable`-like interface for downstream contracts
|
|
47
49
|
|
|
48
|
-
## Read
|
|
50
|
+
## Read these files first
|
|
49
51
|
|
|
50
52
|
1. `src/JBOwnable.sol`
|
|
51
53
|
2. `src/JBOwnableOverrides.sol`
|
|
52
54
|
3. `src/interfaces/IJBOwnable.sol`
|
|
53
55
|
|
|
54
|
-
## Integration
|
|
56
|
+
## Integration traps
|
|
55
57
|
|
|
56
58
|
- ownership may resolve to a project NFT holder instead of a fixed address, so caching `owner()` off-chain can go stale
|
|
57
59
|
- `owner()` can resolve to `address(0)` if the referenced project NFT is invalid or unreadable, which effectively renounces the contract
|
|
@@ -60,13 +62,13 @@ This package is a small ownership adapter:
|
|
|
60
62
|
- a project NFT round trip back to the owner who last set `permissionId` can reactivate that owner's still-granted delegates
|
|
61
63
|
- ownership transfer and permission-ID updates are part of the security model, not just convenience helpers
|
|
62
64
|
|
|
63
|
-
## Where
|
|
65
|
+
## Where state lives
|
|
64
66
|
|
|
65
67
|
- effective ownership configuration: `JBOwnableOverrides`
|
|
66
68
|
- downstream contract state: the inheriting contract
|
|
67
69
|
- project ownership truth: `nana-core-v6` when the owner target is a Juicebox project
|
|
68
70
|
|
|
69
|
-
## High-
|
|
71
|
+
## High-signal tests
|
|
70
72
|
|
|
71
73
|
1. `test/Ownable.t.sol`
|
|
72
74
|
2. `test/OwnableAttacks.t.sol`
|
|
@@ -90,7 +92,7 @@ forge build
|
|
|
90
92
|
forge test
|
|
91
93
|
```
|
|
92
94
|
|
|
93
|
-
## Repository
|
|
95
|
+
## Repository layout
|
|
94
96
|
|
|
95
97
|
```text
|
|
96
98
|
src/
|
|
@@ -102,7 +104,7 @@ test/
|
|
|
102
104
|
core, attack, invariant, mock, and regression coverage
|
|
103
105
|
```
|
|
104
106
|
|
|
105
|
-
## Risks
|
|
107
|
+
## Risks and notes
|
|
106
108
|
|
|
107
109
|
- if ownership is tied to a project NFT and that NFT becomes unreachable, the contract is effectively locked
|
|
108
110
|
- project-owned delegated access depends on a chosen permission ID, so bad permission selection is an operational risk
|
|
@@ -111,7 +113,9 @@ test/
|
|
|
111
113
|
resolved owner still matches the owner who set it
|
|
112
114
|
- 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)`
|
|
113
115
|
|
|
114
|
-
## For AI
|
|
116
|
+
## For AI agents
|
|
115
117
|
|
|
116
118
|
- Do not collapse project-based ownership into ordinary wallet-based ownership in your summary.
|
|
117
119
|
- Read the attack and regression tests before making claims about burn-lock or unminted-project edge cases.
|
|
120
|
+
|
|
121
|
+
If ownership should track a project NFT, reach for this; if a fixed wallet is enough, plain `Ownable` is simpler.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/ownable-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"node": ">=20.0.0"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bananapus/core-v6": "^0.0.
|
|
21
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
20
|
+
"@bananapus/core-v6": "^0.0.78",
|
|
21
|
+
"@bananapus/permission-ids-v6": "^0.0.28",
|
|
22
22
|
"@openzeppelin/contracts": "5.6.1"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
package/references/operations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ownable Operations
|
|
2
2
|
|
|
3
|
-
## Change
|
|
3
|
+
## Change checklist
|
|
4
4
|
|
|
5
5
|
- If you edit owner resolution, verify both direct ownership and project-owned cases.
|
|
6
6
|
- If you edit permission handling, verify explicit transfer resets, project NFT transfer staleness, and NFT round-trip
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
transfers merely make it stale.
|
|
10
10
|
- If the change touches project ownership, check unminted-project and burn-lock regressions before assuming the happy-path tests are enough.
|
|
11
11
|
|
|
12
|
-
## Common
|
|
12
|
+
## Common failure modes
|
|
13
13
|
|
|
14
14
|
- Integrations assume delegated operators survive ownership transfer.
|
|
15
15
|
- Bugs are blamed on this repo when the underlying project NFT ownership changed upstream.
|
package/references/runtime.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# Ownable Runtime
|
|
2
2
|
|
|
3
|
-
## Core
|
|
3
|
+
## Core roles
|
|
4
4
|
|
|
5
5
|
- [`src/JBOwnable.sol`](../src/JBOwnable.sol) is the concrete downstream inheritance surface.
|
|
6
6
|
- [`src/JBOwnableOverrides.sol`](../src/JBOwnableOverrides.sol) owns owner resolution and delegated permission checks.
|
|
7
7
|
|
|
8
|
-
## High-
|
|
8
|
+
## High-risk areas
|
|
9
9
|
|
|
10
10
|
- Effective-owner resolution: ownership may follow a project NFT rather than a fixed address.
|
|
11
11
|
- Delegated `onlyOwner` permissions: the chosen permission ID changes who can administer a contract.
|
|
12
12
|
- Transfer semantics: explicit ownable transfers reset permission IDs, while project NFT transfers preserve the stored
|
|
13
13
|
ID and rely on `_permissionOwner` to decide whether it is effective.
|
|
14
14
|
|
|
15
|
-
## Tests
|
|
15
|
+
## Tests to trust first
|
|
16
16
|
|
|
17
17
|
- [`test/Ownable.t.sol`](../test/Ownable.t.sol) for baseline behavior.
|
|
18
18
|
- [`test/OwnableEdgeCases.t.sol`](../test/OwnableEdgeCases.t.sol) and [`test/OwnableAttacks.t.sol`](../test/OwnableAttacks.t.sol) for edge and adversarial cases.
|
|
19
19
|
- [`test/OwnableInvariantTests.sol`](../test/OwnableInvariantTests.sol) for broader invariants.
|
|
20
|
-
- [`test/regression/BurnLockProtection.t.sol`](../test/regression/BurnLockProtection.t.sol), [`test/RegressionUnmintedProjectHijack.t.sol`](../test/RegressionUnmintedProjectHijack.t.sol), [`test/regression/PermissionIdNFTTransfer.t.sol`](../test/regression/PermissionIdNFTTransfer.t.sol), and [`test/
|
|
20
|
+
- [`test/regression/BurnLockProtection.t.sol`](../test/regression/BurnLockProtection.t.sol), [`test/RegressionUnmintedProjectHijack.t.sol`](../test/RegressionUnmintedProjectHijack.t.sol), [`test/regression/PermissionIdNFTTransfer.t.sol`](../test/regression/PermissionIdNFTTransfer.t.sol), and [`test/regression/StaleDelegateReactivationOnProjectReturn.t.sol`](../test/regression/StaleDelegateReactivationOnProjectReturn.t.sol) for the regressions most likely to matter in review.
|
package/src/JBOwnable.sol
CHANGED
|
@@ -67,12 +67,8 @@ contract JBOwnable is JBOwnableOverrides {
|
|
|
67
67
|
{
|
|
68
68
|
address resolvedNewOwner = newOwner;
|
|
69
69
|
if (newProjectId != 0) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} catch {
|
|
73
|
-
// Pre-bound future projects have no visible owner yet, so the event reports address(0).
|
|
74
|
-
resolvedNewOwner = address(0);
|
|
75
|
-
}
|
|
70
|
+
// Pre-bound future projects have no visible owner yet, so the event reports address(0).
|
|
71
|
+
resolvedNewOwner = _projectOwnerOf(newProjectId);
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
emit OwnershipTransferred({previousOwner: previousOwner, newOwner: resolvedNewOwner, caller: _msgSender()});
|
|
@@ -115,12 +115,8 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
115
115
|
return ownerInfo.owner;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
return projectOwner;
|
|
121
|
-
} catch {
|
|
122
|
-
return address(0);
|
|
123
|
-
}
|
|
118
|
+
// Expose the project owner, or zero if the project's NFT cannot be read, instead of bubbling the revert.
|
|
119
|
+
return _projectOwnerOf(ownerInfo.projectId);
|
|
124
120
|
}
|
|
125
121
|
|
|
126
122
|
//*********************************************************************//
|
|
@@ -150,11 +146,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
150
146
|
return;
|
|
151
147
|
} else {
|
|
152
148
|
// Resolve the project owner dynamically; unreadable projects fail closed to address(0).
|
|
153
|
-
|
|
154
|
-
resolvedOwner = projectOwner;
|
|
155
|
-
} catch {
|
|
156
|
-
resolvedOwner = address(0);
|
|
157
|
-
}
|
|
149
|
+
resolvedOwner = _projectOwnerOf(ownerInfo.projectId);
|
|
158
150
|
}
|
|
159
151
|
|
|
160
152
|
// Ignore the stored permission ID while the project NFT is held by a different owner than the one who set it.
|
|
@@ -179,6 +171,22 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
179
171
|
});
|
|
180
172
|
}
|
|
181
173
|
|
|
174
|
+
/// @notice Resolves the current holder of a project's ownership NFT, or `address(0)` if the project's NFT cannot
|
|
175
|
+
/// be read.
|
|
176
|
+
/// @dev Wraps `PROJECTS.ownerOf` in a try-catch so an unreadable project (for example an NFT that has not been
|
|
177
|
+
/// minted yet) resolves to `address(0)` and owner-gated logic fails closed instead of bubbling the revert. The
|
|
178
|
+
/// resolution lives in one place so the try-catch lives in a single function rather than at every call site and in
|
|
179
|
+
/// every contract that inherits this.
|
|
180
|
+
/// @param projectId The ID of the project whose owner to resolve.
|
|
181
|
+
/// @return projectOwner The project's current owner, or `address(0)` if `PROJECTS.ownerOf` reverts.
|
|
182
|
+
function _projectOwnerOf(uint256 projectId) internal view virtual returns (address projectOwner) {
|
|
183
|
+
try PROJECTS.ownerOf(projectId) returns (address resolved) {
|
|
184
|
+
projectOwner = resolved;
|
|
185
|
+
} catch {
|
|
186
|
+
projectOwner = address(0);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
182
190
|
//*********************************************************************//
|
|
183
191
|
// ---------------------- public transactions ------------------------ //
|
|
184
192
|
//*********************************************************************//
|
|
@@ -288,11 +296,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
288
296
|
if (ownerInfo.projectId == 0) {
|
|
289
297
|
oldOwner = ownerInfo.owner;
|
|
290
298
|
} else {
|
|
291
|
-
|
|
292
|
-
oldOwner = projectOwner;
|
|
293
|
-
} catch {
|
|
294
|
-
oldOwner = address(0);
|
|
295
|
-
}
|
|
299
|
+
oldOwner = _projectOwnerOf(ownerInfo.projectId);
|
|
296
300
|
}
|
|
297
301
|
// Explicit ownership transfers clear delegated access and the owner who authorized it.
|
|
298
302
|
jbOwner = JBOwner({owner: newOwner, projectId: projectId, permissionId: 0});
|