@bananapus/ownable-v6 0.0.8 → 0.0.10

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.
@@ -0,0 +1,245 @@
1
+ # User Journeys -- nana-ownable-v6
2
+
3
+ Concrete end-to-end flows through the JBOwnable system. Each journey traces the exact function calls, state changes, and external interactions.
4
+
5
+ ## Journey 1: Deploy a Project-Owned Contract
6
+
7
+ **Actor:** Protocol developer deploying a hook or extension that should be owned by a Juicebox project.
8
+ **Goal:** Create a contract where the project NFT holder has owner access.
9
+
10
+ ### Precondition
11
+
12
+ A Juicebox project exists with ID `projectId`. The `JBProjects` and `JBPermissions` contracts are deployed.
13
+
14
+ ### Steps
15
+
16
+ 1. **Developer deploys a contract inheriting `JBOwnable`**
17
+
18
+ ```solidity
19
+ new MyHook(permissions, projects, address(0), projectId)
20
+ ```
21
+
22
+ - `initialOwner = address(0)` because ownership is project-based
23
+ - `initialProjectIdOwner = projectId`
24
+
25
+ 2. **Constructor execution in `JBOwnableOverrides`**
26
+
27
+ - Stores `PROJECTS = projects` (immutable)
28
+ - Validates: `initialProjectIdOwner != 0` AND `address(projects) != address(0)` -- passes
29
+ - Validates: not both zero (passes because `initialProjectIdOwner != 0`)
30
+ - Calls `_transferOwnership(address(0), projectId)`:
31
+ - Sets `jbOwner = JBOwner({owner: address(0), projectId: projectId, permissionId: 0})`
32
+ - Calls `_emitTransferEvent(address(0), address(0), projectId)`
33
+ - In `JBOwnable._emitTransferEvent`: emits `OwnershipTransferred(address(0), PROJECTS.ownerOf(projectId), msg.sender)`
34
+
35
+ 3. **Ownership is now live**
36
+
37
+ - `owner()` calls `PROJECTS.ownerOf(projectId)` and returns the current NFT holder
38
+ - `_checkOwner()` validates `msg.sender` against the NFT holder (or permission delegates)
39
+
40
+ ### Result
41
+
42
+ The contract is owned by whichever address holds the project NFT. If the NFT is transferred, ownership automatically follows -- no on-chain update to the JBOwnable contract is needed.
43
+
44
+ ### What to verify
45
+
46
+ - `jbOwner.owner == address(0)` and `jbOwner.projectId == projectId` after construction.
47
+ - `jbOwner.permissionId == 0` (no delegated access until explicitly configured).
48
+ - `owner()` returns the current NFT holder, not a cached value.
49
+ - If the project does not exist (ID > `PROJECTS.count()`), the constructor still succeeds -- the existence check is only enforced in `transferOwnershipToProject`, not the constructor. Verify whether this is safe (the deployer presumably knows the project exists).
50
+
51
+ ---
52
+
53
+ ## Journey 2: Transfer Ownership to a Different Address
54
+
55
+ **Actor:** Current owner (direct address or project NFT holder).
56
+ **Goal:** Transfer ownership from the current owner to a new direct address.
57
+
58
+ ### Precondition
59
+
60
+ The caller is the current owner or has the configured `permissionId` (or ROOT) via `JBPermissions`.
61
+
62
+ ### Steps
63
+
64
+ 1. **Owner calls `transferOwnership(newOwner)`**
65
+
66
+ - `newOwner` must not be `address(0)` (reverts with `JBOwnableOverrides_InvalidNewOwner`)
67
+
68
+ 2. **`_checkOwner()` validates the caller**
69
+
70
+ - Resolves the current owner (via `PROJECTS.ownerOf()` if project-owned, or `jbOwner.owner` if address-owned)
71
+ - Calls `_requirePermissionFrom(resolvedOwner, projectId, permissionId)`
72
+ - Passes if `msg.sender == resolvedOwner` OR `msg.sender` has the required permission
73
+
74
+ 3. **`_transferOwnership(newOwner, 0)` executes the transfer**
75
+
76
+ - Records `oldOwner` (resolved from current `jbOwner`)
77
+ - Overwrites `jbOwner = JBOwner({owner: newOwner, projectId: 0, permissionId: 0})`
78
+ - Calls `_emitTransferEvent(oldOwner, newOwner, 0)`
79
+
80
+ 4. **`_emitTransferEvent` in `JBOwnable`**
81
+
82
+ - Since `newProjectId == 0`: emits `OwnershipTransferred(oldOwner, newOwner, msg.sender)`
83
+
84
+ ### Result
85
+
86
+ `jbOwner.owner == newOwner`, `jbOwner.projectId == 0`, `jbOwner.permissionId == 0`. The new owner must call `setPermissionId()` to re-enable delegated access.
87
+
88
+ ### What to verify
89
+
90
+ - If the contract was previously project-owned, `projectId` is now 0 (project ownership is cleared).
91
+ - `permissionId` is reset to 0, revoking all previously delegated permissions.
92
+ - The previous owner (or their delegates) can no longer call `onlyOwner` functions.
93
+ - `newOwner` can immediately call `onlyOwner` functions without any additional setup.
94
+
95
+ ---
96
+
97
+ ## Journey 3: Transfer Ownership to a Juicebox Project
98
+
99
+ **Actor:** Current owner (direct address or project NFT holder).
100
+ **Goal:** Transfer ownership from the current owner to a Juicebox project, so the NFT holder becomes the new owner.
101
+
102
+ ### Precondition
103
+
104
+ The target project exists (ID <= `PROJECTS.count()`). The caller is the current owner or has adequate permissions.
105
+
106
+ ### Steps
107
+
108
+ 1. **Owner calls `transferOwnershipToProject(projectId)`**
109
+
110
+ - Validates: `projectId != 0` (reverts with `JBOwnableOverrides_InvalidNewOwner`)
111
+ - Validates: `projectId <= type(uint88).max` (reverts with `JBOwnableOverrides_InvalidNewOwner`)
112
+ - Validates: `projectId <= PROJECTS.count()` (reverts with `JBOwnableOverrides_ProjectDoesNotExist`)
113
+
114
+ 2. **`_checkOwner()` validates the caller** (same as Journey 2, Step 2)
115
+
116
+ 3. **`_transferOwnership(address(0), uint88(projectId))` executes the transfer**
117
+
118
+ - Records `oldOwner` (resolved from current `jbOwner`)
119
+ - Overwrites `jbOwner = JBOwner({owner: address(0), projectId: uint88(projectId), permissionId: 0})`
120
+ - Calls `_emitTransferEvent(oldOwner, address(0), uint88(projectId))`
121
+
122
+ 4. **`_emitTransferEvent` in `JBOwnable`**
123
+
124
+ - Since `newProjectId != 0`: emits `OwnershipTransferred(oldOwner, PROJECTS.ownerOf(projectId), msg.sender)`
125
+
126
+ ### Result
127
+
128
+ `jbOwner.owner == address(0)`, `jbOwner.projectId == projectId`, `jbOwner.permissionId == 0`. The project NFT holder is now the owner. Ownership dynamically follows NFT transfers.
129
+
130
+ ### What to verify
131
+
132
+ - The project existence check (`projectId <= PROJECTS.count()`) prevents transferring to a nonexistent project.
133
+ - The `uint88` cast does not truncate (the preceding `type(uint88).max` check ensures this).
134
+ - If the project NFT is subsequently burned (hypothetically), `owner()` returns `address(0)` and the contract is effectively renounced.
135
+
136
+ ---
137
+
138
+ ## Journey 4: Delegate Access via Permission ID
139
+
140
+ **Actor:** Current owner.
141
+ **Goal:** Allow additional addresses to call `onlyOwner` functions through the JBPermissions system.
142
+
143
+ ### Precondition
144
+
145
+ The contract has an owner. The owner wants to delegate access to one or more operators.
146
+
147
+ ### Steps
148
+
149
+ 1. **Owner calls `setPermissionId(permissionId)`**
150
+
151
+ - `_checkOwner()` validates the caller
152
+ - `_setPermissionId(permissionId)` writes `jbOwner.permissionId = permissionId`
153
+ - Emits `PermissionIdChanged(permissionId, msg.sender)`
154
+
155
+ 2. **Owner grants the permission to operators via JBPermissions**
156
+
157
+ - `permissions.setPermissionsFor(account, JBPermissionsData({operator: operatorAddress, projectId: projectId, permissionIds: [permissionId]}))`
158
+ - This is an external call on the JBPermissions contract, not on the JBOwnable contract
159
+
160
+ 3. **Operator calls an `onlyOwner` function**
161
+
162
+ - `_checkOwner()` resolves the owner and calls `_requirePermissionFrom(resolvedOwner, projectId, permissionId)`
163
+ - `JBPermissioned._requirePermissionFrom` checks `JBPermissions.hasPermission(msg.sender, resolvedOwner, projectId, permissionId)` -- passes
164
+
165
+ ### Result
166
+
167
+ The operator can call any function protected by `onlyOwner` on this contract. The permission is scoped to the owner's account and project ID.
168
+
169
+ ### What to verify
170
+
171
+ - `permissionId == 0` effectively disables delegation (permission ID 0 cannot be set in `JBPermissions`). Only the owner (or ROOT holders) can call `onlyOwner` functions.
172
+ - If the owner transfers ownership, `permissionId` resets to 0. The new owner must re-configure delegation.
173
+ - ROOT (permission ID 1) always grants access regardless of the configured `permissionId`. This is a feature of `JBPermissioned`, not specific to `JBOwnable`.
174
+ - The operator's access is not stored on the JBOwnable contract -- it lives in JBPermissions. Changing the `permissionId` on JBOwnable instantly changes which JBPermissions grants are recognized.
175
+
176
+ ---
177
+
178
+ ## Journey 5: Renounce Ownership
179
+
180
+ **Actor:** Current owner.
181
+ **Goal:** Permanently give up ownership, making `onlyOwner` functions uncallable.
182
+
183
+ ### Precondition
184
+
185
+ The caller is the current owner and understands this action is irreversible.
186
+
187
+ ### Steps
188
+
189
+ 1. **Owner calls `renounceOwnership()`**
190
+
191
+ - `_checkOwner()` validates the caller
192
+
193
+ 2. **`_transferOwnership(address(0), 0)` executes**
194
+
195
+ - Records `oldOwner` (resolved from current `jbOwner`)
196
+ - Overwrites `jbOwner = JBOwner({owner: address(0), projectId: 0, permissionId: 0})`
197
+ - Calls `_emitTransferEvent(oldOwner, address(0), 0)`
198
+ - Emits `OwnershipTransferred(oldOwner, address(0), msg.sender)`
199
+
200
+ ### Result
201
+
202
+ `jbOwner` is zeroed out. `owner()` returns `address(0)`. All future calls to `_checkOwner()` revert because `_requirePermissionFrom(address(0), 0, 0)` fails for any `msg.sender` (no address equals `address(0)`, and no permission can satisfy the check against a zero-address account).
203
+
204
+ ### What to verify
205
+
206
+ - After renouncing, `transferOwnership`, `transferOwnershipToProject`, `setPermissionId`, and `renounceOwnership` all revert.
207
+ - There is no recovery mechanism. No admin backdoor. No timelock. Renouncement is permanent.
208
+ - A second call to `renounceOwnership()` also reverts (because `_checkOwner()` fails).
209
+ - Even ROOT holders cannot act as owner after renouncement, because `_requirePermissionFrom(address(0), 0, 0)` does not recognize ROOT as a valid bypass when the account is `address(0)`.
210
+
211
+ ---
212
+
213
+ ## Journey 6: Implicit Renouncement via Project NFT Burn
214
+
215
+ **Actor:** None (system behavior).
216
+ **Goal:** Understand what happens when the project NFT underlying a project-owned contract ceases to exist.
217
+
218
+ ### Precondition
219
+
220
+ The contract is project-owned (`jbOwner.projectId != 0`). The project NFT is burned or otherwise invalidated (note: JBProjects V6 has no burn function, so this is a defensive scenario).
221
+
222
+ ### Steps
223
+
224
+ 1. **`PROJECTS.ownerOf(projectId)` starts reverting**
225
+
226
+ - The ERC-721 `ownerOf` function reverts for burned tokens
227
+
228
+ 2. **`owner()` catches the revert and returns `address(0)`**
229
+
230
+ - The try-catch in `owner()` returns `address(0)` when `ownerOf` reverts
231
+
232
+ 3. **`_checkOwner()` catches the revert and resolves owner to `address(0)`**
233
+
234
+ - `_requirePermissionFrom(address(0), projectId, permissionId)` is called
235
+ - No `msg.sender` can equal `address(0)`, so the check always fails
236
+
237
+ ### Result
238
+
239
+ The contract is effectively renounced without anyone calling `renounceOwnership()`. All `onlyOwner` functions permanently revert. The `jbOwner` struct still contains the old `projectId`, but it has no practical effect.
240
+
241
+ ### What to verify
242
+
243
+ - There is no way to "revive" ownership after the NFT is burned. Even re-minting an NFT with the same ID (if possible) would restore ownership.
244
+ - The `jbOwner` struct is NOT cleared in this scenario -- it still shows the old `projectId`. Only the resolved owner is `address(0)`.
245
+ - This behavior is consistent between `owner()` and `_checkOwner()` (both use the same try-catch pattern).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/ownable-v6",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,8 +10,8 @@
10
10
  "node": ">=20.0.0"
