@bananapus/ownable-v6 0.0.8 → 0.0.9

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/STYLE_GUIDE.md CHANGED
@@ -197,7 +197,7 @@ interface IJBExample is IJBBase {
197
197
  | Public/external function | `camelCase` | `cashOutTokensOf` |
198
198
  | Internal/private function | `_camelCase` | `_processFee` |
199
199
  | Internal storage | `_camelCase` | `_accountingContextForTokenOf` |
200
- | Function parameter | `camelCase` | `projectId`, `cashOutCount` |
200
+ | Function parameter | `camelCase` (no underscores) | `projectId`, `cashOutCount` |
201
201
 
202
202
  ## NatSpec
203
203
 
@@ -253,9 +253,12 @@ uint256 public constant MAX_RESERVED_PERCENT = 10_000;
253
253
 
254
254
  ## Function Calls
255
255
 
256
- Use named parameters for readability when calling functions with 3+ arguments:
256
+ Use named arguments for all function calls with 2 or more arguments — in both `src/` and `script/`:
257
257
 
258
258
  ```solidity
259
+ // Good — named arguments
260
+ token.mint({account: beneficiary, amount: count});
261
+ _transferOwnership({newOwner: address(0), projectId: 0});
259
262
  PERMISSIONS.hasPermission({
260
263
  operator: sender,
261
264
  account: account,
@@ -264,8 +267,18 @@ PERMISSIONS.hasPermission({
264
267
  includeRoot: true,
265
268
  includeWildcardProjectId: true
266
269
  });
270
+
271
+ // Bad — positional arguments with 2+ args
272
+ token.mint(beneficiary, count);
273
+ _transferOwnership(address(0), 0);
267
274
  ```
268
275
 
276
+ Single-argument calls use positional style: `_burn(amount)`.
277
+
278
+ This also applies to constructor calls, struct literals, and inherited/library calls (e.g., OZ `_mint`, `_safeMint`, `safeTransfer`, `allowance`, `Clones.cloneDeterministic`).
279
+
280
+ Named argument keys must use **camelCase** — never underscores. If a function's parameter names use underscores, rename them to camelCase first.
281
+
269
282
  ## Multiline Signatures
270
283
 
271
284
  ```solidity
@@ -553,6 +566,7 @@ CI checks formatting via `forge fmt --check`.
553
566
 
554
567
  CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
555
568
 
569
+
556
570
  ## Repo-Specific Deviations
557
571
 
558
572
  None. This repo follows the standard configuration exactly.
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.9",
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.16",
14
+ "@bananapus/permission-ids-v6": "^0.0.9",
15
15
  "@openzeppelin/contracts": "^5.6.1"
16
16
  },
17
17
  "scripts": {
@@ -75,7 +75,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
75
75
  revert JBOwnableOverrides_InvalidNewOwner();
76
76
  }
77
77
 
78
- _transferOwnership(initialOwner, initialProjectIdOwner);
78
+ _transferOwnership({newOwner: initialOwner, projectId: initialProjectIdOwner});
79
79
  }
80
80
 
81
81
  //*********************************************************************//
@@ -140,7 +140,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
140
140
  /// @dev This can only be called by the current owner.
141
141
  function renounceOwnership() public virtual override {
142
142
  _checkOwner();
143
- _transferOwnership(address(0), 0);
143
+ _transferOwnership({newOwner: address(0), projectId: 0});
144
144
  }
145
145
 
146
146
  /// @notice Sets the permission ID the owner can use to give other addresses owner access.
@@ -162,7 +162,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
162
162
  revert JBOwnableOverrides_InvalidNewOwner();
163
163
  }
164
164
 
165
- _transferOwnership(newOwner, 0);
165
+ _transferOwnership({newOwner: newOwner, projectId: 0});
166
166
  }
167
167
 
168
168
  /// @notice Transfer ownership of this contract to a new Juicebox project.
@@ -181,7 +181,8 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
181
181
  revert JBOwnableOverrides_ProjectDoesNotExist();
182
182
  }
183
183
 
184
- _transferOwnership(address(0), uint88(projectId));
184
+ // forge-lint: disable-next-line(unsafe-typecast)
185
+ _transferOwnership({newOwner: address(0), projectId: uint88(projectId)});
185
186
  }
186
187
 
187
188
  //*********************************************************************//
@@ -207,7 +208,7 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
207
208
  /// @notice Helper to allow for drop-in replacement of OpenZeppelin `Ownable`.
208
209
  /// @param newOwner The address that should receive ownership of this contract.
209
210
  function _transferOwnership(address newOwner) internal virtual {
210
- _transferOwnership(newOwner, 0);
211
+ _transferOwnership({newOwner: newOwner, projectId: 0});
211
212
  }
212
213
 
213
214
  /// @notice Transfers this contract's ownership to an address (`newOwner`) OR a Juicebox project (`projectId`).
@@ -238,6 +239,6 @@ abstract contract JBOwnableOverrides is Context, JBPermissioned, IJBOwnable {
238
239
  // This is to prevent permissions clashes for the new user/owner.
239
240
  jbOwner = JBOwner({owner: newOwner, projectId: projectId, permissionId: 0});
240
241
  // Emit a transfer event with the new owner's address.
241
- _emitTransferEvent(oldOwner, newOwner, projectId);
242
+ _emitTransferEvent({previousOwner: oldOwner, newOwner: newOwner, newProjectId: projectId});
242
243
  }
243
244
  }
@@ -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
  }
@@ -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
 
@@ -16,8 +16,8 @@ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsDat
16
16
  /// @notice Adversarial security tests for JBOwnable covering edge cases
17
17
  /// around dual ownership, permission semantics, and renounced contracts.
18
18
  contract OwnableAttacks is Test {
19
- IJBProjects PROJECTS;
20
- IJBPermissions PERMISSIONS;
19
+ IJBProjects projects;
20
+ IJBPermissions permissions;
21
21
 
22
22
  address alice = makeAddr("alice");
23
23
  address bob = makeAddr("bob");
@@ -33,25 +33,26 @@ contract OwnableAttacks is Test {
33
33
  }
34
34
 
35
35
  function setUp() public {
36
- PERMISSIONS = new JBPermissions(address(0));
37
- PROJECTS = new JBProjects(address(123), address(0), address(0));
36
+ permissions = new JBPermissions(address(0));
37
+ projects = new JBProjects(address(123), address(0), address(0));
38
38
  }
39
39
 
40
40
  // =========================================================================
41
41
  // Test 1: Constructor rejects both owner AND projectId set
42
42
  // =========================================================================
43
43
  function test_bothOwnerAndProjectId_constructorReverts() public {
44
- uint256 projectId = PROJECTS.createFor(alice);
44
+ uint256 projectId = projects.createFor(alice);
45
45
 
46
46
  vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
47
- new MockOwnable(PROJECTS, PERMISSIONS, bob, uint88(projectId));
47
+ // forge-lint: disable-next-line(unsafe-typecast)
48
+ new MockOwnable(projects, permissions, bob, uint88(projectId));
48
49
  }
49
50
 
50
51
  // =========================================================================
51
52
  // Test 2: Renounced contract — protectedMethod always reverts
52
53
  // =========================================================================
53
54
  function test_renounced_protectedMethodAlwaysReverts() public {
54
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
55
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
55
56
 
56
57
  // Owner can call.
57
58
  vm.prank(alice);
@@ -82,8 +83,9 @@ contract OwnableAttacks is Test {
82
83
  /// @notice After any ownership transfer, permissionId should reset to 0.
83
84
  /// This prevents stale permission delegation.
84
85
  function test_permissionIdResetOnTransfer() public {
85
- uint256 projectId = PROJECTS.createFor(alice);
86
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
86
+ uint256 projectId = projects.createFor(alice);
87
+ // forge-lint: disable-next-line(unsafe-typecast)
88
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
87
89
 
88
90
  // Set permission ID.
89
91
  vm.prank(alice);
@@ -105,8 +107,9 @@ contract OwnableAttacks is Test {
105
107
  // =========================================================================
106
108
  /// @notice After transferring project NFT, old owner should lose access.
107
109
  function test_staleOwner_afterNFTTransfer() public {
108
- uint256 projectId = PROJECTS.createFor(alice);
109
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
110
+ uint256 projectId = projects.createFor(alice);
111
+ // forge-lint: disable-next-line(unsafe-typecast)
112
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
110
113
 
111
114
  // Alice is current owner.
112
115
  assertEq(ownable.owner(), alice);
@@ -115,7 +118,7 @@ contract OwnableAttacks is Test {
115
118
 
116
119
  // Transfer project NFT to bob.
117
120
  vm.prank(alice);
118
- PROJECTS.transferFrom(alice, bob, projectId);
121
+ projects.transferFrom(alice, bob, projectId);
119
122
 
120
123
  // Alice should no longer be owner.
121
124
  assertEq(ownable.owner(), bob, "Bob should be new owner");
@@ -135,7 +138,7 @@ contract OwnableAttacks is Test {
135
138
  // =========================================================================
136
139
  /// @notice transferOwnershipToProject with projectId > type(uint88).max should revert.
137
140
  function test_transferOwnershipToProject_overflowReverts() public {
138
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
141
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
139
142
 
140
143
  // type(uint88).max + 1 = 309485009821345068724781056
141
144
  uint256 overflowId = uint256(type(uint88).max) + 1;
@@ -152,11 +155,12 @@ contract OwnableAttacks is Test {
152
155
  /// doesn't grant access to a different project's JBOwnable.
153
156
  function test_rootOnWrongProject_noAccess() public {
154
157
  // Create two projects.
155
- uint256 aliceProject = PROJECTS.createFor(alice);
156
- uint256 attackerProject = PROJECTS.createFor(attacker);
158
+ uint256 aliceProject = projects.createFor(alice);
159
+ uint256 attackerProject = projects.createFor(attacker);
157
160
 
158
161
  // Ownable is owned by alice's project.
159
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(aliceProject));
162
+ // forge-lint: disable-next-line(unsafe-typecast)
163
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(aliceProject));
160
164
 
161
165
  // Set permission ID so delegated access is possible.
162
166
  vm.prank(alice);
@@ -167,8 +171,9 @@ contract OwnableAttacks is Test {
167
171
  rootPerms[0] = 1; // ROOT
168
172
 
169
173
  vm.prank(attacker);
170
- PERMISSIONS.setPermissionsFor(
174
+ permissions.setPermissionsFor(
171
175
  attacker,
176
+ // forge-lint: disable-next-line(unsafe-typecast)
172
177
  JBPermissionsData({operator: attacker, projectId: uint56(attackerProject), permissionIds: rootPerms})
173
178
  );
174
179
 
@@ -1,14 +1,12 @@
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
- import {JBOwner} from "../src/structs/JBOwner.sol";
8
7
  import {IJBOwnable} from "../src/interfaces/IJBOwnable.sol";
9
8
 
10
9
  import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
11
- import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
12
10
  import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
13
11
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
14
12
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
@@ -18,8 +16,8 @@ import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsDat
18
16
  /// @notice Edge case and gap tests for JBOwnable: multi-hop NFT transfers,
19
17
  /// project-to-project ownership, permissionId lifecycle, and nonexistent projects.
20
18
  contract OwnableEdgeCases is Test {
21
- IJBProjects PROJECTS;
22
- IJBPermissions PERMISSIONS;
19
+ IJBProjects projects;
20
+ IJBPermissions permissions;
23
21
 
24
22
  address alice = makeAddr("alice");
25
23
  address bob = makeAddr("bob");
@@ -36,32 +34,33 @@ contract OwnableEdgeCases is Test {
36
34
  }
37
35
 
38
36
  function setUp() public {
39
- PERMISSIONS = new JBPermissions(address(0));
40
- PROJECTS = new JBProjects(address(123), address(0), address(0));
37
+ permissions = new JBPermissions(address(0));
38
+ projects = new JBProjects(address(123), address(0), address(0));
41
39
  }
42
40
 
43
41
  // =========================================================================
44
42
  // Test 1: Multi-hop NFT transfer — ownership follows through A→B→C→D
45
43
  // =========================================================================
46
44
  function test_multiHopNFTTransfer_ownerFollows() public {
47
- uint256 projectId = PROJECTS.createFor(alice);
48
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
45
+ uint256 projectId = projects.createFor(alice);
46
+ // forge-lint: disable-next-line(unsafe-typecast)
47
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
49
48
 
50
49
  assertEq(ownable.owner(), alice);
51
50
 
52
51
  // Transfer NFT: alice → bob
53
52
  vm.prank(alice);
54
- PROJECTS.transferFrom(alice, bob, projectId);
53
+ projects.transferFrom(alice, bob, projectId);
55
54
  assertEq(ownable.owner(), bob, "Should follow to bob");
56
55
 
57
56
  // Transfer NFT: bob → charlie
58
57
  vm.prank(bob);
59
- PROJECTS.transferFrom(bob, charlie, projectId);
58
+ projects.transferFrom(bob, charlie, projectId);
60
59
  assertEq(ownable.owner(), charlie, "Should follow to charlie");
61
60
 
62
61
  // Transfer NFT: charlie → dave
63
62
  vm.prank(charlie);
64
- PROJECTS.transferFrom(charlie, dave, projectId);
63
+ projects.transferFrom(charlie, dave, projectId);
65
64
  assertEq(ownable.owner(), dave, "Should follow to dave");
66
65
 
67
66
  // dave can call protectedMethod, alice/bob/charlie cannot
@@ -85,10 +84,11 @@ contract OwnableEdgeCases is Test {
85
84
  // Test 2: Transfer project → different project
86
85
  // =========================================================================
87
86
  function test_transferProjectToProject() public {
88
- uint256 projectA = PROJECTS.createFor(alice);
89
- uint256 projectB = PROJECTS.createFor(bob);
87
+ uint256 projectA = projects.createFor(alice);
88
+ uint256 projectB = projects.createFor(bob);
90
89
 
91
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectA));
90
+ // forge-lint: disable-next-line(unsafe-typecast)
91
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectA));
92
92
  assertEq(ownable.owner(), alice);
93
93
 
94
94
  // Transfer ownership from project A to project B.
@@ -112,11 +112,11 @@ contract OwnableEdgeCases is Test {
112
112
  // Test 3: Full ownership cycle: address → project → address → project
113
113
  // =========================================================================
114
114
  function test_fullOwnershipCycle() public {
115
- uint256 projectA = PROJECTS.createFor(alice);
116
- uint256 projectB = PROJECTS.createFor(bob);
115
+ uint256 projectA = projects.createFor(alice);
116
+ uint256 projectB = projects.createFor(bob);
117
117
 
118
118
  // Start with address ownership.
119
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, charlie, 0);
119
+ MockOwnable ownable = new MockOwnable(projects, permissions, charlie, 0);
120
120
  assertEq(ownable.owner(), charlie);
121
121
 
122
122
  // charlie → project A (alice)
@@ -137,6 +137,7 @@ contract OwnableEdgeCases is Test {
137
137
  // Verify jbOwner struct is correct (projectId set, owner zeroed).
138
138
  (address storedOwner, uint88 storedProjectId, uint8 storedPermId) = ownable.jbOwner();
139
139
  assertEq(storedOwner, address(0), "owner field should be zero in project mode");
140
+ // forge-lint: disable-next-line(unsafe-typecast)
140
141
  assertEq(storedProjectId, uint88(projectB), "projectId should be projectB");
141
142
  assertEq(storedPermId, 0, "permissionId should be 0");
142
143
  }
@@ -145,9 +146,10 @@ contract OwnableEdgeCases is Test {
145
146
  // Test 4: permissionId lifecycle through multiple transfers
146
147
  // =========================================================================
147
148
  function test_permissionIdLifecycle() public {
148
- uint256 projectA = PROJECTS.createFor(alice);
149
+ uint256 projectA = projects.createFor(alice);
149
150
 
150
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectA));
151
+ // forge-lint: disable-next-line(unsafe-typecast)
152
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectA));
151
153
 
152
154
  // Set permissionId to 42.
153
155
  vm.prank(alice);
@@ -192,7 +194,7 @@ contract OwnableEdgeCases is Test {
192
194
  function test_nonOwnerCannotSetPermissionId(address nonOwner) public {
193
195
  vm.assume(nonOwner != alice && nonOwner != address(0));
194
196
 
195
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
197
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
196
198
 
197
199
  vm.prank(nonOwner);
198
200
  vm.expectRevert();
@@ -204,8 +206,8 @@ contract OwnableEdgeCases is Test {
204
206
  // =========================================================================
205
207
  function test_transferToNonexistentProject_reverts() public {
206
208
  // Create one project so count == 1.
207
- PROJECTS.createFor(alice);
208
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
209
+ projects.createFor(alice);
210
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
209
211
 
210
212
  // Project 2 doesn't exist.
211
213
  vm.prank(alice);
@@ -223,8 +225,9 @@ contract OwnableEdgeCases is Test {
223
225
  // transferred, old delegate loses access
224
226
  // =========================================================================
225
227
  function test_delegatedAccess_lostAfterNFTTransfer() public {
226
- uint256 projectId = PROJECTS.createFor(alice);
227
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
228
+ uint256 projectId = projects.createFor(alice);
229
+ // forge-lint: disable-next-line(unsafe-typecast)
230
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
228
231
 
229
232
  // Set permissionId so delegation is possible.
230
233
  vm.prank(alice);
@@ -234,8 +237,10 @@ contract OwnableEdgeCases is Test {
234
237
  uint8[] memory permIds = new uint8[](1);
235
238
  permIds[0] = 42;
236
239
  vm.prank(alice);
237
- PERMISSIONS.setPermissionsFor(
238
- alice, JBPermissionsData({operator: charlie, projectId: uint56(projectId), permissionIds: permIds})
240
+ permissions.setPermissionsFor(
241
+ // forge-lint: disable-next-line(unsafe-typecast)
242
+ alice,
243
+ JBPermissionsData({operator: charlie, projectId: uint56(projectId), permissionIds: permIds})
239
244
  );
240
245
 
241
246
  // Charlie can call protectedMethod (delegated via permissions).
@@ -244,7 +249,7 @@ contract OwnableEdgeCases is Test {
244
249
 
245
250
  // Transfer NFT to bob.
246
251
  vm.prank(alice);
247
- PROJECTS.transferFrom(alice, bob, projectId);
252
+ projects.transferFrom(alice, bob, projectId);
248
253
 
249
254
  // Charlie's delegation was from alice. Now owner is bob.
250
255
  // Charlie should lose access because _checkOwner resolves to bob,
@@ -262,7 +267,7 @@ contract OwnableEdgeCases is Test {
262
267
  // Test 8: OwnershipTransferred event emitted correctly
263
268
  // =========================================================================
264
269
  function test_ownershipTransferredEvent() public {
265
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
270
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
266
271
 
267
272
  // Transfer to bob — expect event.
268
273
  vm.expectEmit(true, true, false, true);
@@ -276,7 +281,7 @@ contract OwnableEdgeCases is Test {
276
281
  // Test 9: PermissionIdChanged event emitted correctly
277
282
  // =========================================================================
278
283
  function test_permissionIdChangedEvent() public {
279
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
284
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
280
285
 
281
286
  vm.expectEmit(true, true, false, true);
282
287
  emit IJBOwnable.PermissionIdChanged(42, alice);
@@ -291,8 +296,8 @@ contract OwnableEdgeCases is Test {
291
296
  function testFuzz_transferToProject(address projectOwner) public isNotContract(projectOwner) {
292
297
  vm.assume(projectOwner != address(0));
293
298
 
294
- uint256 projectId = PROJECTS.createFor(projectOwner);
295
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
299
+ uint256 projectId = projects.createFor(projectOwner);
300
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
296
301
 
297
302
  vm.prank(alice);
298
303
  ownable.transferOwnershipToProject(projectId);
@@ -302,6 +307,7 @@ contract OwnableEdgeCases is Test {
302
307
  // Verify jbOwner struct.
303
308
  (address storedOwner, uint88 storedProjectId,) = ownable.jbOwner();
304
309
  assertEq(storedOwner, address(0), "stored owner should be zero");
310
+ // forge-lint: disable-next-line(unsafe-typecast)
305
311
  assertEq(storedProjectId, uint88(projectId), "stored projectId should match");
306
312
  }
307
313
 
@@ -311,8 +317,8 @@ contract OwnableEdgeCases is Test {
311
317
  /// @notice After renouncing, no one can call transferOwnership, transferOwnershipToProject,
312
318
  /// setPermissionId, or renounceOwnership again.
313
319
  function test_renouncedContract_cannotReclaim() public {
314
- uint256 projectId = PROJECTS.createFor(alice);
315
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
320
+ uint256 projectId = projects.createFor(alice);
321
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
316
322
 
317
323
  vm.prank(alice);
318
324
  ownable.renounceOwnership();
@@ -349,7 +355,7 @@ contract OwnableEdgeCases is Test {
349
355
  /// NOT ERC2771Context. This test documents that a trusted forwarder
350
356
  /// appending a sender address to calldata does NOT affect _checkOwner.
351
357
  function test_noERC2771_trustedForwarderHasNoEffect() public {
352
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
358
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
353
359
 
354
360
  // Simulate what a trusted forwarder would do: call with alice's address
355
361
  // appended to calldata. Since JBOwnable uses plain Context, this has no effect.
@@ -4,15 +4,6 @@ pragma solidity ^0.8.26;
4
4
  import {Test} from "forge-std/Test.sol";
5
5
  import {OwnableHandler} from "./handlers/OwnableHandler.sol";
6
6
 
7
- import {MockOwnable} from "./mocks/MockOwnable.sol";
8
- import {JBOwnableOverrides} from "../src/JBOwnableOverrides.sol";
9
- import {JBOwner} from "../src/structs/JBOwner.sol";
10
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
11
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
12
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
13
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
14
- import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
15
-
16
7
  contract OwnableInvariantTests is Test {
17
8
  OwnableHandler handler;
18
9
 
@@ -5,12 +5,9 @@ pragma solidity ^0.8.26;
5
5
  import {CommonBase} from "forge-std/Base.sol";
6
6
  import {StdCheats} from "forge-std/StdCheats.sol";
7
7
  import {StdUtils} from "forge-std/StdUtils.sol";
8
- import {console} from "forge-std/console.sol";
9
-
10
- import {MockOwnable, JBOwnableOverrides} from "../mocks/MockOwnable.sol";
8
+ import {MockOwnable} from "../mocks/MockOwnable.sol";
11
9
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
12
10
  import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
13
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
14
11
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
15
12
  import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
16
13
 
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: UNLICENSED
2
2
  pragma solidity ^0.8.26;
3
3
 
4
- import {JBOwnable, JBOwnableOverrides} from "../../src/JBOwnable.sol";
4
+ import {JBOwnable} from "../../src/JBOwnable.sol";
5
5
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
6
6
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
7
7
 
@@ -3,7 +3,6 @@ pragma solidity ^0.8.26;
3
3
 
4
4
  import {Test} from "forge-std/Test.sol";
5
5
  import {MockOwnable} from "../mocks/MockOwnable.sol";
6
- import {JBOwnableOverrides} from "../../src/JBOwnableOverrides.sol";
7
6
 
8
7
  import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
9
8
  import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
@@ -11,34 +10,35 @@ import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.s
11
10
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
12
11
  import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
13
12
 
14
- /// @title L65_BurnLockProtection
13
+ /// @title BurnLockProtection
15
14
  /// @notice Verifies that if a project NFT is burned/invalidated,
16
15
  /// owner() returns address(0) and _checkOwner() reverts gracefully instead of
17
16
  /// permanently locking the contract with an unrecoverable revert.
18
- contract L65_BurnLockProtection is Test {
19
- IJBProjects PROJECTS;
20
- IJBPermissions PERMISSIONS;
17
+ contract BurnLockProtection is Test {
18
+ IJBProjects projects;
19
+ IJBPermissions permissions;
21
20
 
22
21
  address alice = makeAddr("alice");
23
22
  address bob = makeAddr("bob");
24
23
 
25
24
  function setUp() public {
26
- PERMISSIONS = new JBPermissions(address(0));
27
- PROJECTS = new JBProjects(address(123), address(0), address(0));
25
+ permissions = new JBPermissions(address(0));
26
+ projects = new JBProjects(address(123), address(0), address(0));
28
27
  }
29
28
 
30
29
  /// @notice When a project NFT is burned (simulated via mockCallRevert), owner() should
31
30
  /// return address(0) instead of reverting — contract degrades to "renounced" state.
32
31
  function test_burnedProjectNFT_ownerReturnsZero() public {
33
- uint256 projectId = PROJECTS.createFor(alice);
34
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
32
+ uint256 projectId = projects.createFor(alice);
33
+ // forge-lint: disable-next-line(unsafe-typecast)
34
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
35
35
 
36
36
  // Verify normal operation first.
37
37
  assertEq(ownable.owner(), alice, "Owner should be alice before burn");
38
38
 
39
39
  // Simulate project NFT burn by making ownerOf revert for this projectId.
40
40
  vm.mockCallRevert(
41
- address(PROJECTS), abi.encodeWithSelector(IERC721.ownerOf.selector, projectId), "ERC721: invalid token ID"
41
+ address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, projectId), "ERC721: invalid token ID"
42
42
  );
43
43
 
44
44
  // After burn, owner() should return address(0) — NOT revert.
@@ -50,8 +50,9 @@ contract L65_BurnLockProtection is Test {
50
50
  /// Unauthorized error (not an unrecoverable ownerOf revert), making the contract
51
51
  /// behave as if ownership was renounced.
52
52
  function test_burnedProjectNFT_checkOwnerRevertsGracefully() public {
53
- uint256 projectId = PROJECTS.createFor(alice);
54
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
53
+ uint256 projectId = projects.createFor(alice);
54
+ // forge-lint: disable-next-line(unsafe-typecast)
55
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
55
56
 
56
57
  // Alice can call the protected method before burn.
57
58
  vm.prank(alice);
@@ -59,7 +60,7 @@ contract L65_BurnLockProtection is Test {
59
60
 
60
61
  // Simulate project NFT burn.
61
62
  vm.mockCallRevert(
62
- address(PROJECTS), abi.encodeWithSelector(IERC721.ownerOf.selector, projectId), "ERC721: invalid token ID"
63
+ address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, projectId), "ERC721: invalid token ID"
63
64
  );
64
65
 
65
66
  // After burn, nobody can call protected methods — but the revert is graceful
@@ -75,7 +76,7 @@ contract L65_BurnLockProtection is Test {
75
76
 
76
77
  /// @notice Address-based ownership is unaffected by the try-catch change.
77
78
  function test_addressBasedOwnership_unaffectedByTryCatch() public {
78
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, alice, 0);
79
+ MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
79
80
 
80
81
  assertEq(ownable.owner(), alice, "Owner should be alice");
81
82
 
@@ -93,14 +94,15 @@ contract L65_BurnLockProtection is Test {
93
94
 
94
95
  /// @notice Normal project-based ownership still works correctly after the fix.
95
96
  function test_normalProjectOwnership_stillWorks() public {
96
- uint256 projectId = PROJECTS.createFor(alice);
97
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
97
+ uint256 projectId = projects.createFor(alice);
98
+ // forge-lint: disable-next-line(unsafe-typecast)
99
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
98
100
 
99
101
  assertEq(ownable.owner(), alice);
100
102
 
101
103
  // Transfer project NFT.
102
104
  vm.prank(alice);
103
- PROJECTS.transferFrom(alice, bob, projectId);
105
+ projects.transferFrom(alice, bob, projectId);
104
106
 
105
107
  assertEq(ownable.owner(), bob, "Owner should follow project NFT transfer");
106
108
 
@@ -10,19 +10,19 @@ import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
10
10
  import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
11
11
  import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
12
12
 
13
- /// @title L66_ZeroAddressValidation
14
- /// @notice Verifies that deploying with a zero-address PROJECTS
13
+ /// @title ZeroAddressValidation
14
+ /// @notice Verifies that deploying with a zero-address projects
15
15
  /// contract and a non-zero projectId reverts at construction time, preventing
16
16
  /// permanently broken project-based ownership.
17
- contract L66_ZeroAddressValidation is Test {
18
- IJBProjects PROJECTS;
19
- IJBPermissions PERMISSIONS;
17
+ contract ZeroAddressValidation is Test {
18
+ IJBProjects projects;
19
+ IJBPermissions permissions;
20
20
 
21
21
  address alice = makeAddr("alice");
22
22
 
23
23
  function setUp() public {
24
- PERMISSIONS = new JBPermissions(address(0));
25
- PROJECTS = new JBProjects(address(123), address(0), address(0));
24
+ permissions = new JBPermissions(address(0));
25
+ projects = new JBProjects(address(123), address(0), address(0));
26
26
  }
27
27
 
28
28
  /// @notice Deploying with projects=address(0) and non-zero projectId must revert.
@@ -30,7 +30,7 @@ contract L66_ZeroAddressValidation is Test {
30
30
  vm.expectRevert(
31
31
  abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_ZeroAddressProjectsWithProjectOwner.selector)
32
32
  );
33
- new MockOwnable(IJBProjects(address(0)), PERMISSIONS, address(0), uint88(1));
33
+ new MockOwnable(IJBProjects(address(0)), permissions, address(0), uint88(1));
34
34
  }
35
35
 
36
36
  /// @notice Fuzz: any non-zero projectId with projects=address(0) must revert.
@@ -40,27 +40,28 @@ contract L66_ZeroAddressValidation is Test {
40
40
  vm.expectRevert(
41
41
  abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_ZeroAddressProjectsWithProjectOwner.selector)
42
42
  );
43
- new MockOwnable(IJBProjects(address(0)), PERMISSIONS, address(0), projectId);
43
+ new MockOwnable(IJBProjects(address(0)), permissions, address(0), projectId);
44
44
  }
45
45
 
46
46
  /// @notice Deploying with projects=address(0) and projectId=0 (address-based ownership)
47
47
  /// should NOT revert for this error — it's valid as long as initialOwner != address(0).
48
48
  function test_zeroProjectsWithAddressOwnership_succeeds() public {
49
49
  // This is valid: address-based ownership with projects=address(0).
50
- MockOwnable ownable = new MockOwnable(IJBProjects(address(0)), PERMISSIONS, alice, uint88(0));
50
+ MockOwnable ownable = new MockOwnable(IJBProjects(address(0)), permissions, alice, uint88(0));
51
51
  assertEq(ownable.owner(), alice, "Owner should be alice with address-based ownership");
52
52
  }
53
53
 
54
- /// @notice Normal deployment with valid PROJECTS contract and projectId succeeds.
54
+ /// @notice Normal deployment with valid projects contract and projectId succeeds.
55
55
  function test_validProjectsWithProjectId_succeeds() public {
56
- uint256 projectId = PROJECTS.createFor(alice);
57
- MockOwnable ownable = new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(projectId));
56
+ uint256 projectId = projects.createFor(alice);
57
+ // forge-lint: disable-next-line(unsafe-typecast)
58
+ MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
58
59
  assertEq(ownable.owner(), alice, "Owner should be alice via project NFT");
59
60
  }
60
61
 
61
62
  /// @notice The existing check for both zero owner and zero projectId is still enforced.
62
63
  function test_bothZero_stillReverts() public {
63
64
  vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
64
- new MockOwnable(PROJECTS, PERMISSIONS, address(0), uint88(0));
65
+ new MockOwnable(projects, permissions, address(0), uint88(0));
65
66
  }
66
67
  }