@bananapus/ownable-v6 0.0.22 → 0.0.24

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,58 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
-
6
- import {MockOwnable} from "../mocks/MockOwnable.sol";
7
-
8
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
9
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
10
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
11
-
12
- contract PermissionDriftAfterProjectTransferTest is Test {
13
- JBPermissions internal permissions;
14
- JBProjects internal projects;
15
-
16
- address internal alice = makeAddr("alice");
17
- address internal bob = makeAddr("bob");
18
- address internal operator = makeAddr("operator");
19
-
20
- function setUp() public {
21
- permissions = new JBPermissions(address(0));
22
- projects = new JBProjects(address(this), address(0), address(0));
23
- }
24
-
25
- function test_operatorCannotInheritOwnerAccessAfterProjectNftTransfer() public {
26
- uint256 projectId = projects.createFor(alice);
27
- // forge-lint: disable-next-line(unsafe-typecast)
28
- MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
29
-
30
- vm.prank(alice);
31
- ownable.setPermissionId(42);
32
-
33
- uint8[] memory permissionIds = new uint8[](1);
34
- permissionIds[0] = 42;
35
-
36
- vm.prank(bob);
37
- permissions.setPermissionsFor(
38
- bob, JBPermissionsData({operator: operator, projectId: 0, permissionIds: permissionIds})
39
- );
40
-
41
- vm.prank(alice);
42
- projects.transferFrom(alice, bob, projectId);
43
-
44
- assertEq(ownable.owner(), bob, "project NFT transfer makes bob the direct owner");
45
- (, uint88 storedProjectId, uint8 storedPermissionId) = ownable.jbOwner();
46
- assertEq(storedProjectId, projectId, "ownable remains project-owned");
47
- assertEq(storedPermissionId, 42, "old owner's permissionId survived the ownership change in storage");
48
-
49
- // After the fix, the stale permissionId is ignored because the resolved owner
50
- // (bob) differs from _permissionOwner (alice). The operator cannot seize ownership.
51
- vm.prank(operator);
52
- vm.expectRevert();
53
- ownable.transferOwnership(operator);
54
-
55
- // Bob is still the owner.
56
- assertEq(ownable.owner(), bob, "bob retains ownership; operator was blocked");
57
- }
58
- }
@@ -1,93 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
-
6
- import {MockOwnable} from "../mocks/MockOwnable.sol";
7
-
8
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
9
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
10
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
11
- import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
12
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
13
-
14
- contract PermissionIdNFTTransferTest is Test {
15
- IJBProjects internal projects;
16
- IJBPermissions internal permissions;
17
-
18
- address internal seller = makeAddr("seller");
19
- address internal buyer = makeAddr("buyer");
20
- address internal buyerOperator = makeAddr("buyerOperator");
21
-
22
- function setUp() public {
23
- permissions = new JBPermissions(address(0));
24
- projects = new JBProjects(address(this), address(0), address(0));
25
- }
26
-
27
- function test_projectNftTransferInvalidatesStalePermissionId() external {
28
- uint256 projectId = projects.createFor(seller);
29
- MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
30
-
31
- // Seller sets permissionId 30 while they own the project.
32
- vm.prank(seller);
33
- ownable.setPermissionId(30);
34
-
35
- // Buyer grants their own operator permissionId 30 for this project.
36
- uint8[] memory permissionIds = new uint8[](1);
37
- permissionIds[0] = 30;
38
- vm.prank(buyer);
39
- permissions.setPermissionsFor(
40
- buyer,
41
- JBPermissionsData({operator: buyerOperator, projectId: uint56(projectId), permissionIds: permissionIds})
42
- );
43
-
44
- // Seller transfers the project NFT to buyer.
45
- vm.prank(seller);
46
- projects.transferFrom(seller, buyer, projectId);
47
-
48
- assertEq(ownable.owner(), buyer, "project NFT transfer changes the effective owner");
49
-
50
- // The stored permissionId is still 30 in the struct, but the fix makes it
51
- // stale because the owner changed since the permissionId was set.
52
- (,, uint8 inheritedPermissionId) = ownable.jbOwner();
53
- assertEq(inheritedPermissionId, 30, "struct still holds old permissionId");
54
-
55
- // The stale permissionId should NOT allow the buyer's operator through.
56
- // After the fix, _checkOwner treats the permissionId as 0 (direct-owner-only)
57
- // because the resolved owner != _permissionOwner.
58
- vm.prank(buyerOperator);
59
- vm.expectRevert();
60
- ownable.protectedMethod();
61
- }
62
-
63
- function test_newOwnerCanSetOwnPermissionIdAndDelegateAccess() external {
64
- uint256 projectId = projects.createFor(seller);
65
- MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
66
-
67
- // Seller sets permissionId 30 while they own the project.
68
- vm.prank(seller);
69
- ownable.setPermissionId(30);
70
-
71
- // Transfer the project NFT to buyer.
72
- vm.prank(seller);
73
- projects.transferFrom(seller, buyer, projectId);
74
-
75
- // Buyer grants their own operator permissionId 30 for this project.
76
- uint8[] memory permissionIds = new uint8[](1);
77
- permissionIds[0] = 30;
78
- vm.prank(buyer);
79
- permissions.setPermissionsFor(
80
- buyer,
81
- JBPermissionsData({operator: buyerOperator, projectId: uint56(projectId), permissionIds: permissionIds})
82
- );
83
-
84
- // Buyer explicitly sets the permissionId on the ownable contract.
85
- // This updates _permissionOwner to the buyer, making the permissionId valid.
86
- vm.prank(buyer);
87
- ownable.setPermissionId(30);
88
-
89
- // Now buyerOperator should be able to call the protected method.
90
- vm.prank(buyerOperator);
91
- ownable.protectedMethod();
92
- }
93
- }
@@ -1,58 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
-
6
- import {MockOwnable} from "../mocks/MockOwnable.sol";
7
-
8
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
9
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
10
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
11
-
12
- contract ProjectTransferPermissionPolicyTest is Test {
13
- JBPermissions internal permissions;
14
- JBProjects internal projects;
15
-
16
- address internal seller = makeAddr("seller");
17
- address internal buyer = makeAddr("buyer");
18
- address internal operator = makeAddr("operator");
19
-
20
- function setUp() public {
21
- permissions = new JBPermissions(address(0));
22
- projects = new JBProjects(address(this), address(0), address(0));
23
- }
24
-
25
- function test_stalePermissionIdAfterProjectTransferBlocksBuyerOperator() public {
26
- uint256 projectId = projects.createFor(seller);
27
- // forge-lint: disable-next-line(unsafe-typecast)
28
- MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
29
-
30
- // The seller chooses a non-root ecosystem permission as this ownable's owner-equivalent permission.
31
- vm.prank(seller);
32
- ownable.setPermissionId(30);
33
-
34
- // The buyer has independently delegated permission 30 across their projects for unrelated operations.
35
- uint8[] memory permissionIds = new uint8[](1);
36
- permissionIds[0] = 30;
37
-
38
- vm.prank(buyer);
39
- permissions.setPermissionsFor(
40
- buyer, JBPermissionsData({operator: operator, projectId: 0, permissionIds: permissionIds})
41
- );
42
-
43
- // Buying/transferring the project NFT changes the effective owner, but does not reset permissionId in storage.
44
- vm.prank(seller);
45
- projects.transferFrom(seller, buyer, projectId);
46
-
47
- assertEq(ownable.owner(), buyer, "buyer is now the effective owner");
48
-
49
- (,, uint8 inheritedPermissionId) = ownable.jbOwner();
50
- assertEq(inheritedPermissionId, 30, "seller-selected permission policy persists in storage");
51
-
52
- // After the fix, the stale permissionId is ignored because the resolved owner (buyer)
53
- // differs from _permissionOwner (seller). The operator is blocked.
54
- vm.prank(operator);
55
- vm.expectRevert();
56
- ownable.protectedMethod();
57
- }
58
- }
@@ -1,75 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- // import { Test } from "forge-std/Test.sol";
5
- import {CommonBase} from "forge-std/Base.sol";
6
- import {StdCheats} from "forge-std/StdCheats.sol";
7
- import {StdUtils} from "forge-std/StdUtils.sol";
8
- import {MockOwnable} from "../mocks/MockOwnable.sol";
9
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
10
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
11
- import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
12
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
13
-
14
- contract OwnableHandler is CommonBase, StdCheats, StdUtils {
15
- IJBProjects public immutable PROJECTS;
16
- IJBPermissions public immutable PERMISSIONS;
17
- MockOwnable public immutable OWNABLE;
18
-
19
- address[] public actors;
20
- address internal currentActor;
21
-
22
- // Ghost variables for tracking state.
23
- uint256 public transferCount;
24
- uint256 public renounceCount;
25
- uint256 public projectTransferCount;
26
- bool public wasEverRenounced;
27
-
28
- modifier useActor(uint256 actorIndexSeed) {
29
- currentActor = actors[bound(actorIndexSeed, 0, actors.length - 1)];
30
- vm.startPrank(currentActor);
31
- _;
32
- vm.stopPrank();
33
- }
34
-
35
- constructor() {
36
- address deployer = vm.addr(1);
37
- address initialOwner = vm.addr(2);
38
- // Deploy the permissions contract.
39
- PERMISSIONS = new JBPermissions(address(0));
40
- // Deploy the `JBProjects` contract.
41
- PROJECTS = new JBProjects(address(123), address(0), address(0));
42
- // Deploy the `JBOwnable` contract.
43
- vm.prank(deployer);
44
- OWNABLE = new MockOwnable(PROJECTS, PERMISSIONS, initialOwner, uint88(0));
45
-
46
- actors.push(deployer);
47
- actors.push(initialOwner);
48
- actors.push(address(420));
49
- }
50
-
51
- function transferOwnershipToAddress(uint256 actorIndexSeed, address _newOwner) public useActor(actorIndexSeed) {
52
- // Skip zero address — that's renounceOwnership's job.
53
- if (_newOwner == address(0)) return;
54
-
55
- try OWNABLE.transferOwnership(_newOwner) {
56
- transferCount++;
57
- } catch {}
58
- }
59
-
60
- function renounceOwnership(uint256 actorIndexSeed) public useActor(actorIndexSeed) {
61
- try OWNABLE.renounceOwnership() {
62
- renounceCount++;
63
- wasEverRenounced = true;
64
- } catch {}
65
- }
66
-
67
- function transferOwnershipToProject(uint256 actorIndexSeed, uint256 projectId) public useActor(actorIndexSeed) {
68
- // Bound to valid project ID range (1 to type(uint88).max).
69
- projectId = bound(projectId, 1, type(uint88).max);
70
-
71
- try OWNABLE.transferOwnershipToProject(projectId) {
72
- projectTransferCount++;
73
- } catch {}
74
- }
75
- }
@@ -1,112 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
- import {MockOwnable} from "../mocks/MockOwnable.sol";
6
-
7
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
8
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
9
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
10
- import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
11
- import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
12
-
13
- /// @title BurnLockProtection
14
- /// @notice Verifies that if a project NFT is burned/invalidated,
15
- /// owner() returns address(0) and _checkOwner() reverts gracefully instead of
16
- /// permanently locking the contract with an unrecoverable revert.
17
- contract BurnLockProtection is Test {
18
- IJBProjects projects;
19
- IJBPermissions permissions;
20
-
21
- address alice = makeAddr("alice");
22
- address bob = makeAddr("bob");
23
-
24
- function setUp() public {
25
- permissions = new JBPermissions(address(0));
26
- projects = new JBProjects(address(123), address(0), address(0));
27
- }
28
-
29
- /// @notice When a project NFT is burned (simulated via mockCallRevert), owner() should
30
- /// return address(0) instead of reverting — contract degrades to "renounced" state.
31
- function test_burnedProjectNFT_ownerReturnsZero() public {
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
-
36
- // Verify normal operation first.
37
- assertEq(ownable.owner(), alice, "Owner should be alice before burn");
38
-
39
- // Simulate project NFT burn by making ownerOf revert for this projectId.
40
- vm.mockCallRevert(
41
- address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, projectId), "ERC721: invalid token ID"
42
- );
43
-
44
- // After burn, owner() should return address(0) — NOT revert.
45
- address resolvedOwner = ownable.owner();
46
- assertEq(resolvedOwner, address(0), "owner() should return address(0) when project NFT is burned");
47
- }
48
-
49
- /// @notice When a project NFT is burned, _checkOwner() should revert with the standard
50
- /// Unauthorized error (not an unrecoverable ownerOf revert), making the contract
51
- /// behave as if ownership was renounced.
52
- function test_burnedProjectNFT_checkOwnerRevertsGracefully() public {
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));
56
-
57
- // Alice can call the protected method before burn.
58
- vm.prank(alice);
59
- ownable.protectedMethod();
60
-
61
- // Simulate project NFT burn.
62
- vm.mockCallRevert(
63
- address(projects), abi.encodeWithSelector(IERC721.ownerOf.selector, projectId), "ERC721: invalid token ID"
64
- );
65
-
66
- // After burn, nobody can call protected methods — but the revert is graceful
67
- // (Unauthorized from _requirePermissionFrom, not a raw ownerOf revert).
68
- vm.prank(alice);
69
- vm.expectRevert();
70
- ownable.protectedMethod();
71
-
72
- vm.prank(bob);
73
- vm.expectRevert();
74
- ownable.protectedMethod();
75
- }
76
-
77
- /// @notice Address-based ownership is unaffected by the try-catch change.
78
- function test_addressBasedOwnership_unaffectedByTryCatch() public {
79
- MockOwnable ownable = new MockOwnable(projects, permissions, alice, 0);
80
-
81
- assertEq(ownable.owner(), alice, "Owner should be alice");
82
-
83
- vm.prank(alice);
84
- ownable.protectedMethod();
85
-
86
- // Transfer to bob.
87
- vm.prank(alice);
88
- ownable.transferOwnership(bob);
89
- assertEq(ownable.owner(), bob, "Owner should be bob after transfer");
90
-
91
- vm.prank(bob);
92
- ownable.protectedMethod();
93
- }
94
-
95
- /// @notice Normal project-based ownership still works correctly after the fix.
96
- function test_normalProjectOwnership_stillWorks() public {
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));
100
-
101
- assertEq(ownable.owner(), alice);
102
-
103
- // Transfer project NFT.
104
- vm.prank(alice);
105
- projects.transferFrom(alice, bob, projectId);
106
-
107
- assertEq(ownable.owner(), bob, "Owner should follow project NFT transfer");
108
-
109
- vm.prank(bob);
110
- ownable.protectedMethod();
111
- }
112
- }
@@ -1,87 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
-
6
- import {MockOwnable} from "../mocks/MockOwnable.sol";
7
-
8
- import {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
9
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
10
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
11
- import {JBPermissionsData} from "@bananapus/core-v6/src/structs/JBPermissionsData.sol";
12
-
13
- contract RootPermissionBypassesPermissionIdZeroTest is Test {
14
- JBProjects internal projects;
15
- JBPermissions internal permissions;
16
-
17
- address internal alice = makeAddr("alice");
18
- address internal operator = makeAddr("operator");
19
-
20
- function setUp() public {
21
- permissions = new JBPermissions(address(0));
22
- projects = new JBProjects(address(this), address(0), address(0));
23
- }
24
-
25
- /// @notice After M-39 fix: ROOT operator is rejected when permissionId=0 (direct-owner-only mode).
26
- function test_rootPermissionRejectedWhenPermissionIdIsZero() public {
27
- uint256 projectId = projects.createFor(alice);
28
- MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
29
-
30
- // Grant ROOT permission (id=1) to operator.
31
- uint8[] memory permissionIds = new uint8[](1);
32
- permissionIds[0] = 1;
33
-
34
- vm.prank(alice);
35
- permissions.setPermissionsFor(
36
- alice, JBPermissionsData({operator: operator, projectId: uint56(projectId), permissionIds: permissionIds})
37
- );
38
-
39
- (, uint88 storedProjectId, uint8 permissionId) = ownable.jbOwner();
40
- assertEq(storedProjectId, projectId);
41
- assertEq(permissionId, 0, "expected direct-owner-only mode");
42
-
43
- // Operator should be rejected when permissionId=0.
44
- vm.prank(operator);
45
- vm.expectRevert(
46
- abi.encodeWithSelector(JBPermissioned.JBPermissioned_Unauthorized.selector, alice, operator, projectId, 0)
47
- );
48
- ownable.protectedMethod();
49
- }
50
-
51
- /// @notice Direct owner still works when permissionId=0.
52
- function test_directOwnerStillWorksWithPermissionIdZero() public {
53
- uint256 projectId = projects.createFor(alice);
54
- MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
55
-
56
- (, uint88 storedProjectId, uint8 permissionId) = ownable.jbOwner();
57
- assertEq(storedProjectId, projectId);
58
- assertEq(permissionId, 0);
59
-
60
- // Alice (project owner) should still be able to call the protected method.
61
- vm.prank(alice);
62
- ownable.protectedMethod();
63
- }
64
-
65
- /// @notice Non-zero permissionId still delegates correctly via the permission system.
66
- function test_delegatedOperatorWorksWhenPermissionIdNonZero() public {
67
- uint256 projectId = projects.createFor(alice);
68
- MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
69
-
70
- // Set permissionId to 42 (non-zero = delegation enabled).
71
- vm.prank(alice);
72
- ownable.setPermissionId(42);
73
-
74
- // Grant permission 42 to operator.
75
- uint8[] memory permissionIds = new uint8[](1);
76
- permissionIds[0] = 42;
77
-
78
- vm.prank(alice);
79
- permissions.setPermissionsFor(
80
- alice, JBPermissionsData({operator: operator, projectId: uint56(projectId), permissionIds: permissionIds})
81
- );
82
-
83
- // Operator should succeed with matching permissionId.
84
- vm.prank(operator);
85
- ownable.protectedMethod();
86
- }
87
- }
@@ -1,67 +0,0 @@
1
- // SPDX-License-Identifier: UNLICENSED
2
- pragma solidity 0.8.28;
3
-
4
- import {Test} from "forge-std/Test.sol";
5
- import {MockOwnable} from "../mocks/MockOwnable.sol";
6
- import {JBOwnableOverrides} from "../../src/JBOwnableOverrides.sol";
7
-
8
- import {JBPermissions} from "@bananapus/core-v6/src/JBPermissions.sol";
9
- import {JBProjects} from "@bananapus/core-v6/src/JBProjects.sol";
10
- import {IJBPermissions} from "@bananapus/core-v6/src/interfaces/IJBPermissions.sol";
11
- import {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
12
-
13
- /// @title ZeroAddressValidation
14
- /// @notice Verifies that deploying with a zero-address projects
15
- /// contract and a non-zero projectId reverts at construction time, preventing
16
- /// permanently broken project-based ownership.
17
- contract ZeroAddressValidation is Test {
18
- IJBProjects projects;
19
- IJBPermissions permissions;
20
-
21
- address alice = makeAddr("alice");
22
-
23
- function setUp() public {
24
- permissions = new JBPermissions(address(0));
25
- projects = new JBProjects(address(123), address(0), address(0));
26
- }
27
-
28
- /// @notice Deploying with projects=address(0) and non-zero projectId must revert.
29
- function test_zeroProjectsWithProjectId_reverts() public {
30
- vm.expectRevert(
31
- abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_ZeroAddressProjectsWithProjectOwner.selector)
32
- );
33
- new MockOwnable(IJBProjects(address(0)), permissions, address(0), uint88(1));
34
- }
35
-
36
- /// @notice Fuzz: any non-zero projectId with projects=address(0) must revert.
37
- function testFuzz_zeroProjectsWithAnyProjectId_reverts(uint88 projectId) public {
38
- vm.assume(projectId != 0);
39
-
40
- vm.expectRevert(
41
- abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_ZeroAddressProjectsWithProjectOwner.selector)
42
- );
43
- new MockOwnable(IJBProjects(address(0)), permissions, address(0), projectId);
44
- }
45
-
46
- /// @notice Deploying with projects=address(0) and projectId=0 (address-based ownership)
47
- /// should NOT revert for this error — it's valid as long as initialOwner != address(0).
48
- function test_zeroProjectsWithAddressOwnership_succeeds() public {
49
- // This is valid: address-based ownership with projects=address(0).
50
- MockOwnable ownable = new MockOwnable(IJBProjects(address(0)), permissions, alice, uint88(0));
51
- assertEq(ownable.owner(), alice, "Owner should be alice with address-based ownership");
52
- }
53
-
54
- /// @notice Normal deployment with valid projects contract and projectId succeeds.
55
- function test_validProjectsWithProjectId_succeeds() public {
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));
59
- assertEq(ownable.owner(), alice, "Owner should be alice via project NFT");
60
- }
61
-
62
- /// @notice The existing check for both zero owner and zero projectId is still enforced.
63
- function test_bothZero_stillReverts() public {
64
- vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
65
- new MockOwnable(projects, permissions, address(0), uint88(0));
66
- }
67
- }