11
11
  },
12
12
  "dependencies": {
13
- "@bananapus/core-v6": "^0.0.15",
14
- "@bananapus/permission-ids-v6": "^0.0.7",
13
+ "@bananapus/core-v6": "^0.0.17",
14
+ "@bananapus/permission-ids-v6": "^0.0.10",
15
15
  "@openzeppelin/contracts": "^5.6.1"
16
16
  },
17
17
  "scripts": {
package/src/JBOwnable.sol CHANGED
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Juicebox variation on OpenZeppelin Ownable
3
- pragma solidity ^0.8.26;
3
+ pragma solidity 0.8.26;
4
4
 
5
5
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
6
6
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
@@ -58,6 +58,9 @@ contract JBOwnable is JBOwnableOverrides {
58
58
  /// @notice Either `newOwner` or `newProjectId` is non-zero or both are zero. But they can never both be non-zero.
59
59
  /// @dev This function exists because some contracts need to deploy contracts for a project before the project's NFT
60
60
  /// has been minted, so the transfer event resolves the project's current owner at emission time.
61
+ /// @dev Unlike `_transferOwnership` (which uses try-catch to resolve the *old* owner in case its project NFT was
62
+ /// burned), this function intentionally lets `PROJECTS.ownerOf(newProjectId)` revert if the new project doesn't
63
+ /// exist. A revert here is desirable — it prevents transferring ownership to a non-existent project.
61
64
  function _emitTransferEvent(
62
65
  address previousOwner,
63
66
  address newOwner,
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Juicebox variation on OpenZeppelin Ownable
3
- pragma solidity ^0.8.26;
3
+ pragma solidity 0.8.26;
4
4
 
5
5
  import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
6
6
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
@@ -75,7 +75,11 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
75
75
  revert JBOwnableOverrides_InvalidNewOwner();
76
76
  }
77
77
 
78
- _transferOwnership(initialOwner, initialProjectIdOwner);
78
+ // No explicit project existence check here — if `initialProjectIdOwner` refers to an unminted project,
79
+ // `owner()` will resolve via `PROJECTS.ownerOf()`, which reverts for non-existent tokens. The try-catch
80
+ // in `owner()` treats this as renounced (returns address(0)), effectively locking the contract until
81
+ // the project is minted. This is acceptable because deployers control the constructor arguments.
82
+ _transferOwnership({newOwner: initialOwner, projectId: initialProjectIdOwner});
79
83
  }
80
84
 
81
85
  //*********************************************************************//
@@ -140,7 +144,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
140
144
  /// @dev This can only be called by the current owner.
141
145
  function renounceOwnership() public virtual override {
142
146
  _checkOwner();
143
- _transferOwnership(address(0), 0);
147
+ _transferOwnership({newOwner: address(0), projectId: 0});
144
148
  }
145
149
 
146
150
  /// @notice Sets the permission ID the owner can use to give other addresses owner access.
@@ -162,7 +166,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
162
166
  revert JBOwnableOverrides_InvalidNewOwner();
163
167
  }
