@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 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
- Architecture: [ARCHITECTURE.md](./ARCHITECTURE.md)
6
- User journeys: [USER_JOURNEYS.md](./USER_JOURNEYS.md)
7
- Skills: [SKILLS.md](./SKILLS.md)
8
- Risks: [RISKS.md](./RISKS.md)
9
- Administration: [ADMINISTRATION.md](./ADMINISTRATION.md)
10
- Audit instructions: [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md)
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 can satisfy `onlyOwner` through a configured `JBPermissions` permission ID
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/audit/CodexNemesisPermissionReactivation.t.sol`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/ownable-v6",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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). The owner can delegate access to other addresses by configuring a `permissionId` in `JBPermissions`.
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 The owner can give owner access to other addresses through the `permissions` contract.
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 (only the direct owner can call).
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.
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). The owner can
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 the owner can use to give other addresses owner access.
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