@bananapus/ownable-v6 0.0.32 → 0.0.34
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 +20 -12
- package/package.json +1 -1
- package/src/JBOwnableOverrides.sol +31 -8
- package/src/interfaces/IJBOwnable.sol +4 -3
package/README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# Juicebox Ownable
|
|
2
2
|
|
|
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.
|
|
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 project-scoped 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
|
|
|
@@ -15,7 +20,8 @@ This package extends the standard ownership model in three ways:
|
|
|
15
20
|
|
|
16
21
|
- ownership can point to a Juicebox project ID instead of an address
|
|
17
22
|
- `owner()` can resolve dynamically to the current holder of that project NFT
|
|
18
|
-
- delegated operators
|
|
23
|
+
- project-owned contracts can let delegated operators satisfy `onlyOwner` through a configured `JBPermissions` permission ID
|
|
24
|
+
- address-owned contracts are direct-owner-only
|
|
19
25
|
|
|
20
26
|
For contracts that are already meant to be owned by a project, this avoids manual ownership transfers when the project NFT changes hands.
|
|
21
27
|
|
|
@@ -36,7 +42,7 @@ If the issue is in project ownership itself, start in `nana-core-v6` and `JBProj
|
|
|
36
42
|
This package is a small ownership adapter:
|
|
37
43
|
|
|
38
44
|
1. resolve who the effective owner is
|
|
39
|
-
2. optionally allow a delegated permission to satisfy `onlyOwner`
|
|
45
|
+
2. optionally allow a delegated permission to satisfy `onlyOwner` when the contract is project-owned
|
|
40
46
|
3. preserve an `Ownable`-like interface for downstream contracts
|
|
41
47
|
|
|
42
48
|
## Read These Files First
|
|
@@ -49,7 +55,7 @@ This package is a small ownership adapter:
|
|
|
49
55
|
|
|
50
56
|
- ownership may resolve to a project NFT holder instead of a fixed address, so caching `owner()` off-chain can go stale
|
|
51
57
|
- `owner()` can resolve to `address(0)` if the referenced project NFT is invalid or unreadable, which effectively renounces the contract
|
|
52
|
-
- delegated operator access depends on a chosen permission ID, not on a generic admin role
|
|
58
|
+
- delegated operator access only applies in project-owned mode and depends on a chosen permission ID, not on a generic admin role
|
|
53
59
|
- explicit ownership transfers reset the permission ID, but project NFT transfers do not mutate stored owner data
|
|
54
60
|
- a project NFT round trip back to the owner who last set `permissionId` can reactivate that owner's still-granted delegates
|
|
55
61
|
- ownership transfer and permission-ID updates are part of the security model, not just convenience helpers
|
|
@@ -67,7 +73,8 @@ This package is a small ownership adapter:
|
|
|
67
73
|
3. `test/RegressionUnmintedProjectHijack.t.sol`
|
|
68
74
|
4. `test/regression/BurnLockProtection.t.sol`
|
|
69
75
|
5. `test/regression/PermissionIdNFTTransfer.t.sol`
|
|
70
|
-
6. `test/
|
|
76
|
+
6. `test/regression/StaleDelegateReactivationOnProjectReturn.t.sol`
|
|
77
|
+
7. `test/regression/AddressOwnerPermissionPolicy.t.sol`
|
|
71
78
|
|
|
72
79
|
## Install
|
|
73
80
|
|
|
@@ -98,7 +105,8 @@ test/
|
|
|
98
105
|
## Risks And Notes
|
|
99
106
|
|
|
100
107
|
- if ownership is tied to a project NFT and that NFT becomes unreachable, the contract is effectively locked
|
|
101
|
-
- delegated access depends on a chosen permission ID, so bad permission selection is an operational risk
|
|
108
|
+
- project-owned delegated access depends on a chosen permission ID, so bad permission selection is an operational risk
|
|
109
|
+
- address-owned contracts cannot enable delegated owner access
|
|
102
110
|
- permission IDs reset on explicit ownership transfers; project NFT transfers leave the ID stored but stale unless the
|
|
103
111
|
resolved owner still matches the owner who set it
|
|
104
112
|
- 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)`
|
package/package.json
CHANGED
|
@@ -12,7 +12,8 @@ import {JBOwner} from "./structs/JBOwner.sol";
|
|
|
12
12
|
|
|
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
|
-
/// NFT).
|
|
15
|
+
/// NFT). A project-based owner can delegate access by configuring a `permissionId` in `JBPermissions`; address-based
|
|
16
|
+
/// ownership is direct-owner-only.
|
|
16
17
|
/// @dev Project NFT transfers do not update stored owner data. A nonzero `permissionId` is only effective while the
|
|
17
18
|
/// resolved owner still equals `_permissionOwner`, the owner who last set that ID. If the NFT leaves and later returns
|
|
18
19
|
/// to that owner, their still-granted delegate permissions become effective again.
|
|
@@ -21,6 +22,11 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
21
22
|
// --------------------------- custom errors ------------------------- //
|
|
22
23
|
//*********************************************************************//
|
|
23
24
|
|
|
25
|
+
/// @notice Thrown when address-based ownership tries to enable `JBPermissions` delegation.
|
|
26
|
+
/// @param owner The address-based owner.
|
|
27
|
+
/// @param permissionId The nonzero permission ID being set.
|
|
28
|
+
error JBOwnableOverrides_AddressOwnerCannotSetPermissionId(address owner, uint8 permissionId);
|
|
29
|
+
|
|
24
30
|
/// @notice Thrown when an ownership transfer or constructor input does not identify exactly one valid owner.
|
|
25
31
|
/// @param newOwner The address owner being set.
|
|
26
32
|
/// @param projectId The project owner ID being set.
|
|
@@ -61,7 +67,9 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
61
67
|
/// the zero address as the `initialOwner`.
|
|
62
68
|
/// To restrict access to a specific address, pass that address as the `initialOwner` and `0` as the
|
|
63
69
|
/// `initialProjectIdOwner`.
|
|
64
|
-
/// @dev
|
|
70
|
+
/// @dev Project-based owners can give owner access to other addresses through the `permissions` contract.
|
|
71
|
+
/// Address-based owners cannot enable delegated owner access because `JBPermissions` project ID `0` is the
|
|
72
|
+
/// wildcard project namespace.
|
|
65
73
|
/// @dev If `initialProjectIdOwner` references an unminted project, `owner()` resolves to `address(0)` and
|
|
66
74
|
/// owner-gated calls revert until that project is created. The first account to mint that project becomes the
|
|
67
75
|
/// effective owner, so deployers must control the mint sequence.
|
|
@@ -131,6 +139,15 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
131
139
|
address resolvedOwner;
|
|
132
140
|
if (ownerInfo.projectId == 0) {
|
|
133
141
|
resolvedOwner = ownerInfo.owner;
|
|
142
|
+
|
|
143
|
+
// Address-owned contracts do not have a safe project namespace in JBPermissions: project ID 0 is the
|
|
144
|
+
// wildcard scope. Keep address-based ownership direct-owner-only.
|
|
145
|
+
if (_msgSender() != resolvedOwner) {
|
|
146
|
+
revert JBPermissioned.JBPermissioned_Unauthorized({
|
|
147
|
+
account: resolvedOwner, sender: _msgSender(), projectId: 0, permissionId: 0
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
134
151
|
} else {
|
|
135
152
|
// Resolve the project owner dynamically; unreadable projects fail closed to address(0).
|
|
136
153
|
try PROJECTS.ownerOf(ownerInfo.projectId) returns (address projectOwner) {
|
|
@@ -173,10 +190,10 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
173
190
|
_transferOwnership({newOwner: address(0), projectId: 0});
|
|
174
191
|
}
|
|
175
192
|
|
|
176
|
-
/// @notice Configures which `JBPermissions` permission ID grants delegate access to `onlyOwner` functions
|
|
177
|
-
/// Set to 0 to disable delegation entirely
|
|
178
|
-
/// @dev Can only be called by the current owner.
|
|
179
|
-
/// different owner holds the project NFT.
|
|
193
|
+
/// @notice Configures which `JBPermissions` permission ID grants delegate access to `onlyOwner` functions while
|
|
194
|
+
/// this contract is project-owned. Set to 0 to disable delegation entirely.
|
|
195
|
+
/// @dev Can only be called by the current owner. Address-owned contracts can only set `permissionId` to 0.
|
|
196
|
+
/// Records the current owner so delegation is ignored while a different owner holds the project NFT.
|
|
180
197
|
/// @param permissionId The permission ID to use for `onlyOwner` delegation.
|
|
181
198
|
function setPermissionId(uint8 permissionId) public virtual override {
|
|
182
199
|
_checkOwner();
|
|
@@ -231,10 +248,16 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
|
|
|
231
248
|
/// @param newProjectId The ID of the new owning project (zero if transferring to an address).
|
|
232
249
|
function _emitTransferEvent(address previousOwner, address newOwner, uint88 newProjectId) internal virtual;
|
|
233
250
|
|
|
234
|
-
/// @notice Sets the permission ID the current owner can use to delegate owner access.
|
|
235
|
-
/// @dev Internal function without access restriction.
|
|
251
|
+
/// @notice Sets the permission ID the current project owner can use to delegate owner access.
|
|
252
|
+
/// @dev Internal function without access restriction. Address-owned contracts can only clear the permission ID.
|
|
236
253
|
/// @param permissionId The permission ID to use for `onlyOwner`.
|
|
237
254
|
function _setPermissionId(uint8 permissionId) internal virtual {
|
|
255
|
+
if (jbOwner.projectId == 0 && permissionId != 0) {
|
|
256
|
+
revert JBOwnableOverrides_AddressOwnerCannotSetPermissionId({
|
|
257
|
+
owner: jbOwner.owner, permissionId: permissionId
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
238
261
|
jbOwner.permissionId = permissionId;
|
|
239
262
|
_permissionOwner = owner();
|
|
240
263
|
emit PermissionIdChanged({newId: permissionId, caller: _msgSender()});
|
|
@@ -4,8 +4,8 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
5
5
|
|
|
6
6
|
/// @notice Interface for Juicebox-aware ownership. Supports two modes: address-based (a fixed EOA/contract owns the
|
|
7
|
-
/// contract) or project-based (whoever holds a specific Juicebox project's ERC-721 NFT is the owner).
|
|
8
|
-
/// delegate access to other addresses via `JBPermissions
|
|
7
|
+
/// contract) or project-based (whoever holds a specific Juicebox project's ERC-721 NFT is the owner). Project-based
|
|
8
|
+
/// owners can delegate access to other addresses via `JBPermissions`; address-based ownership is direct-owner-only.
|
|
9
9
|
interface IJBOwnable {
|
|
10
10
|
/// @notice Emitted when ownership is transferred to a new owner.
|
|
11
11
|
/// @param previousOwner The address of the previous owner.
|
|
@@ -36,7 +36,8 @@ interface IJBOwnable {
|
|
|
36
36
|
/// @notice Gives up ownership, making it impossible to call `onlyOwner` functions.
|
|
37
37
|
function renounceOwnership() external;
|
|
38
38
|
|
|
39
|
-
/// @notice Sets the permission ID
|
|
39
|
+
/// @notice Sets the permission ID a project-based owner can use to give other addresses owner access.
|
|
40
|
+
/// @dev Address-based owners can only set `permissionId` to 0.
|
|
40
41
|
/// @param permissionId The permission ID to use for `onlyOwner`.
|
|
41
42
|
function setPermissionId(uint8 permissionId) external;
|
|
42
43
|
|