@bananapus/ownable-v6 0.0.31 → 0.0.33
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 +17 -7
- package/package.json +3 -3
- package/references/operations.md +5 -2
- package/references/runtime.md +3 -2
- package/src/JBOwnable.sol +7 -8
- package/src/JBOwnableOverrides.sol +35 -41
- package/src/interfaces/IJBOwnable.sol +3 -2
- package/src/structs/JBOwner.sol +2 -2
package/README.md
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- [ARCHITECTURE.md](./ARCHITECTURE.md) — system architecture and component interactions
|
|
8
|
+
- [INVARIANTS.md](./INVARIANTS.md) — guarantees enforced by the contracts
|
|
9
|
+
- [USER_JOURNEYS.md](./USER_JOURNEYS.md) — typical flows for each actor
|
|
10
|
+
- [RISKS.md](./RISKS.md) — known risks and operational caveats
|
|
11
|
+
- [ADMINISTRATION.md](./ADMINISTRATION.md) — administrative powers and lifecycle
|
|
12
|
+
- [SKILLS.md](./SKILLS.md) — reusable patterns and gotchas for builders
|
|
13
|
+
- [STYLE_GUIDE.md](./STYLE_GUIDE.md) — code style conventions
|
|
14
|
+
- [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — guidance for auditors
|
|
15
|
+
- [CHANGELOG.md](./CHANGELOG.md) — release notes
|
|
11
16
|
|
|
12
17
|
## Overview
|
|
13
18
|
|
|
@@ -50,6 +55,8 @@ This package is a small ownership adapter:
|
|
|
50
55
|
- ownership may resolve to a project NFT holder instead of a fixed address, so caching `owner()` off-chain can go stale
|
|
51
56
|
- `owner()` can resolve to `address(0)` if the referenced project NFT is invalid or unreadable, which effectively renounces the contract
|
|
52
57
|
- delegated operator access depends on a chosen permission ID, not on a generic admin role
|
|
58
|
+
- explicit ownership transfers reset the permission ID, but project NFT transfers do not mutate stored owner data
|
|
59
|
+
- a project NFT round trip back to the owner who last set `permissionId` can reactivate that owner's still-granted delegates
|
|
53
60
|
- ownership transfer and permission-ID updates are part of the security model, not just convenience helpers
|
|
54
61
|
|
|
55
62
|
## Where State Lives
|
|
@@ -64,6 +71,8 @@ This package is a small ownership adapter:
|
|
|
64
71
|
2. `test/OwnableAttacks.t.sol`
|
|
65
72
|
3. `test/RegressionUnmintedProjectHijack.t.sol`
|
|
66
73
|
4. `test/regression/BurnLockProtection.t.sol`
|
|
74
|
+
5. `test/regression/PermissionIdNFTTransfer.t.sol`
|
|
75
|
+
6. `test/audit/CodexNemesisPermissionReactivation.t.sol`
|
|
67
76
|
|
|
68
77
|
## Install
|
|
69
78
|
|
|
@@ -95,7 +104,8 @@ test/
|
|
|
95
104
|
|
|
96
105
|
- if ownership is tied to a project NFT and that NFT becomes unreachable, the contract is effectively locked
|
|
97
106
|
- delegated access depends on a chosen permission ID, so bad permission selection is an operational risk
|
|
98
|
-
- permission IDs reset on ownership
|
|
107
|
+
- permission IDs reset on explicit ownership transfers; project NFT transfers leave the ID stored but stale unless the
|
|
108
|
+
resolved owner still matches the owner who set it
|
|
99
109
|
- 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
110
|
|
|
101
111
|
## For AI Agents
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/ownable-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.33",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/Bananapus/nana-ownable-v6"
|
|
7
|
+
"url": "git+https://github.com/Bananapus/nana-ownable-v6.git"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"foundry.toml",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"node": ">=20.0.0"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bananapus/core-v6": "^0.0.
|
|
20
|
+
"@bananapus/core-v6": "^0.0.72",
|
|
21
21
|
"@bananapus/permission-ids-v6": "^0.0.27",
|
|
22
22
|
"@openzeppelin/contracts": "5.6.1"
|
|
23
23
|
},
|
package/references/operations.md
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
## Change Checklist
|
|
4
4
|
|
|
5
5
|
- If you edit owner resolution, verify both direct ownership and project-owned cases.
|
|
6
|
-
- If you edit permission handling, verify transfer
|
|
7
|
-
|
|
6
|
+
- If you edit permission handling, verify explicit transfer resets, project NFT transfer staleness, and NFT round-trip
|
|
7
|
+
reactivation.
|
|
8
|
+
- If an integration expects long-lived delegated access, confirm whether explicit transfers clear it or project NFT
|
|
9
|
+
transfers merely make it stale.
|
|
8
10
|
- If the change touches project ownership, check unminted-project and burn-lock regressions before assuming the happy-path tests are enough.
|
|
9
11
|
|
|
10
12
|
## Common Failure Modes
|
|
@@ -12,3 +14,4 @@
|
|
|
12
14
|
- Integrations assume delegated operators survive ownership transfer.
|
|
13
15
|
- Bugs are blamed on this repo when the underlying project NFT ownership changed upstream.
|
|
14
16
|
- A project-owned contract is treated like an address-owned contract and the wrong actor is allowed through `onlyOwner`.
|
|
17
|
+
- Operators are assumed inactive after a project NFT round trip even though the prior owner's grants can reactivate.
|
package/references/runtime.md
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
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
|
-
- Transfer semantics:
|
|
12
|
+
- Transfer semantics: explicit ownable transfers reset permission IDs, while project NFT transfers preserve the stored
|
|
13
|
+
ID and rely on `_permissionOwner` to decide whether it is effective.
|
|
13
14
|
|
|
14
15
|
## Tests To Trust First
|
|
15
16
|
|
|
16
17
|
- [`test/Ownable.t.sol`](../test/Ownable.t.sol) for baseline behavior.
|
|
17
18
|
- [`test/OwnableEdgeCases.t.sol`](../test/OwnableEdgeCases.t.sol) and [`test/OwnableAttacks.t.sol`](../test/OwnableAttacks.t.sol) for edge and adversarial cases.
|
|
18
19
|
- [`test/OwnableInvariantTests.sol`](../test/OwnableInvariantTests.sol) for broader invariants.
|
|
19
|
-
- [`test/regression/BurnLockProtection.t.sol`](../test/regression/BurnLockProtection.t.sol), [`test/regression/
|
|
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/audit/CodexNemesisPermissionReactivation.t.sol`](../test/audit/CodexNemesisPermissionReactivation.t.sol) for the regressions most likely to matter in review.
|
package/src/JBOwnable.sol
CHANGED
|
@@ -8,7 +8,7 @@ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.s
|
|
|
8
8
|
import {JBOwnableOverrides} from "./JBOwnableOverrides.sol";
|
|
9
9
|
|
|
10
10
|
/// @notice Juicebox-aware ownership for any contract. Inherit this and apply the `onlyOwner` modifier to restrict
|
|
11
|
-
/// functions to the project owner, a fixed address, or
|
|
11
|
+
/// functions to the project owner, a fixed address, or an effective delegate authorized through `JBPermissions`.
|
|
12
12
|
/// @dev Ownership resolves dynamically: if `JBOwner.projectId` is set, the holder of that project's ERC-721 NFT is
|
|
13
13
|
/// the owner. If `projectId` is 0, the stored `JBOwner.owner` address is used instead. The owner can delegate access
|
|
14
14
|
/// to other addresses by setting a `permissionId` and granting that permission through `JBPermissions`.
|
|
@@ -50,11 +50,11 @@ contract JBOwnable is JBOwnableOverrides {
|
|
|
50
50
|
// ------------------------ internal functions ----------------------- //
|
|
51
51
|
//*********************************************************************//
|
|
52
52
|
|
|
53
|
-
/// @notice
|
|
54
|
-
/// @dev
|
|
55
|
-
///
|
|
56
|
-
/// @dev Unlike `_transferOwnership` (which uses try-catch to resolve the *old* owner in case its project NFT
|
|
57
|
-
///
|
|
53
|
+
/// @notice Emits the ownership transfer event after resolving the visible new owner address.
|
|
54
|
+
/// @dev Constructor pre-binding can point ownership at a project before the project's NFT has been minted, so the
|
|
55
|
+
/// event resolves the project's current owner at emission time.
|
|
56
|
+
/// @dev Unlike `_transferOwnership` (which uses try-catch to resolve the *old* owner in case its project NFT is
|
|
57
|
+
/// unreadable), this function resolves the *new* owner's current address for event purposes only.
|
|
58
58
|
/// If the new project NFT does not exist yet, the event uses `address(0)` until ownership can resolve normally.
|
|
59
59
|
function _emitTransferEvent(
|
|
60
60
|
address previousOwner,
|
|
@@ -70,8 +70,7 @@ contract JBOwnable is JBOwnableOverrides {
|
|
|
70
70
|
try PROJECTS.ownerOf(newProjectId) returns (address projectOwner) {
|
|
71
71
|
resolvedNewOwner = projectOwner;
|
|
72
72
|
} catch {
|
|
73
|
-
//
|
|
74
|
-
// once the project NFT exists, so the transfer event uses address(0) until then.
|
|
73
|
+
// Pre-bound future projects have no visible owner yet, so the event reports address(0).
|
|
75
74
|
resolvedNewOwner = address(0);
|
|
76
75
|
}
|
|
77
76
|
}
|
|
@@ -13,9 +13,9 @@ import {JBOwner} from "./structs/JBOwner.sol";
|
|
|
13
13
|
/// @notice Abstract base implementing Juicebox-aware ownership resolution, transfer, and permission delegation.
|
|
14
14
|
/// Ownership is either address-based (a fixed EOA/contract) or project-based (whoever holds the project's ERC-721
|
|
15
15
|
/// NFT). The owner can delegate access to other addresses by configuring a `permissionId` in `JBPermissions`.
|
|
16
|
-
/// @dev
|
|
17
|
-
///
|
|
18
|
-
///
|
|
16
|
+
/// @dev Project NFT transfers do not update stored owner data. A nonzero `permissionId` is only effective while the
|
|
17
|
+
/// resolved owner still equals `_permissionOwner`, the owner who last set that ID. If the NFT leaves and later returns
|
|
18
|
+
/// to that owner, their still-granted delegate permissions become effective again.
|
|
19
19
|
abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
20
20
|
//*********************************************************************//
|
|
21
21
|
// --------------------------- custom errors ------------------------- //
|
|
@@ -49,8 +49,8 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
49
49
|
// -------------------- internal stored properties ------------------- //
|
|
50
50
|
//*********************************************************************//
|
|
51
51
|
|
|
52
|
-
/// @notice The resolved owner address at the time permissionId was last set.
|
|
53
|
-
/// @dev Used to
|
|
52
|
+
/// @notice The resolved owner address at the time `permissionId` was last set.
|
|
53
|
+
/// @dev Used to ignore delegated permissions while project ownership is held by someone else.
|
|
54
54
|
address internal _permissionOwner;
|
|
55
55
|
|
|
56
56
|
//*********************************************************************//
|
|
@@ -62,10 +62,9 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
62
62
|
/// To restrict access to a specific address, pass that address as the `initialOwner` and `0` as the
|
|
63
63
|
/// `initialProjectIdOwner`.
|
|
64
64
|
/// @dev The owner can give owner access to other addresses through the `permissions` contract.
|
|
65
|
-
/// @dev If `initialProjectIdOwner` references
|
|
66
|
-
/// revert until that project is created
|
|
67
|
-
///
|
|
68
|
-
/// assumption.
|
|
65
|
+
/// @dev If `initialProjectIdOwner` references an unminted project, `owner()` resolves to `address(0)` and
|
|
66
|
+
/// owner-gated calls revert until that project is created. The first account to mint that project becomes the
|
|
67
|
+
/// effective owner, so deployers must control the mint sequence.
|
|
69
68
|
/// @param permissions A contract storing permissions. Assumed to be a valid deployment-time dependency.
|
|
70
69
|
/// @param projects Mints ERC-721s that represent project ownership and transfers. Assumed to be a valid
|
|
71
70
|
/// deployment-time dependency.
|
|
@@ -82,18 +81,14 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
82
81
|
{
|
|
83
82
|
PROJECTS = projects;
|
|
84
83
|
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// It's more likely both were accidentally set to `0`. If you really want an unowned contract, set the owner to
|
|
88
|
-
// an address and call `renounceOwnership()` in the constructor body.
|
|
84
|
+
// Require an initial owner. To deploy unowned on purpose, deploy with an address owner and call
|
|
85
|
+
// `renounceOwnership()` from the inheriting constructor.
|
|
89
86
|
if (initialProjectIdOwner == 0 && initialOwner == address(0)) {
|
|
90
87
|
revert JBOwnableOverrides_InvalidNewOwner({newOwner: initialOwner, projectId: initialProjectIdOwner});
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
// in `owner()` treats this as renounced (returns address(0)), effectively locking the contract until
|
|
96
|
-
// the project is minted. This is acceptable because deployers control the constructor arguments.
|
|
90
|
+
// Constructors may pre-bind ownership to a project that will be minted later. Until then, `owner()` returns
|
|
91
|
+
// address(0); once minted, ownership follows whoever received that project NFT.
|
|
97
92
|
_transferOwnership({newOwner: initialOwner, projectId: initialProjectIdOwner});
|
|
98
93
|
}
|
|
99
94
|
|
|
@@ -103,9 +98,8 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
103
98
|
|
|
104
99
|
/// @notice Returns the current owner's address. If ownership is project-based, this dynamically resolves to
|
|
105
100
|
/// whoever holds the project's ERC-721 NFT right now.
|
|
106
|
-
/// @dev If `projectId` is non-zero, resolves via `PROJECTS.ownerOf()`. If that call reverts
|
|
107
|
-
///
|
|
108
|
-
/// so this is a defensive measure only.
|
|
101
|
+
/// @dev If `projectId` is non-zero, resolves via `PROJECTS.ownerOf()`. If that call reverts, returns
|
|
102
|
+
/// `address(0)`, making owner-gated functions fail closed.
|
|
109
103
|
function owner() public view virtual returns (address) {
|
|
110
104
|
JBOwner memory ownerInfo = jbOwner;
|
|
111
105
|
|
|
@@ -113,8 +107,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
113
107
|
return ownerInfo.owner;
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
//
|
|
117
|
-
// If ownerOf reverts, the contract is effectively renounced (returns address(0)).
|
|
110
|
+
// If the project owner cannot be read, expose the owner as zero instead of bubbling the upstream revert.
|
|
118
111
|
try PROJECTS.ownerOf(ownerInfo.projectId) returns (address projectOwner) {
|
|
119
112
|
return projectOwner;
|
|
120
113
|
} catch {
|
|
@@ -127,10 +120,11 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
127
120
|
//*********************************************************************//
|
|
128
121
|
|
|
129
122
|
/// @notice Reverts if the caller is not the owner (or an authorized delegate when `permissionId` is set).
|
|
130
|
-
/// @dev If `projectId` is non-zero and `PROJECTS.ownerOf()` reverts
|
|
131
|
-
///
|
|
132
|
-
/// @dev
|
|
133
|
-
///
|
|
123
|
+
/// @dev If `projectId` is non-zero and `PROJECTS.ownerOf()` reverts, the resolved owner is `address(0)`, causing
|
|
124
|
+
/// all `_checkOwner` calls to revert.
|
|
125
|
+
/// @dev A nonzero `permissionId` only delegates while the current resolved owner equals `_permissionOwner`.
|
|
126
|
+
/// Project NFT transfers therefore disable delegation for the new holder until they call `setPermissionId()`;
|
|
127
|
+
/// returning the NFT to the original `_permissionOwner` can reactivate their old delegate grants.
|
|
134
128
|
function _checkOwner() internal view virtual {
|
|
135
129
|
JBOwner memory ownerInfo = jbOwner;
|
|
136
130
|
|
|
@@ -138,7 +132,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
138
132
|
if (ownerInfo.projectId == 0) {
|
|
139
133
|
resolvedOwner = ownerInfo.owner;
|
|
140
134
|
} else {
|
|
141
|
-
//
|
|
135
|
+
// Resolve the project owner dynamically; unreadable projects fail closed to address(0).
|
|
142
136
|
try PROJECTS.ownerOf(ownerInfo.projectId) returns (address projectOwner) {
|
|
143
137
|
resolvedOwner = projectOwner;
|
|
144
138
|
} catch {
|
|
@@ -146,14 +140,13 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
146
140
|
}
|
|
147
141
|
}
|
|
148
142
|
|
|
149
|
-
//
|
|
150
|
-
// (e.g., project NFT transferred), treat permissionId as 0 (direct-owner-only).
|
|
143
|
+
// Ignore the stored permission ID while the project NFT is held by a different owner than the one who set it.
|
|
151
144
|
uint8 effectivePermissionId = ownerInfo.permissionId;
|
|
152
145
|
if (effectivePermissionId != 0 && resolvedOwner != _permissionOwner) {
|
|
153
146
|
effectivePermissionId = 0;
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
// When
|
|
149
|
+
// When delegation is disabled or stale, bypass the permission system entirely.
|
|
157
150
|
// This ensures ROOT operators cannot act as owner when delegation is disabled.
|
|
158
151
|
if (effectivePermissionId == 0) {
|
|
159
152
|
if (_msgSender() != resolvedOwner) {
|
|
@@ -182,8 +175,8 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
182
175
|
|
|
183
176
|
/// @notice Configures which `JBPermissions` permission ID grants delegate access to `onlyOwner` functions.
|
|
184
177
|
/// Set to 0 to disable delegation entirely (only the direct owner can call).
|
|
185
|
-
/// @dev Can only be called by the current owner. Records the current owner so
|
|
186
|
-
///
|
|
178
|
+
/// @dev Can only be called by the current owner. Records the current owner so delegation is ignored while a
|
|
179
|
+
/// different owner holds the project NFT.
|
|
187
180
|
/// @param permissionId The permission ID to use for `onlyOwner` delegation.
|
|
188
181
|
function setPermissionId(uint8 permissionId) public virtual override {
|
|
189
182
|
_checkOwner();
|
|
@@ -216,7 +209,8 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
216
209
|
revert JBOwnableOverrides_InvalidNewOwner({newOwner: address(0), projectId: projectId});
|
|
217
210
|
}
|
|
218
211
|
|
|
219
|
-
//
|
|
212
|
+
// Public project transfers require an already-minted project. Constructor pre-binding is the only path that
|
|
213
|
+
// can point at a future project ID.
|
|
220
214
|
if (projectId > PROJECTS.count()) {
|
|
221
215
|
revert JBOwnableOverrides_ProjectDoesNotExist({projectId: projectId, projectCount: PROJECTS.count()});
|
|
222
216
|
}
|
|
@@ -229,7 +223,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
229
223
|
// ------------------------ internal functions ----------------------- //
|
|
230
224
|
//*********************************************************************//
|
|
231
225
|
|
|
232
|
-
/// @notice
|
|
226
|
+
/// @notice Emits the ownership transfer event after resolving the visible new owner address.
|
|
233
227
|
/// @dev This function exists because some contracts need to deploy contracts for a project before the project's NFT
|
|
234
228
|
/// has been minted, so the transfer event resolves the project's current owner at emission time.
|
|
235
229
|
/// @param previousOwner The address of the previous owner.
|
|
@@ -237,7 +231,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
237
231
|
/// @param newProjectId The ID of the new owning project (zero if transferring to an address).
|
|
238
232
|
function _emitTransferEvent(address previousOwner, address newOwner, uint88 newProjectId) internal virtual;
|
|
239
233
|
|
|
240
|
-
/// @notice Sets the permission ID the owner can use to
|
|
234
|
+
/// @notice Sets the permission ID the current owner can use to delegate owner access.
|
|
241
235
|
/// @dev Internal function without access restriction.
|
|
242
236
|
/// @param permissionId The permission ID to use for `onlyOwner`.
|
|
243
237
|
function _setPermissionId(uint8 permissionId) internal virtual {
|
|
@@ -252,20 +246,21 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
252
246
|
_transferOwnership({newOwner: newOwner, projectId: 0});
|
|
253
247
|
}
|
|
254
248
|
|
|
255
|
-
/// @notice Transfers this contract's ownership to an address (`newOwner`)
|
|
249
|
+
/// @notice Transfers this contract's ownership to either an address (`newOwner`) or a Juicebox project
|
|
250
|
+
/// (`projectId`).
|
|
256
251
|
/// @dev Updates this contract's `JBOwner` owner information and resets the `JBOwner.permissionId`.
|
|
257
252
|
/// @dev If both `newOwner` and `projectId` are set, this will revert.
|
|
258
253
|
/// @dev Internal function without access restriction.
|
|
259
254
|
/// @param newOwner The address that should become this contract's owner.
|
|
260
255
|
/// @param projectId The ID of the project whose owner should become this contract's owner.
|
|
261
256
|
function _transferOwnership(address newOwner, uint88 projectId) internal virtual {
|
|
262
|
-
//
|
|
257
|
+
// Ownership has exactly one live mode: address owner, project owner, or neither after renounce.
|
|
263
258
|
if (projectId != 0 && newOwner != address(0)) {
|
|
264
259
|
revert JBOwnableOverrides_InvalidNewOwner({newOwner: newOwner, projectId: projectId});
|
|
265
260
|
}
|
|
266
|
-
//
|
|
261
|
+
// Snapshot the current owner configuration before replacing it.
|
|
267
262
|
JBOwner memory ownerInfo = jbOwner;
|
|
268
|
-
//
|
|
263
|
+
// Resolve the previous owner for the event; unreadable project ownership is reported as address(0).
|
|
269
264
|
address oldOwner;
|
|
270
265
|
if (ownerInfo.projectId == 0) {
|
|
271
266
|
oldOwner = ownerInfo.owner;
|
|
@@ -276,8 +271,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
276
271
|
oldOwner = address(0);
|
|
277
272
|
}
|
|
278
273
|
}
|
|
279
|
-
//
|
|
280
|
-
// This is to prevent permissions clashes for the new user/owner.
|
|
274
|
+
// Explicit ownership transfers clear delegated access and the owner who authorized it.
|
|
281
275
|
jbOwner = JBOwner({owner: newOwner, projectId: projectId, permissionId: 0});
|
|
282
276
|
_permissionOwner = address(0);
|
|
283
277
|
// Emit a transfer event with the new owner's address.
|
|
@@ -22,10 +22,11 @@ interface IJBOwnable {
|
|
|
22
22
|
/// @return projects The `IJBProjects` contract.
|
|
23
23
|
function PROJECTS() external view returns (IJBProjects projects);
|
|
24
24
|
|
|
25
|
-
/// @notice The
|
|
25
|
+
/// @notice The stored ownership state and delegation policy.
|
|
26
26
|
/// @return owner The owner address (used when `projectId` is 0).
|
|
27
27
|
/// @return projectId The Juicebox project whose NFT holder is the owner (0 if address-based ownership).
|
|
28
|
-
/// @return permissionId The permission ID
|
|
28
|
+
/// @return permissionId The stored permission ID. It only delegates while the resolved owner matches the owner who
|
|
29
|
+
/// last set it.
|
|
29
30
|
function jbOwner() external view returns (address owner, uint88 projectId, uint8 permissionId);
|
|
30
31
|
|
|
31
32
|
/// @notice Returns the current owner's address.
|
package/src/structs/JBOwner.sol
CHANGED
|
@@ -5,8 +5,8 @@ pragma solidity ^0.8.0;
|
|
|
5
5
|
/// @custom:member owner The owner address — only used when `projectId` is 0 (address-based ownership mode).
|
|
6
6
|
/// @custom:member projectId If non-zero, the holder of this Juicebox project's ERC-721 NFT is the owner. When set,
|
|
7
7
|
/// the `owner` field is ignored and ownership resolves dynamically via `JBProjects.ownerOf(projectId)`.
|
|
8
|
-
/// @custom:member permissionId The permission ID that delegates can hold
|
|
9
|
-
///
|
|
8
|
+
/// @custom:member permissionId The permission ID that delegates can hold via `JBPermissions`. Nonzero IDs only count
|
|
9
|
+
/// while the resolved owner matches the owner who last set the ID. Set to 0 to disable delegation entirely.
|
|
10
10
|
struct JBOwner {
|
|
11
11
|
address owner;
|
|
12
12
|
uint88 projectId;
|