164
168
 
165
- _transferOwnership(newOwner, 0);
169
+ _transferOwnership({newOwner: newOwner, projectId: 0});
166
170
  }
167
171
 
168
172
  /// @notice Transfer ownership of this contract to a new Juicebox project.
@@ -181,7 +185,8 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
181
185
  revert JBOwnableOverrides_ProjectDoesNotExist();
182
186
  }
183
187
 
184
- _transferOwnership(address(0), uint88(projectId));
188
+ // forge-lint: disable-next-line(unsafe-typecast)
189
+ _transferOwnership({newOwner: address(0), projectId: uint88(projectId)});
185
190
  }
186
191
 
187
192
  //*********************************************************************//
@@ -207,7 +212,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
207
212
  /// @notice Helper to allow for drop-in replacement of OpenZeppelin `Ownable`.
208
213
  /// @param newOwner The address that should receive ownership of this contract.
209
214
  function _transferOwnership(address newOwner) internal virtual {
210
- _transferOwnership(newOwner, 0);
215
+ _transferOwnership({newOwner: newOwner, projectId: 0});
211
216
  }
212
217
 
213
218
  /// @notice Transfers this contract's ownership to an address (`newOwner`) OR a Juicebox project (`projectId`).
@@ -238,6 +243,6 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
238
243
  // This is to prevent permissions clashes for the new user/owner.
