/
PermissionManager.sol
495 lines (435 loc) · 23.9 KB
/
PermissionManager.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.8;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import {IPermissionCondition} from "@aragon/osx-commons-contracts/src/permission/condition/IPermissionCondition.sol";
import {PermissionCondition} from "@aragon/osx-commons-contracts/src/permission/condition/PermissionCondition.sol";
import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol";
/// @title PermissionManager
/// @author Aragon X - 2021-2023
/// @notice The abstract permission manager used in a DAO, its associated plugins, and other framework-related components.
/// @custom:security-contact sirt@aragon.org
abstract contract PermissionManager is Initializable {
using AddressUpgradeable for address;
/// @notice The ID of the permission required to call the `grant`, `grantWithCondition`, `revoke`, and `bulk` function.
bytes32 public constant ROOT_PERMISSION_ID = keccak256("ROOT_PERMISSION");
/// @notice A special address encoding permissions that are valid for any address `who` or `where`.
address internal constant ANY_ADDR = address(type(uint160).max);
/// @notice A special address encoding if a permissions is not set and therefore not allowed.
address internal constant UNSET_FLAG = address(0);
/// @notice A special address encoding if a permission is allowed.
address internal constant ALLOW_FLAG = address(2);
/// @notice A mapping storing permissions as hashes (i.e., `permissionHash(where, who, permissionId)`) and their status encoded by an address (unset, allowed, or redirecting to a `PermissionCondition`).
mapping(bytes32 => address) internal permissionsHashed;
/// @notice Thrown if a call is unauthorized.
/// @param where The context in which the authorization reverted.
/// @param who The address (EOA or contract) missing the permission.
/// @param permissionId The permission identifier.
error Unauthorized(address where, address who, bytes32 permissionId);
/// @notice Thrown if a permission has been already granted with a different condition.
/// @dev This makes sure that condition on the same permission can not be overwriten by a different condition.
/// @param where The address of the target contract to grant `_who` permission to.
/// @param who The address (EOA or contract) to which the permission has already been granted.
/// @param permissionId The permission identifier.
/// @param currentCondition The current condition set for permissionId.
/// @param newCondition The new condition it tries to set for permissionId.
error PermissionAlreadyGrantedForDifferentCondition(
address where,
address who,
bytes32 permissionId,
address currentCondition,
address newCondition
);
/// @notice Thrown if a condition address is not a contract.
/// @param condition The address that is not a contract.
error ConditionNotAContract(IPermissionCondition condition);
/// @notice Thrown if a condition contract does not support the `IPermissionCondition` interface.
/// @param condition The address that is not a contract.
error ConditionInterfacNotSupported(IPermissionCondition condition);
/// @notice Thrown for `ROOT_PERMISSION_ID` or `EXECUTE_PERMISSION_ID` permission grants where `who` or `where` is `ANY_ADDR`.
error PermissionsForAnyAddressDisallowed();
/// @notice Thrown for permission grants where `who` and `where` are both `ANY_ADDR`.
error AnyAddressDisallowedForWhoAndWhere();
/// @notice Thrown if `Operation.GrantWithCondition` is requested as an operation but the method does not support it.
error GrantWithConditionNotSupported();
/// @notice Emitted when a permission `permission` is granted in the context `here` to the address `_who` for the contract `_where`.
/// @param permissionId The permission identifier.
/// @param here The address of the context in which the permission is granted.
/// @param where The address of the target contract for which `_who` receives permission.
/// @param who The address (EOA or contract) receiving the permission.
/// @param condition The address `ALLOW_FLAG` for regular permissions or, alternatively, the `IPermissionCondition` contract implementation to be used.
event Granted(
bytes32 indexed permissionId,
address indexed here,
address where,
address indexed who,
address condition
);
/// @notice Emitted when a permission `permission` is revoked in the context `here` from the address `_who` for the contract `_where`.
/// @param permissionId The permission identifier.
/// @param here The address of the context in which the permission is revoked.
/// @param where The address of the target contract for which `_who` loses permission.
/// @param who The address (EOA or contract) losing the permission.
event Revoked(
bytes32 indexed permissionId,
address indexed here,
address where,
address indexed who
);
/// @notice A modifier to make functions on inheriting contracts authorized. Permissions to call the function are checked through this permission manager.
/// @param _permissionId The permission identifier required to call the method this modifier is applied to.
modifier auth(bytes32 _permissionId) {
_auth(_permissionId);
_;
}
/// @notice Initialization method to set the initial owner of the permission manager.
/// @dev The initial owner is granted the `ROOT_PERMISSION_ID` permission.
/// @param _initialOwner The initial owner of the permission manager.
function __PermissionManager_init(address _initialOwner) internal onlyInitializing {
_initializePermissionManager({_initialOwner: _initialOwner});
}
/// @notice Grants permission to an address to call methods in a contract guarded by an auth modifier with the specified permission identifier.
/// @dev Requires the `ROOT_PERMISSION_ID` permission.
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) receiving the permission.
/// @param _permissionId The permission identifier.
/// @dev Note, that granting permissions with `_who` or `_where` equal to `ANY_ADDR` does not replace other permissions with specific `_who` and `_where` addresses that exist in parallel.
function grant(
address _where,
address _who,
bytes32 _permissionId
) external virtual auth(ROOT_PERMISSION_ID) {
_grant({_where: _where, _who: _who, _permissionId: _permissionId});
}
/// @notice Grants permission to an address to call methods in a target contract guarded by an auth modifier with the specified permission identifier if the referenced condition permits it.
/// @dev Requires the `ROOT_PERMISSION_ID` permission
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) receiving the permission.
/// @param _permissionId The permission identifier.
/// @param _condition The `PermissionCondition` that will be asked for authorization on calls connected to the specified permission identifier.
/// @dev Note, that granting permissions with `_who` or `_where` equal to `ANY_ADDR` does not replace other permissions with specific `_who` and `_where` addresses that exist in parallel.
function grantWithCondition(
address _where,
address _who,
bytes32 _permissionId,
IPermissionCondition _condition
) external virtual auth(ROOT_PERMISSION_ID) {
_grantWithCondition({
_where: _where,
_who: _who,
_permissionId: _permissionId,
_condition: _condition
});
}
/// @notice Revokes permission from an address to call methods in a target contract guarded by an auth modifier with the specified permission identifier.
/// @dev Requires the `ROOT_PERMISSION_ID` permission.
/// @param _where The address of the target contract for which `_who` loses permission.
/// @param _who The address (EOA or contract) losing the permission.
/// @param _permissionId The permission identifier.
/// @dev Note, that revoking permissions with `_who` or `_where` equal to `ANY_ADDR` does not revoke other permissions with specific `_who` and `_where` addresses that exist in parallel.
function revoke(
address _where,
address _who,
bytes32 _permissionId
) external virtual auth(ROOT_PERMISSION_ID) {
_revoke({_where: _where, _who: _who, _permissionId: _permissionId});
}
/// @notice Applies an array of permission operations on a single target contracts `_where`.
/// @param _where The address of the single target contract.
/// @param items The array of single-targeted permission operations to apply.
function applySingleTargetPermissions(
address _where,
PermissionLib.SingleTargetPermission[] calldata items
) external virtual auth(ROOT_PERMISSION_ID) {
for (uint256 i; i < items.length; ) {
PermissionLib.SingleTargetPermission memory item = items[i];
if (item.operation == PermissionLib.Operation.Grant) {
_grant({_where: _where, _who: item.who, _permissionId: item.permissionId});
} else if (item.operation == PermissionLib.Operation.Revoke) {
_revoke({_where: _where, _who: item.who, _permissionId: item.permissionId});
} else if (item.operation == PermissionLib.Operation.GrantWithCondition) {
revert GrantWithConditionNotSupported();
}
unchecked {
++i;
}
}
}
/// @notice Applies an array of permission operations on multiple target contracts `items[i].where`.
/// @param _items The array of multi-targeted permission operations to apply.
function applyMultiTargetPermissions(
PermissionLib.MultiTargetPermission[] calldata _items
) external virtual auth(ROOT_PERMISSION_ID) {
for (uint256 i; i < _items.length; ) {
PermissionLib.MultiTargetPermission memory item = _items[i];
if (item.operation == PermissionLib.Operation.Grant) {
_grant({_where: item.where, _who: item.who, _permissionId: item.permissionId});
} else if (item.operation == PermissionLib.Operation.Revoke) {
_revoke({_where: item.where, _who: item.who, _permissionId: item.permissionId});
} else if (item.operation == PermissionLib.Operation.GrantWithCondition) {
_grantWithCondition({
_where: item.where,
_who: item.who,
_permissionId: item.permissionId,
_condition: IPermissionCondition(item.condition)
});
}
unchecked {
++i;
}
}
}
/// @notice Checks if the caller address has permission on the target contract via a permission identifier and relays the answer to a condition contract if this was declared during the granting process.
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) for which the permission is checked.
/// @param _permissionId The permission identifier.
/// @param _data Optional data to be passed to the set `PermissionCondition`.
/// @return Returns true if `_who` has the permissions on the target contract via the specified permission identifier.
function isGranted(
address _where,
address _who,
bytes32 _permissionId,
bytes memory _data
) public view virtual returns (bool) {
// Specific caller (`_who`) and target (`_where`) permission check
{
// This permission may have been granted directly via the `grant` function or with a condition via the `grantWithCondition` function.
address specificCallerTargetPermission = permissionsHashed[
permissionHash({_where: _where, _who: _who, _permissionId: _permissionId})
];
// If the permission was granted directly, return `true`.
if (specificCallerTargetPermission == ALLOW_FLAG) return true;
// If the permission was granted with a condition, check the condition and return the result.
if (specificCallerTargetPermission != UNSET_FLAG) {
return
_checkCondition({
_condition: specificCallerTargetPermission,
_where: _where,
_who: _who,
_permissionId: _permissionId,
_data: _data
});
}
// If this permission is not set, continue.
}
// Generic caller (`_who: ANY_ADDR`) condition check
{
// This permission can only be granted in conjunction with a condition via the `grantWithCondition` function.
address genericCallerPermission = permissionsHashed[
permissionHash({_where: _where, _who: ANY_ADDR, _permissionId: _permissionId})
];
// If the permission was granted with a condition, check the condition and return the result.
if (genericCallerPermission != UNSET_FLAG) {
return
_checkCondition({
_condition: genericCallerPermission,
_where: _where,
_who: _who,
_permissionId: _permissionId,
_data: _data
});
}
// If this permission is not set, continue.
}
// Generic target (`_where: ANY_ADDR`) condition check
{
// This permission can only be granted in conjunction with a condition via the `grantWithCondition` function.
address genericTargetPermission = permissionsHashed[
permissionHash({_where: ANY_ADDR, _who: _who, _permissionId: _permissionId})
];
// If the permission was granted with a condition, check the condition and return the result.
if (genericTargetPermission != UNSET_FLAG) {
return
_checkCondition({
_condition: genericTargetPermission,
_where: _where,
_who: _who,
_permissionId: _permissionId,
_data: _data
});
}
// If this permission is not set, continue.
}
// No specific or generic permission applies to the `_who`, `_where`, `_permissionId`, so we return `false`.
return false;
}
/// @notice Relays the question if caller address has permission on target contract via a permission identifier to a condition contract.
/// @notice Checks a condition contract by doing an external call via try/catch.
/// @param _condition The condition contract that is called.
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) owning the permission.
/// @param _permissionId The permission identifier.
/// @param _data Optional data to be passed to a referenced `PermissionCondition`.
/// @return Returns `true` if a caller (`_who`) has the permissions on the contract (`_where`) via the specified permission identifier.
/// @dev If the external call fails, we return `false`.
function _checkCondition(
address _condition,
address _where,
address _who,
bytes32 _permissionId,
bytes memory _data
) internal view virtual returns (bool) {
// Try-catch to skip failures
try
IPermissionCondition(_condition).isGranted({
_where: _where,
_who: _who,
_permissionId: _permissionId,
_data: _data
})
returns (bool result) {
if (result) {
return true;
}
} catch {}
return false;
}
/// @notice Grants the `ROOT_PERMISSION_ID` permission to the initial owner during initialization of the permission manager.
/// @param _initialOwner The initial owner of the permission manager.
function _initializePermissionManager(address _initialOwner) internal {
_grant({_where: address(this), _who: _initialOwner, _permissionId: ROOT_PERMISSION_ID});
}
/// @notice This method is used in the external `grant` method of the permission manager.
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) owning the permission.
/// @param _permissionId The permission identifier.
/// @dev Note, that granting permissions with `_who` or `_where` equal to `ANY_ADDR` does not replace other permissions with specific `_who` and `_where` addresses that exist in parallel.
function _grant(address _where, address _who, bytes32 _permissionId) internal virtual {
if (_where == ANY_ADDR || _who == ANY_ADDR) {
revert PermissionsForAnyAddressDisallowed();
}
bytes32 permHash = permissionHash({
_where: _where,
_who: _who,
_permissionId: _permissionId
});
address currentFlag = permissionsHashed[permHash];
// Means permHash is not currently set.
if (currentFlag == UNSET_FLAG) {
permissionsHashed[permHash] = ALLOW_FLAG;
emit Granted({
permissionId: _permissionId,
here: msg.sender,
where: _where,
who: _who,
condition: ALLOW_FLAG
});
}
}
/// @notice This method is used in the external `grantWithCondition` method of the permission manager.
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) owning the permission.
/// @param _permissionId The permission identifier.
/// @param _condition An address either resolving to a `PermissionCondition` contract address or being the `ALLOW_FLAG` address (`address(2)`).
/// @dev Note, that granting permissions with `_who` or `_where` equal to `ANY_ADDR` does not replace other permissions with specific `_who` and `_where` addresses that exist in parallel.
function _grantWithCondition(
address _where,
address _who,
bytes32 _permissionId,
IPermissionCondition _condition
) internal virtual {
address conditionAddr = address(_condition);
if (!conditionAddr.isContract()) {
revert ConditionNotAContract(_condition);
}
if (
!PermissionCondition(conditionAddr).supportsInterface(
type(IPermissionCondition).interfaceId
)
) {
revert ConditionInterfacNotSupported(_condition);
}
if (_where == ANY_ADDR && _who == ANY_ADDR) {
revert AnyAddressDisallowedForWhoAndWhere();
}
if (_where == ANY_ADDR || _who == ANY_ADDR) {
if (
_permissionId == ROOT_PERMISSION_ID ||
isPermissionRestrictedForAnyAddr(_permissionId)
) {
revert PermissionsForAnyAddressDisallowed();
}
}
bytes32 permHash = permissionHash({
_where: _where,
_who: _who,
_permissionId: _permissionId
});
address currentCondition = permissionsHashed[permHash];
// Means permHash is not currently set.
if (currentCondition == UNSET_FLAG) {
permissionsHashed[permHash] = conditionAddr;
emit Granted({
permissionId: _permissionId,
here: msg.sender,
where: _where,
who: _who,
condition: conditionAddr
});
} else if (currentCondition != conditionAddr) {
// Revert if `permHash` is already granted, but uses a different condition.
// If we don't revert, we either should:
// - allow overriding the condition on the same permission
// which could be confusing whoever granted the same permission first
// - or do nothing and succeed silently which could be confusing for the caller.
revert PermissionAlreadyGrantedForDifferentCondition({
where: _where,
who: _who,
permissionId: _permissionId,
currentCondition: currentCondition,
newCondition: conditionAddr
});
}
}
/// @notice This method is used in the public `revoke` method of the permission manager.
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) owning the permission.
/// @param _permissionId The permission identifier.
/// @dev Note, that revoking permissions with `_who` or `_where` equal to `ANY_ADDR` does not revoke other permissions with specific `_who` and `_where` addresses that might have been granted in parallel.
function _revoke(address _where, address _who, bytes32 _permissionId) internal virtual {
bytes32 permHash = permissionHash({
_where: _where,
_who: _who,
_permissionId: _permissionId
});
if (permissionsHashed[permHash] != UNSET_FLAG) {
permissionsHashed[permHash] = UNSET_FLAG;
emit Revoked({permissionId: _permissionId, here: msg.sender, where: _where, who: _who});
}
}
/// @notice A private function to be used to check permissions on the permission manager contract (`address(this)`) itself.
/// @param _permissionId The permission identifier required to call the method this modifier is applied to.
function _auth(bytes32 _permissionId) internal view virtual {
if (!isGranted(address(this), msg.sender, _permissionId, msg.data)) {
revert Unauthorized({
where: address(this),
who: msg.sender,
permissionId: _permissionId
});
}
}
/// @notice Generates the hash for the `permissionsHashed` mapping obtained from the word "PERMISSION", the contract address, the address owning the permission, and the permission identifier.
/// @param _where The address of the target contract for which `_who` receives permission.
/// @param _who The address (EOA or contract) owning the permission.
/// @param _permissionId The permission identifier.
/// @return The permission hash.
function permissionHash(
address _where,
address _who,
bytes32 _permissionId
) internal pure virtual returns (bytes32) {
return keccak256(abi.encodePacked("PERMISSION", _who, _where, _permissionId));
}
/// @notice Decides if the granting permissionId is restricted when `_who == ANY_ADDR` or `_where == ANY_ADDR`.
/// @param _permissionId The permission identifier.
/// @return Whether or not the permission is restricted.
/// @dev By default, every permission is unrestricted and it is the derived contract's responsibility to override it. Note, that the `ROOT_PERMISSION_ID` is included and not required to be set it again.
function isPermissionRestrictedForAnyAddr(
bytes32 _permissionId
) internal view virtual returns (bool) {
(_permissionId); // silence the warning.
return false;
}
/// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)).
uint256[49] private __gap;
}