@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.
- package/foundry.toml +1 -1
- package/package.json +13 -4
- package/src/JBOwnable.sol +5 -10
- package/src/JBOwnableOverrides.sol +29 -23
- package/src/interfaces/IJBOwnable.sol +7 -5
- package/src/structs/JBOwner.sol +6 -6
- package/ADMINISTRATION.md +0 -79
- package/ARCHITECTURE.md +0 -92
- package/AUDIT_INSTRUCTIONS.md +0 -69
- package/RISKS.md +0 -51
- package/SKILLS.md +0 -41
- package/STYLE_GUIDE.md +0 -610
- package/USER_JOURNEYS.md +0 -117
- package/slither-ci.config.json +0 -10
- package/test/CodexUnmintedProjectHijack.t.sol +0 -45
- package/test/Ownable.t.sol +0 -383
- package/test/OwnableAttacks.t.sol +0 -190
- package/test/OwnableEdgeCases.t.sol +0 -437
- package/test/OwnableInvariantTests.sol +0 -48
- package/test/audit/PermissionDriftAfterProjectTransfer.t.sol +0 -58
- package/test/audit/PermissionIdNFTTransfer.t.sol +0 -93
- package/test/audit/ProjectTransferPermissionPolicy.t.sol +0 -58
- package/test/handlers/OwnableHandler.sol +0 -75
- package/test/regression/BurnLockProtection.t.sol +0 -112
- package/test/regression/RootPermissionBypassesPermissionIdZero.t.sol +0 -87
- package/test/regression/ZeroAddressValidation.t.sol +0 -67
package/USER_JOURNEYS.md
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# User Journeys
|
|
2
|
-
|
|
3
|
-
## Repo Purpose
|
|
4
|
-
|
|
5
|
-
This repo adapts `Ownable`-style control to Juicebox project ownership and project-scoped operator permissions. It is an ownership adapter. It does not replace the underlying ownership or permission registries in [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md).
|
|
6
|
-
|
|
7
|
-
## Primary Actors
|
|
8
|
-
|
|
9
|
-
- protocol or product teams that want `onlyOwner` to follow a project NFT
|
|
10
|
-
- operators who need owner-like access without receiving the project itself
|
|
11
|
-
- auditors checking whether delegated owner semantics strand or over-grant authority
|
|
12
|
-
|
|
13
|
-
## Key Surfaces
|
|
14
|
-
|
|
15
|
-
- `JBOwnable`: `Ownable`-style adapter whose owner follows a Juicebox project
|
|
16
|
-
- `JBOwnableOverrides`: extension that lets a project-scoped permission satisfy `onlyOwner`
|
|
17
|
-
- `owner()`, `transferOwnership(...)`, `transferOwnershipToProject(...)`, `setPermissionId(...)`: core ownership-resolution and migration paths
|
|
18
|
-
|
|
19
|
-
## Journey 1: Give A Contract To A Juicebox Project Instead Of A Wallet
|
|
20
|
-
|
|
21
|
-
**Actor:** downstream contract author.
|
|
22
|
-
|
|
23
|
-
**Intent:** make a contract follow Juicebox project ownership instead of a fixed EOA or multisig.
|
|
24
|
-
|
|
25
|
-
**Preconditions**
|
|
26
|
-
- the downstream contract wants `onlyOwner` ergonomics
|
|
27
|
-
- a project ID and `JBProjects` dependency are already known
|
|
28
|
-
|
|
29
|
-
**Main Flow**
|
|
30
|
-
1. Inherit `JBOwnable` or `JBOwnableOverrides`.
|
|
31
|
-
2. Initialize ownership with the relevant project ID and `JBProjects` reference.
|
|
32
|
-
3. Let `owner()` resolve through the current project NFT holder instead of a fixed address.
|
|
33
|
-
|
|
34
|
-
**Failure Modes**
|
|
35
|
-
- the contract assumes ordinary `Ownable` transfer semantics after adopting project-based ownership
|
|
36
|
-
- the wrong project ID is configured
|
|
37
|
-
- reviewers ignore the adapter and audit the downstream contract as if `owner` were fixed
|
|
38
|
-
|
|
39
|
-
**Postconditions**
|
|
40
|
-
- `owner()` now resolves through the configured project NFT instead of a fixed wallet
|
|
41
|
-
|
|
42
|
-
## Journey 2: Delegate Owner-Level Access To Operators
|
|
43
|
-
|
|
44
|
-
**Actor:** current project owner.
|
|
45
|
-
|
|
46
|
-
**Intent:** let an operator satisfy `onlyOwner` for one contract without transferring the project.
|
|
47
|
-
|
|
48
|
-
**Preconditions**
|
|
49
|
-
- the downstream contract uses `JBOwnableOverrides`
|
|
50
|
-
- the team has chosen the permission ID that should count as delegated owner access
|
|
51
|
-
|
|
52
|
-
**Main Flow**
|
|
53
|
-
1. Choose the permission ID the downstream contract should respect.
|
|
54
|
-
2. Grant that permission through `JBPermissions`.
|
|
55
|
-
3. `JBOwnableOverrides` treats the operator as satisfying `onlyOwner` for that contract.
|
|
56
|
-
|
|
57
|
-
**Failure Modes**
|
|
58
|
-
- teams grant a broader permission than intended
|
|
59
|
-
- downstream reviewers forget that `onlyOwner` may resolve through permissions instead of direct ownership
|
|
60
|
-
- operators keep stale permissions after governance changes
|
|
61
|
-
|
|
62
|
-
**Postconditions**
|
|
63
|
-
- the chosen operator can satisfy `onlyOwner` without receiving direct ownership of the project or contract
|
|
64
|
-
|
|
65
|
-
## Journey 3: Change The Delegated Permission ID Without Changing Ownership
|
|
66
|
-
|
|
67
|
-
**Actor:** current effective owner.
|
|
68
|
-
|
|
69
|
-
**Intent:** rotate delegated owner policy without changing the underlying owner.
|
|
70
|
-
|
|
71
|
-
**Preconditions**
|
|
72
|
-
- the contract already uses `JBOwnableOverrides`
|
|
73
|
-
- all operators who need continued access can be regranted under the new permission ID
|
|
74
|
-
|
|
75
|
-
**Main Flow**
|
|
76
|
-
1. Update the permission ID the adapter treats as owner-equivalent with `setPermissionId(...)`.
|
|
77
|
-
2. Re-grant the new permission where needed.
|
|
78
|
-
3. Re-audit operator assumptions because the old permission no longer satisfies `onlyOwner`.
|
|
79
|
-
|
|
80
|
-
**Failure Modes**
|
|
81
|
-
- operator access disappears unintentionally after a permission-ID rotation
|
|
82
|
-
- teams forget that old delegations stop working immediately
|
|
83
|
-
|
|
84
|
-
**Postconditions**
|
|
85
|
-
- the adapter now resolves delegated owner access through the new permission ID only
|
|
86
|
-
|
|
87
|
-
## Journey 4: Transfer Or Burn Ownership Deliberately
|
|
88
|
-
|
|
89
|
-
**Actor:** current effective owner.
|
|
90
|
-
|
|
91
|
-
**Intent:** move or remove control with full awareness of the consequences.
|
|
92
|
-
|
|
93
|
-
**Preconditions**
|
|
94
|
-
- the team understands whether admin recovery should remain possible
|
|
95
|
-
- downstream integrations can tolerate the new owner model
|
|
96
|
-
|
|
97
|
-
**Main Flow**
|
|
98
|
-
1. Use `transferOwnership(...)` for an address owner or `transferOwnershipToProject(...)` for a project owner.
|
|
99
|
-
2. Re-establish delegated permission policy if the new owner still wants operators.
|
|
100
|
-
3. Renounce only when permanent admin loss is intentional.
|
|
101
|
-
|
|
102
|
-
**Failure Modes**
|
|
103
|
-
- ownership is burned even though the downstream contract still needs administration
|
|
104
|
-
- teams forget that delegated permissions reset across ownership changes
|
|
105
|
-
|
|
106
|
-
**Postconditions**
|
|
107
|
-
- control moves to the chosen address or project, or is intentionally removed
|
|
108
|
-
|
|
109
|
-
## Trust Boundaries
|
|
110
|
-
|
|
111
|
-
- this repo trusts `JBProjects` for project ownership and `JBPermissions` for delegated authority
|
|
112
|
-
- downstream contracts still need their own audit because this adapter changes who satisfies `onlyOwner`
|
|
113
|
-
|
|
114
|
-
## Hand-Offs
|
|
115
|
-
|
|
116
|
-
- Use [nana-core-v6](../nana-core-v6/USER_JOURNEYS.md) for the project-NFT and permission machinery this adapter depends on.
|
|
117
|
-
- Use [nana-permission-ids-v6](../nana-permission-ids-v6/USER_JOURNEYS.md) if you need the shared numeric permission vocabulary for delegated `onlyOwner` checks.
|
package/slither-ci.config.json
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"detectors_to_exclude": "timestamp,uninitialized-local,naming-convention,solc-version,shadowing-local",
|
|
3
|
-
"exclude_informational": true,
|
|
4
|
-
"exclude_low": false,
|
|
5
|
-
"exclude_medium": false,
|
|
6
|
-
"exclude_high": false,
|
|
7
|
-
"disable_color": false,
|
|
8
|
-
"filter_paths": "(mocks/|test/|node_modules/|lib/)",
|
|
9
|
-
"legacy_ast": false
|
|
10
|
-
}
|
|
@@ -1,45 +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
|
-
|
|
13
|
-
contract CodexUnmintedProjectHijackTest is Test {
|
|
14
|
-
IJBProjects internal projects;
|
|
15
|
-
IJBPermissions internal permissions;
|
|
16
|
-
|
|
17
|
-
address internal deployer = makeAddr("deployer");
|
|
18
|
-
address internal attacker = makeAddr("attacker");
|
|
19
|
-
address internal intendedOwner = makeAddr("intendedOwner");
|
|
20
|
-
|
|
21
|
-
function setUp() public {
|
|
22
|
-
permissions = new JBPermissions(address(0));
|
|
23
|
-
projects = new JBProjects(address(this), address(0), address(0));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function test_unmintedProjectOwnerCanBeHijackedByFirstMinter() external {
|
|
27
|
-
vm.prank(deployer);
|
|
28
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), 1);
|
|
29
|
-
|
|
30
|
-
assertEq(ownable.owner(), address(0), "owner should be unresolved before project 1 exists");
|
|
31
|
-
|
|
32
|
-
vm.prank(attacker);
|
|
33
|
-
uint256 hijackedProjectId = projects.createFor(attacker);
|
|
34
|
-
|
|
35
|
-
assertEq(hijackedProjectId, 1, "attacker should receive the configured project ID");
|
|
36
|
-
assertEq(ownable.owner(), attacker, "first minter becomes owner of the ownable contract");
|
|
37
|
-
|
|
38
|
-
vm.prank(attacker);
|
|
39
|
-
ownable.protectedMethod();
|
|
40
|
-
|
|
41
|
-
vm.prank(intendedOwner);
|
|
42
|
-
vm.expectRevert();
|
|
43
|
-
ownable.protectedMethod();
|
|
44
|
-
}
|
|
45
|
-
}
|
package/test/Ownable.t.sol
DELETED
|
@@ -1,383 +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 {JBPermissioned} from "@bananapus/core-v6/src/abstract/JBPermissioned.sol";
|
|
10
|
-
import {JBProjects} from "@bananapus/core-v6/src/JBProjects.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 {IJBProjects} from "@bananapus/core-v6/src/interfaces/IJBProjects.sol";
|
|
14
|
-
|
|
15
|
-
contract OwnableTest is Test {
|
|
16
|
-
IJBProjects projects;
|
|
17
|
-
IJBPermissions permissions;
|
|
18
|
-
|
|
19
|
-
modifier isNotContract(address a) {
|
|
20
|
-
uint256 size;
|
|
21
|
-
assembly {
|
|
22
|
-
size := extcodesize(a)
|
|
23
|
-
}
|
|
24
|
-
vm.assume(size == 0);
|
|
25
|
-
_;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function setUp() public {
|
|
29
|
-
// Deploy the permissions contract.
|
|
30
|
-
permissions = new JBPermissions(address(0));
|
|
31
|
-
// Deploy the projects contract.
|
|
32
|
-
projects = new JBProjects(address(123), address(0), address(0));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function testDeployerDoesNotBecomeOwner(address deployer, address owner) public isNotContract(owner) {
|
|
36
|
-
// `CreateFor` won't work if the address is a contract that doesn't support `ERC721Receiver`.
|
|
37
|
-
vm.assume(owner != address(0));
|
|
38
|
-
|
|
39
|
-
vm.prank(deployer);
|
|
40
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, owner, uint88(0));
|
|
41
|
-
|
|
42
|
-
assertEq(owner, ownable.owner(), "Deployer did not become the owner.");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function testJBOwnableFollowsTheProjectOwner(
|
|
46
|
-
address projectOwner,
|
|
47
|
-
address newProjectOwner
|
|
48
|
-
)
|
|
49
|
-
public
|
|
50
|
-
isNotContract(projectOwner)
|
|
51
|
-
isNotContract(newProjectOwner)
|
|
52
|
-
{
|
|
53
|
-
// `CreateFor` won't work if the address is a contract that doesn't support `ERC721Receiver`.
|
|
54
|
-
vm.assume(projectOwner != address(0));
|
|
55
|
-
// Can't transfer ownership to the zero address.
|
|
56
|
-
vm.assume(newProjectOwner != address(0));
|
|
57
|
-
|
|
58
|
-
// Create a project for the owner.
|
|
59
|
-
uint256 projectId = projects.createFor(projectOwner);
|
|
60
|
-
|
|
61
|
-
// Create the `Ownable` contract.
|
|
62
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
63
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(projectId));
|
|
64
|
-
|
|
65
|
-
// Make sure the deployer owns it.
|
|
66
|
-
assertEq(projectOwner, ownable.owner(), "Deployer is not the owner.");
|
|
67
|
-
|
|
68
|
-
// Transfer the project's ownership.
|
|
69
|
-
vm.prank(projectOwner);
|
|
70
|
-
projects.transferFrom(projectOwner, newProjectOwner, projectId);
|
|
71
|
-
|
|
72
|
-
// Make sure the `Ownable` contract has also been transferred to the new project owner.
|
|
73
|
-
assertEq(newProjectOwner, ownable.owner(), "Ownable did not follow the Project owner.");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function testBasicOwnable(
|
|
77
|
-
address projectOwner,
|
|
78
|
-
address newOwnableOwner
|
|
79
|
-
)
|
|
80
|
-
public
|
|
81
|
-
isNotContract(projectOwner)
|
|
82
|
-
isNotContract(newOwnableOwner)
|
|
83
|
-
{
|
|
84
|
-
// Ownership can't be transferred to the 0 address. To transfer to the 0 address, ownership must be renounced.
|
|
85
|
-
vm.assume(newOwnableOwner != address(0));
|
|
86
|
-
// `CreateFor` won't work if the address is a contract that doesn't support `ERC721Receiver`.
|
|
87
|
-
vm.assume(projectOwner != address(0));
|
|
88
|
-
|
|
89
|
-
// Create a project for the owner.
|
|
90
|
-
uint256 _projectId = projects.createFor(projectOwner);
|
|
91
|
-
|
|
92
|
-
// Create the `Ownable` contract.
|
|
93
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
94
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
|
|
95
|
-
|
|
96
|
-
// Make sure the project owner owns it.
|
|
97
|
-
assertEq(projectOwner, ownable.owner(), "Deployer is not the owner.");
|
|
98
|
-
|
|
99
|
-
// We now stop using it as a `JBOwnable` and start using it like a basic `Ownable`.
|
|
100
|
-
vm.prank(projectOwner);
|
|
101
|
-
ownable.transferOwnership(newOwnableOwner);
|
|
102
|
-
// Make sure it was transferred to the new owner.
|
|
103
|
-
assertEq(newOwnableOwner, ownable.owner());
|
|
104
|
-
// Sanity check to make sure it only the `Ownable` changed, and that the project did not.
|
|
105
|
-
assertEq(projects.ownerOf(_projectId), projectOwner);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function testCantTransferToProjectZero(address owner) public {
|
|
109
|
-
vm.assume(owner != address(0));
|
|
110
|
-
vm.startPrank(owner);
|
|
111
|
-
|
|
112
|
-
// Create the `Ownable` contract.
|
|
113
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, owner, 0);
|
|
114
|
-
|
|
115
|
-
vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
|
|
116
|
-
|
|
117
|
-
// Transfer ownership to project ID 0 (should revert).
|
|
118
|
-
ownable.transferOwnershipToProject(0);
|
|
119
|
-
vm.stopPrank();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function testCantTransferToAddressZero(address owner) public {
|
|
123
|
-
vm.assume(owner != address(0));
|
|
124
|
-
vm.startPrank(owner);
|
|
125
|
-
|
|
126
|
-
// Create the `Ownable` contract.
|
|
127
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, owner, uint88(0));
|
|
128
|
-
|
|
129
|
-
vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
|
|
130
|
-
|
|
131
|
-
// Transfer ownership to the 0 address (should revert).
|
|
132
|
-
ownable.transferOwnership(address(0));
|
|
133
|
-
vm.stopPrank();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function testOwnableFollowsProjectOwner(
|
|
137
|
-
address projectOwner,
|
|
138
|
-
address newProjectOwner
|
|
139
|
-
)
|
|
140
|
-
public
|
|
141
|
-
isNotContract(projectOwner)
|
|
142
|
-
isNotContract(newProjectOwner)
|
|
143
|
-
{
|
|
144
|
-
vm.assume(projectOwner != newProjectOwner);
|
|
145
|
-
// `CreateFor` won't work if the address is a contract that doesn't support `ERC721Receiver`.
|
|
146
|
-
vm.assume(projectOwner != address(0));
|
|
147
|
-
vm.assume(newProjectOwner != address(0));
|
|
148
|
-
|
|
149
|
-
// Create a project for the owner.
|
|
150
|
-
uint256 _projectId = projects.createFor(projectOwner);
|
|
151
|
-
|
|
152
|
-
// Create the `Ownable` contract.
|
|
153
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
154
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
|
|
155
|
-
|
|
156
|
-
// Make sure the project owner owns it.
|
|
157
|
-
assertEq(projectOwner, ownable.owner(), "Deployer is not the owner.");
|
|
158
|
-
|
|
159
|
-
// Transfer the project ownership.
|
|
160
|
-
vm.prank(projectOwner);
|
|
161
|
-
projects.transferFrom(projectOwner, newProjectOwner, _projectId);
|
|
162
|
-
assertEq(projects.ownerOf(_projectId), newProjectOwner);
|
|
163
|
-
|
|
164
|
-
// Make sure the `Ownable` contract has also been transferred to the new project owner.
|
|
165
|
-
assertEq(newProjectOwner, ownable.owner());
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function testOwnableOwnerCanRennounce(address deployer, address owner) public {
|
|
169
|
-
vm.assume(owner != address(0));
|
|
170
|
-
vm.assume(deployer != owner);
|
|
171
|
-
|
|
172
|
-
// Create the `Ownable` contract.
|
|
173
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, owner, uint88(0));
|
|
174
|
-
|
|
175
|
-
// Transfer ownership to the project owner.
|
|
176
|
-
vm.prank(owner);
|
|
177
|
-
ownable.transferOwnership(owner);
|
|
178
|
-
assertEq(owner, ownable.owner(), "Deployer is not the owner.");
|
|
179
|
-
|
|
180
|
-
// Renounce the ownership.
|
|
181
|
-
vm.prank(owner);
|
|
182
|
-
ownable.renounceOwnership();
|
|
183
|
-
assertEq(address(0), ownable.owner(), "Owner was not renounced.");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function testJBOwnableOwnerCanRennounce(address deployer, address projectOwner) public isNotContract(projectOwner) {
|
|
187
|
-
// `CreateFor` won't work if the address is a contract that doesn't support `ERC721Receiver`.
|
|
188
|
-
vm.assume(projectOwner != address(0));
|
|
189
|
-
|
|
190
|
-
// Create a project for the owner.
|
|
191
|
-
uint256 _projectId = projects.createFor(projectOwner);
|
|
192
|
-
|
|
193
|
-
// Create the `Ownable` contract.
|
|
194
|
-
vm.prank(deployer);
|
|
195
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
196
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
|
|
197
|
-
|
|
198
|
-
// Renounce the ownership.
|
|
199
|
-
vm.prank(projectOwner);
|
|
200
|
-
ownable.renounceOwnership();
|
|
201
|
-
assertEq(address(0), ownable.owner(), "Owner was not renounced.");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function testJBOwnablePermissions(
|
|
205
|
-
address projectOwner,
|
|
206
|
-
address callerAddress,
|
|
207
|
-
uint8 requiredPermissionId,
|
|
208
|
-
uint8[] memory permissionIdsToGrant
|
|
209
|
-
)
|
|
210
|
-
public
|
|
211
|
-
isNotContract(projectOwner)
|
|
212
|
-
{
|
|
213
|
-
// `CreateFor` won't work if the address is a contract that doesn't support `ERC721Receiver`.
|
|
214
|
-
vm.assume(projectOwner != address(0) && callerAddress != projectOwner);
|
|
215
|
-
requiredPermissionId = uint8(bound(uint256(requiredPermissionId), 1, 255));
|
|
216
|
-
|
|
217
|
-
// Truncate array instead of rejecting to avoid exceeding max_test_rejects.
|
|
218
|
-
if (permissionIdsToGrant.length > 4) {
|
|
219
|
-
assembly {
|
|
220
|
-
mstore(permissionIdsToGrant, 4)
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Create a project for the owner.
|
|
225
|
-
uint256 _projectId = projects.createFor(projectOwner);
|
|
226
|
-
|
|
227
|
-
// Create the `Ownable` contract.
|
|
228
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
229
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
|
|
230
|
-
|
|
231
|
-
// Set the required permission.
|
|
232
|
-
vm.prank(projectOwner);
|
|
233
|
-
ownable.setPermissionId(requiredPermissionId);
|
|
234
|
-
|
|
235
|
-
// Attempt to call the protected method without permission.
|
|
236
|
-
vm.expectRevert(
|
|
237
|
-
abi.encodeWithSelector(
|
|
238
|
-
JBPermissioned.JBPermissioned_Unauthorized.selector,
|
|
239
|
-
projectOwner,
|
|
240
|
-
callerAddress,
|
|
241
|
-
_projectId,
|
|
242
|
-
requiredPermissionId
|
|
243
|
-
)
|
|
244
|
-
);
|
|
245
|
-
vm.prank(callerAddress);
|
|
246
|
-
ownable.protectedMethod();
|
|
247
|
-
|
|
248
|
-
// Give permission.
|
|
249
|
-
bool _shouldHavePermission;
|
|
250
|
-
uint8[] memory _permissionIds = new uint8[](permissionIdsToGrant.length);
|
|
251
|
-
for (uint256 i; i < permissionIdsToGrant.length; i++) {
|
|
252
|
-
permissionIdsToGrant[i] = uint8(bound(uint256(permissionIdsToGrant[i]), 1, 255));
|
|
253
|
-
// Check if the permission we need is in the permissions to grant, including if it's ROOT.
|
|
254
|
-
if (permissionIdsToGrant[i] == requiredPermissionId || permissionIdsToGrant[i] == 1) {
|
|
255
|
-
_shouldHavePermission = true;
|
|
256
|
-
}
|
|
257
|
-
_permissionIds[i] = permissionIdsToGrant[i];
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// The owner gives permission to the caller.
|
|
261
|
-
vm.prank(projectOwner);
|
|
262
|
-
permissions.setPermissionsFor(
|
|
263
|
-
projectOwner,
|
|
264
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
265
|
-
JBPermissionsData({operator: callerAddress, projectId: uint56(_projectId), permissionIds: _permissionIds})
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
if (!_shouldHavePermission) {
|
|
269
|
-
vm.expectRevert(
|
|
270
|
-
abi.encodeWithSelector(
|
|
271
|
-
JBPermissioned.JBPermissioned_Unauthorized.selector,
|
|
272
|
-
projectOwner,
|
|
273
|
-
callerAddress,
|
|
274
|
-
_projectId,
|
|
275
|
-
requiredPermissionId
|
|
276
|
-
)
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
vm.prank(callerAddress);
|
|
281
|
-
ownable.protectedMethod();
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function testJBOwnablePermissionsRequiredModifier(
|
|
285
|
-
address projectOwner,
|
|
286
|
-
address callerAddress,
|
|
287
|
-
uint8 requiredPermissionId,
|
|
288
|
-
uint8[] memory permissionIdsToGrant
|
|
289
|
-
)
|
|
290
|
-
public
|
|
291
|
-
isNotContract(projectOwner)
|
|
292
|
-
{
|
|
293
|
-
// `CreateFor` won't work if the address is a contract that doesn't support `ERC721Receiver`.
|
|
294
|
-
vm.assume(projectOwner != address(0) && callerAddress != projectOwner);
|
|
295
|
-
requiredPermissionId = uint8(bound(uint256(requiredPermissionId), 1, 255));
|
|
296
|
-
|
|
297
|
-
// Truncate array instead of rejecting to avoid exceeding max_test_rejects.
|
|
298
|
-
if (permissionIdsToGrant.length > 4) {
|
|
299
|
-
assembly {
|
|
300
|
-
mstore(permissionIdsToGrant, 4)
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Create a project for the owner.
|
|
305
|
-
uint256 _projectId = projects.createFor(projectOwner);
|
|
306
|
-
|
|
307
|
-
// Create the `Ownable` contract.
|
|
308
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
309
|
-
MockOwnable ownable = new MockOwnable(projects, permissions, address(0), uint88(_projectId));
|
|
310
|
-
|
|
311
|
-
// Set the permission that is required.
|
|
312
|
-
ownable.setPermission(requiredPermissionId);
|
|
313
|
-
|
|
314
|
-
// Attempt to call the protected method without permission.
|
|
315
|
-
vm.expectRevert(
|
|
316
|
-
abi.encodeWithSelector(
|
|
317
|
-
JBPermissioned.JBPermissioned_Unauthorized.selector,
|
|
318
|
-
projectOwner,
|
|
319
|
-
callerAddress,
|
|
320
|
-
_projectId,
|
|
321
|
-
requiredPermissionId
|
|
322
|
-
)
|
|
323
|
-
);
|
|
324
|
-
vm.prank(callerAddress);
|
|
325
|
-
ownable.protectedMethodWithRequirePermission();
|
|
326
|
-
|
|
327
|
-
// Give permission.
|
|
328
|
-
bool _shouldHavePermission;
|
|
329
|
-
uint8[] memory _permissionIds = new uint8[](permissionIdsToGrant.length);
|
|
330
|
-
for (uint256 i; i < permissionIdsToGrant.length; i++) {
|
|
331
|
-
permissionIdsToGrant[i] = uint8(bound(uint256(permissionIdsToGrant[i]), 1, 255));
|
|
332
|
-
// Check if the permission we need is in the permissions to grant, including if it's ROOT.
|
|
333
|
-
if (permissionIdsToGrant[i] == requiredPermissionId || permissionIdsToGrant[i] == 1) {
|
|
334
|
-
_shouldHavePermission = true;
|
|
335
|
-
}
|
|
336
|
-
_permissionIds[i] = permissionIdsToGrant[i];
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// The owner gives permission to the caller.
|
|
340
|
-
vm.prank(projectOwner);
|
|
341
|
-
permissions.setPermissionsFor(
|
|
342
|
-
projectOwner,
|
|
343
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
344
|
-
JBPermissionsData({operator: callerAddress, projectId: uint56(_projectId), permissionIds: _permissionIds})
|
|
345
|
-
);
|
|
346
|
-
|
|
347
|
-
if (!_shouldHavePermission) {
|
|
348
|
-
vm.expectRevert(
|
|
349
|
-
abi.encodeWithSelector(
|
|
350
|
-
JBPermissioned.JBPermissioned_Unauthorized.selector,
|
|
351
|
-
projectOwner,
|
|
352
|
-
callerAddress,
|
|
353
|
-
_projectId,
|
|
354
|
-
requiredPermissionId
|
|
355
|
-
)
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
vm.prank(callerAddress);
|
|
360
|
-
ownable.protectedMethodWithRequirePermission();
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function testCantConfigureOwnerAndProject(address owner, address projectOwner) public isNotContract(projectOwner) {
|
|
364
|
-
vm.assume(owner != address(0) && projectOwner != address(0));
|
|
365
|
-
|
|
366
|
-
// Create a project for the owner.
|
|
367
|
-
uint256 _projectId = projects.createFor(projectOwner);
|
|
368
|
-
|
|
369
|
-
// Should revert because we set both a owner and a projectOwner
|
|
370
|
-
vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
|
|
371
|
-
|
|
372
|
-
// Create the `Ownable` contract.
|
|
373
|
-
// forge-lint: disable-next-line(unsafe-typecast)
|
|
374
|
-
new MockOwnable(projects, permissions, address(owner), uint88(_projectId));
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function testCantInitializeAsRenounced() public {
|
|
378
|
-
// Should revert because we set both a owner and a projectOwner
|
|
379
|
-
vm.expectRevert(abi.encodeWithSelector(JBOwnableOverrides.JBOwnableOverrides_InvalidNewOwner.selector));
|
|
380
|
-
// Create the `Ownable` contract.
|
|
381
|
-
new MockOwnable(projects, permissions, address(0), uint88(0));
|
|
382
|
-
}
|
|
383
|
-
}
|