239
244
  jbOwner = JBOwner({owner: newOwner, projectId: projectId, permissionId: 0});
240
245
  // Emit a transfer event with the new owner's address.
241
- _emitTransferEvent(oldOwner, newOwner, projectId);
246
+ _emitTransferEvent({previousOwner: oldOwner, newOwner: newOwner, newProjectId: projectId});
242
247
  }
243
248
  }
@@ -7,6 +7,7 @@ pragma solidity ^0.8.0;
7
7
  /// `owner` address has owner access.
8
8
  /// @custom:member permissionId The permission ID which corresponds to owner access. See `JBPermissions` in `nana-core`
9
9
  /// and `nana-permission-ids`.
10
+ // forge-lint: disable-next-line(pascal-case-struct)
10
11
  struct JBOwner {
11
12
  address owner;
12
13
  uint88 projectId;
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: UNLICENSED
2
2
  pragma solidity ^0.8.26;
3
3
 
4
- import "forge-std/Test.sol";
4
+ import {Test} from "forge-std/Test.sol";
5
5
  import {MockOwnable} from "./mocks/MockOwnable.sol";
6
6
  import {JBOwnableOverrides} from "../src/JBOwnableOverrides.sol";
7
7
 
@@ -13,8 +13,8 @@ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsDat
13
13
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
14
14
 
15
15
  contract OwnableTest is Test {
16
- IJBProjects PROJECTS;
17
- IJBPermissions PERMISSIONS;
16
+ IJBProjects projects;
17
+ IJBPermissions permissions;
18
18
 
19
19
  modifier isNotContract(address a) {
20
20
  uint256 size;
@@ -27,9 +27,9 @@ contract OwnableTest is Test {
27
27
 
28
28
  function setUp() public {
29
29
  // Deploy the permissions contract.
30
- PERMISSIONS = new JBPermissions(address(0));
30
+ permissions = new JBPermissions(address(0));
31
31
  // Deploy the projects contract.
32
- PROJECTS = new JBProjects(address(123), address(0), address(0));
32
+ projects = new JBProjects(address(123), address(0), address(0));
33
33
  }
34
34
 
35
35
  function testDeployerDoesNotBecomeOwner(address deployer, address owner) public isNotContract(owner) {
@@ -37,7 +37,7 @@ contract OwnableTest is Test {
37
37
  vm.assume(owner != address(0));
38
38
 
39
39
  vm.prank(deployer);
40
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, owner, uint88(0));
40
+ MockOwnable ownable = new MockOwnable(projects, permissions, owner, uint88(0));
41
41
 
42
42
  assertEq(owner, ownable.owner(), "Deployer did not become the owner.");
43
43
  }
@@ -56,17 +56,18 @@ contract OwnableTest is Test {
56
56
  vm.assume(newProjectOwner != address(0));
57
57
 
58
58
  // Create a project for the owner.
59
- uint256 projectId = PROJECTS.createFor(projectOwner);
59
+ uint256 projectId = projects.createFor(projectOwner);
60
60
 
61
61
  // Create the `Ownable` contract.
62
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
62
+ // forge-lint: disable-next-line(unsafe-typecast)
63
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
63
64
 
64
65
  // Make sure the deployer owns it.
65
66
  assertEq(projectOwner, ownable.owner(), "Deployer is not the owner.");
66
67
 
67
68
  // Transfer the project's ownership.
68
69
  vm.prank(projectOwner);
69
- PROJECTS.transferFrom(projectOwner, newProjectOwner, projectId);
70
+ projects.transferFrom(projectOwner, newProjectOwner, projectId);
70
71
 
71
72
  // Make sure the `Ownable` contract has also been transferred to the new project owner.
72
73
  assertEq(newProjectOwner, ownable.owner(), "Ownable did not follow the Project owner.");
@@ -86,10 +87,11 @@ contract OwnableTest is Test {
86
87
  vm.assume(projectOwner != address(0));
87
88
 
88
89
  // Create a project for the owner.
89
- uint256 _projectId = PROJECTS.createFor(projectOwner);
90
+ uint256 _projectId = projects.createFor(projectOwner);
90
91
 
91
92
  // Create the `Ownable` contract.
92
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(_projectId));
93
+ // forge-lint: disable-next-line(unsafe-typecast)
94
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
93
95
 
94
96
  // Make sure the project owner owns it.
95
97
  assertEq(projectOwner, ownable.owner(), "Deployer is not the owner.");
@@ -100,7 +102,7 @@ contract OwnableTest is Test {
100
102
  // Make sure it was transferred to the new owner.
101
103
  assertEq(newOwnableOwner, ownable.owner());
102
104
  // Sanity check to make sure it only the `Ownable` changed, and that the project did not.
103
- assertEq(PROJECTS.ownerOf(_projectId), projectOwner);
105
+ assertEq(projects.ownerOf(_projectId), projectOwner);
104
106
  }
105
107
 
106
108
  function testCantTransferToProjectZero(address owner) public {
@@ -108,7 +110,7 @@ contract OwnableTest is Test {
108
110
  vm.startPrank(owner);
109
111
 
110
112
  // Create the `Ownable` contract.
111
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, owner, 0);
113
+ MockOwnable ownable = new MockOwnable(projects, permissions, owner, 0);
112
114
 
113
115
  vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
114
116
 
@@ -122,7 +124,7 @@ contract OwnableTest is Test {
122
124
  vm.startPrank(owner);
123
125
 
124
126
  // Create the `Ownable` contract.
125
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, owner, uint88(0));
127
+ MockOwnable ownable = new MockOwnable(projects, permissions, owner, uint88(0));
126
128
 
127
129
  vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
128
130
 
@@ -145,18 +147,19 @@ contract OwnableTest is Test {
145
147
  vm.assume(newProjectOwner != address(0));
146
148
 
147
149
  // Create a project for the owner.
148
- uint256 _projectId = PROJECTS.createFor(projectOwner);
150
+ uint256 _projectId = projects.createFor(projectOwner);
149
151
 
150
152
  // Create the `Ownable` contract.
151
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(_projectId));
153
+ // forge-lint: disable-next-line(unsafe-typecast)
154
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
152
155
 
153
156
  // Make sure the project owner owns it.
154
157
  assertEq(projectOwner, ownable.owner(), "Deployer is not the owner.");
155
158
 
156
159
  // Transfer the project ownership.
157
160
  vm.prank(projectOwner);
158
- PROJECTS.transferFrom(projectOwner, newProjectOwner, _projectId);
159
- assertEq(PROJECTS.ownerOf(_projectId), newProjectOwner);
161
+ projects.transferFrom(projectOwner, newProjectOwner, _projectId);
162
+ assertEq(projects.ownerOf(_projectId), newProjectOwner);
160
163
 
161
164
  // Make sure the `Ownable` contract has also been transferred to the new project owner.
162
165
  assertEq(newProjectOwner, ownable.owner());
@@ -167,7 +170,7 @@ contract OwnableTest is Test {
167
170
  vm.assume(deployer != owner);
168
171
 
169
172
  // Create the `Ownable` contract.
170
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, owner, uint88(0));
173
+ MockOwnable ownable = new MockOwnable(projects, permissions, owner, uint88(0));
171
174
 
172
175
  // Transfer ownership to the project owner.
173
176
  vm.prank(owner);
@@ -185,11 +188,12 @@ contract OwnableTest is Test {
185
188
  vm.assume(projectOwner != address(0));
186
189
 
187
190
  // Create a project for the owner.
188
- uint256 _projectId = PROJECTS.createFor(projectOwner);
191
+ uint256 _projectId = projects.createFor(projectOwner);
189
192
 
190
193
  // Create the `Ownable` contract.
191
194
  vm.prank(deployer);
192
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(_projectId));
195
+ // forge-lint: disable-next-line(unsafe-typecast)
196
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
193
197
 
194
198
  // Renounce the ownership.
195
199
  vm.prank(projectOwner);
@@ -218,10 +222,11 @@ contract OwnableTest is Test {
218
222
  }
219
223
 
220
224
  // Create a project for the owner.
221
- uint256 _projectId = PROJECTS.createFor(projectOwner);
225
+ uint256 _projectId = projects.createFor(projectOwner);
222
226
 
223
227
  // Create the `Ownable` contract.
224
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(_projectId));
228
+ // forge-lint: disable-next-line(unsafe-typecast)
229
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
225
230
 
226
231
  // Set the required permission.
227
232
  vm.prank(projectOwner);
@@ -254,8 +259,9 @@ contract OwnableTest is Test {
254
259
 
255
260
  // The owner gives permission to the caller.
256
261
  vm.prank(projectOwner);
257
- PERMISSIONS.setPermissionsFor(
262
+ permissions.setPermissionsFor(
258
263
  projectOwner,
264
+ // forge-lint: disable-next-line(unsafe-typecast)
259
265
  JBPermissionsData({operator: callerAddress, projectId: uint56(_projectId), permissionIds: _permissionIds})
260
266
  );
261
267
 
@@ -296,10 +302,11 @@ contract OwnableTest is Test {
296
302
  }
297
303
 
298
304
  // Create a project for the owner.
299
- uint256 _projectId = PROJECTS.createFor(projectOwner);
305
+ uint256 _projectId = projects.createFor(projectOwner);
300
306
 
301
307
  // Create the `Ownable` contract.
302
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(_projectId));
308
+ // forge-lint: disable-next-line(unsafe-typecast)
309
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
303
310
 
304
311
  // Set the permission that is required.
305
312
  ownable.setPermission(requiredPermissionId);
@@ -331,8 +338,9 @@ contract OwnableTest is Test {
331
338
 
332
339
  // The owner gives permission to the caller.
333
340
  vm.prank(projectOwner);
334
- PERMISSIONS.setPermissionsFor(
341
+ permissions.setPermissionsFor(
335
342
  projectOwner,
343
+ // forge-lint: disable-next-line(unsafe-typecast)
336
344
  JBPermissionsData({operator: callerAddress, projectId: uint56(_projectId), permissionIds: _permissionIds})
337
345
  );
338
346
 
@@ -356,19 +364,20 @@ contract OwnableTest is Test {
356
364
  vm.assume(owner != address(0) && projectOwner != address(0));
357
365
 
358
366
  // Create a project for the owner.
359
- uint256 _projectId = PROJECTS.createFor(projectOwner);
367
+ uint256 _projectId = projects.createFor(projectOwner);
360
368
 
361
369
  // Should revert because we set both a owner and a projectOwner
362
370
  vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
363
371
 
364
372
  // Create the `Ownable` contract.
365
- new MockOwnable(PROJECTS, PERMISSIONS, address(owner), uint88(_projectId));
373
+ // forge-lint: disable-next-line(unsafe-typecast)
374
+ new MockOwnable(projects, permissions, address(owner), uint88(_projectId));
366
375
  }
367
376
 
368
377
  function testCantInitializeAsRenounced() public {
369
378
  // Should revert because we set both a owner and a projectOwner
370
379
  vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
371
380
  // Create the `Ownable` contract.
372
- new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(0));
381
+ new MockOwnable(projects, permissions, address(0), uint88(0));
373
382
  }
374
383
  }