@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.
@@ -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
+ alice,
242
+ // forge-lint: disable-next-line(unsafe-typecast)
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
  }