|
|
|
@ -7,6 +7,7 @@ import {IAccessManaged} from "./IAccessManaged.sol"; |
|
|
|
|
import {Address} from "../../utils/Address.sol"; |
|
|
|
|
import {Context} from "../../utils/Context.sol"; |
|
|
|
|
import {Multicall} from "../../utils/Multicall.sol"; |
|
|
|
|
import {Math} from "../../utils/math/Math.sol"; |
|
|
|
|
import {Time} from "../../utils/types/Time.sol"; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
@ -95,20 +96,11 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
bytes32 private _relayIdentifier; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @dev Check that the caller has a given permission level (`groupId`). Note that this does NOT consider execution |
|
|
|
|
* delays that may be associated to that group. |
|
|
|
|
* @dev Check that the caller is authorized to perform the operation, following the restrictions encoded in |
|
|
|
|
* {_getAdminRestrictions}. |
|
|
|
|
*/ |
|
|
|
|
modifier onlyGroup(uint64 groupId) { |
|
|
|
|
_checkGroup(groupId); |
|
|
|
|
_; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @dev Check that the caller is an admin and that the top-level function currently executing has been scheduled |
|
|
|
|
* sufficiently ahead of time, if necessary according to admin delays. |
|
|
|
|
*/ |
|
|
|
|
modifier withFamilyDelay(uint64 familyId) { |
|
|
|
|
_checkFamilyDelay(familyId); |
|
|
|
|
modifier onlyAuthorized() { |
|
|
|
|
_checkAuthorized(); |
|
|
|
|
_; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -145,7 +137,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
} else { |
|
|
|
|
uint64 groupId = getFamilyFunctionGroup(familyId, selector); |
|
|
|
|
(bool inGroup, uint32 currentDelay) = hasGroup(groupId, caller); |
|
|
|
|
return (inGroup && currentDelay == 0, currentDelay); |
|
|
|
|
return inGroup ? (currentDelay == 0, currentDelay) : (false, 0); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -250,7 +242,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {GroupLabel} event. |
|
|
|
|
*/ |
|
|
|
|
function labelGroup(uint64 groupId, string calldata label) public virtual onlyGroup(ADMIN_GROUP) { |
|
|
|
|
function labelGroup(uint64 groupId, string calldata label) public virtual onlyAuthorized { |
|
|
|
|
if (groupId == ADMIN_GROUP || groupId == PUBLIC_GROUP) { |
|
|
|
|
revert AccessManagerLockedGroup(groupId); |
|
|
|
|
} |
|
|
|
@ -270,11 +262,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {GroupGranted} event |
|
|
|
|
*/ |
|
|
|
|
function grantGroup( |
|
|
|
|
uint64 groupId, |
|
|
|
|
address account, |
|
|
|
|
uint32 executionDelay |
|
|
|
|
) public virtual onlyGroup(getGroupAdmin(groupId)) { |
|
|
|
|
function grantGroup(uint64 groupId, address account, uint32 executionDelay) public virtual onlyAuthorized { |
|
|
|
|
_grantGroup(groupId, account, getGroupGrantDelay(groupId), executionDelay); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -287,7 +275,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {GroupRevoked} event |
|
|
|
|
*/ |
|
|
|
|
function revokeGroup(uint64 groupId, address account) public virtual onlyGroup(getGroupAdmin(groupId)) { |
|
|
|
|
function revokeGroup(uint64 groupId, address account) public virtual onlyAuthorized { |
|
|
|
|
_revokeGroup(groupId, account); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -319,11 +307,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {GroupExecutionDelayUpdated} event |
|
|
|
|
*/ |
|
|
|
|
function setExecuteDelay( |
|
|
|
|
uint64 groupId, |
|
|
|
|
address account, |
|
|
|
|
uint32 newDelay |
|
|
|
|
) public virtual onlyGroup(getGroupAdmin(groupId)) { |
|
|
|
|
function setExecuteDelay(uint64 groupId, address account, uint32 newDelay) public virtual onlyAuthorized { |
|
|
|
|
_setExecuteDelay(groupId, account, newDelay); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -336,7 +320,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {GroupAdminChanged} event |
|
|
|
|
*/ |
|
|
|
|
function setGroupAdmin(uint64 groupId, uint64 admin) public virtual onlyGroup(ADMIN_GROUP) { |
|
|
|
|
function setGroupAdmin(uint64 groupId, uint64 admin) public virtual onlyAuthorized { |
|
|
|
|
_setGroupAdmin(groupId, admin); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -349,7 +333,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {GroupGuardianChanged} event |
|
|
|
|
*/ |
|
|
|
|
function setGroupGuardian(uint64 groupId, uint64 guardian) public virtual onlyGroup(ADMIN_GROUP) { |
|
|
|
|
function setGroupGuardian(uint64 groupId, uint64 guardian) public virtual onlyAuthorized { |
|
|
|
|
_setGroupGuardian(groupId, guardian); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -362,7 +346,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {GroupGrantDelayChanged} event |
|
|
|
|
*/ |
|
|
|
|
function setGrantDelay(uint64 groupId, uint32 newDelay) public virtual onlyGroup(ADMIN_GROUP) { |
|
|
|
|
function setGrantDelay(uint64 groupId, uint32 newDelay) public virtual onlyAuthorized { |
|
|
|
|
_setGrantDelay(groupId, newDelay); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -482,7 +466,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
uint64 familyId, |
|
|
|
|
bytes4[] calldata selectors, |
|
|
|
|
uint64 groupId |
|
|
|
|
) public virtual onlyGroup(ADMIN_GROUP) withFamilyDelay(familyId) { |
|
|
|
|
) public virtual onlyAuthorized { |
|
|
|
|
for (uint256 i = 0; i < selectors.length; ++i) { |
|
|
|
|
_setFamilyFunctionGroup(familyId, selectors[i], groupId); |
|
|
|
|
} |
|
|
|
@ -508,7 +492,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {FunctionAllowedGroupUpdated} event per selector |
|
|
|
|
*/ |
|
|
|
|
function setFamilyAdminDelay(uint64 familyId, uint32 newDelay) public virtual onlyGroup(ADMIN_GROUP) { |
|
|
|
|
function setFamilyAdminDelay(uint64 familyId, uint32 newDelay) public virtual onlyAuthorized { |
|
|
|
|
_setFamilyAdminDelay(familyId, newDelay); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -544,10 +528,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {ContractFamilyUpdated} event. |
|
|
|
|
*/ |
|
|
|
|
function setContractFamily( |
|
|
|
|
address target, |
|
|
|
|
uint64 familyId |
|
|
|
|
) public virtual onlyGroup(ADMIN_GROUP) withFamilyDelay(_getContractFamilyId(target)) { |
|
|
|
|
function setContractFamily(address target, uint64 familyId) public virtual onlyAuthorized { |
|
|
|
|
_setContractFamily(target, familyId); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -570,7 +551,7 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits a {ContractClosed} event. |
|
|
|
|
*/ |
|
|
|
|
function setContractClosed(address target, bool closed) public virtual onlyGroup(ADMIN_GROUP) { |
|
|
|
|
function setContractClosed(address target, bool closed) public virtual onlyAuthorized { |
|
|
|
|
_setContractClosed(target, closed); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -636,6 +617,9 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* Emits an {OperationExecuted} event only if the call was scheduled and delayed. |
|
|
|
|
*/ |
|
|
|
|
// Reentrancy is not an issue because permissions are checked on msg.sender. Additionally, |
|
|
|
|
// _consumeScheduledOp guarantees a scheduled operation is only executed once. |
|
|
|
|
// slither-disable-next-line reentrancy-no-eth |
|
|
|
|
function relay(address target, bytes calldata data) public payable virtual { |
|
|
|
|
address caller = _msgSender(); |
|
|
|
|
|
|
|
|
@ -716,11 +700,9 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
revert AccessManagerNotScheduled(operationId); |
|
|
|
|
} else if (caller != msgsender) { |
|
|
|
|
// calls can only be canceled by the account that scheduled them, a global admin, or by a guardian of the required group. |
|
|
|
|
(uint64 familyId, ) = getContractFamily(target); |
|
|
|
|
(bool isAdmin, ) = hasGroup(ADMIN_GROUP, msgsender); |
|
|
|
|
(bool isGuardian, ) = hasGroup( |
|
|
|
|
getGroupGuardian(getFamilyFunctionGroup(_getContractFamilyId(target), selector)), |
|
|
|
|
msgsender |
|
|
|
|
); |
|
|
|
|
(bool isGuardian, ) = hasGroup(getGroupGuardian(getFamilyFunctionGroup(familyId, selector)), msgsender); |
|
|
|
|
if (!isAdmin && !isGuardian) { |
|
|
|
|
revert AccessManagerCannotCancel(msgsender, caller, target, selector); |
|
|
|
|
} |
|
|
|
@ -753,56 +735,96 @@ contract AccessManager is Context, Multicall, IAccessManager { |
|
|
|
|
* |
|
|
|
|
* - the caller must be a global admin |
|
|
|
|
*/ |
|
|
|
|
function updateAuthority( |
|
|
|
|
address target, |
|
|
|
|
address newAuthority |
|
|
|
|
) public virtual onlyGroup(ADMIN_GROUP) withFamilyDelay(_getContractFamilyId(target)) { |
|
|
|
|
function updateAuthority(address target, address newAuthority) public virtual onlyAuthorized { |
|
|
|
|
IAccessManaged(target).setAuthority(newAuthority); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// =================================================== HELPERS ==================================================== |
|
|
|
|
function _checkGroup(uint64 groupId) internal view virtual { |
|
|
|
|
address account = _msgSender(); |
|
|
|
|
(bool inGroup, ) = hasGroup(groupId, account); |
|
|
|
|
if (!inGroup) { |
|
|
|
|
revert AccessManagerUnauthorizedAccount(account, groupId); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function _checkFamilyDelay(uint64 familyId) internal virtual { |
|
|
|
|
uint32 delay = getFamilyAdminDelay(familyId); |
|
|
|
|
if (delay > 0) { |
|
|
|
|
_consumeScheduledOp(_hashOperation(_msgSender(), address(this), _msgData())); |
|
|
|
|
// ================================================= ADMIN LOGIC ================================================== |
|
|
|
|
/** |
|
|
|
|
* @dev Check if the current call is authorized according to admin logic. |
|
|
|
|
*/ |
|
|
|
|
function _checkAuthorized() private { |
|
|
|
|
address caller = _msgSender(); |
|
|
|
|
(bool allowed, uint32 delay) = _canCallExtended(caller, address(this), _msgData()); |
|
|
|
|
if (!allowed) { |
|
|
|
|
if (delay == 0) { |
|
|
|
|
(, uint64 requiredGroup, ) = _getAdminRestrictions(_msgData()); |
|
|
|
|
revert AccessManagerUnauthorizedAccount(caller, requiredGroup); |
|
|
|
|
} else { |
|
|
|
|
_consumeScheduledOp(_hashOperation(caller, address(this), _msgData())); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function _getContractFamilyId(address target) private view returns (uint64 familyId) { |
|
|
|
|
(familyId, ) = getContractFamily(target); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function _parseFamilyOperation(bytes calldata data) private view returns (bool, uint64) { |
|
|
|
|
/** |
|
|
|
|
* @dev Get the admin restrictions of a given function call based on the function and arguments involved. |
|
|
|
|
*/ |
|
|
|
|
function _getAdminRestrictions(bytes calldata data) private view returns (bool, uint64, uint32) { |
|
|
|
|
bytes4 selector = bytes4(data); |
|
|
|
|
|
|
|
|
|
if (selector == this.updateAuthority.selector || selector == this.setContractFamily.selector) { |
|
|
|
|
return (true, _getContractFamilyId(abi.decode(data[0x04:0x24], (address)))); |
|
|
|
|
// First argument is a target. Restricted to ADMIN with the family delay corresponding to the target's family |
|
|
|
|
address target = abi.decode(data[0x04:0x24], (address)); |
|
|
|
|
(uint64 familyId, ) = getContractFamily(target); |
|
|
|
|
uint32 delay = getFamilyAdminDelay(familyId); |
|
|
|
|
return (true, ADMIN_GROUP, delay); |
|
|
|
|
} else if (selector == this.setFamilyFunctionGroup.selector) { |
|
|
|
|
return (true, abi.decode(data[0x04:0x24], (uint64))); |
|
|
|
|
// First argument is a family. Restricted to ADMIN with the family delay corresponding to the family |
|
|
|
|
uint64 familyId = abi.decode(data[0x04:0x24], (uint64)); |
|
|
|
|
uint32 delay = getFamilyAdminDelay(familyId); |
|
|
|
|
return (true, ADMIN_GROUP, delay); |
|
|
|
|
} else if ( |
|
|
|
|
selector == this.labelGroup.selector || |
|
|
|
|
selector == this.setGroupAdmin.selector || |
|
|
|
|
selector == this.setGroupGuardian.selector || |
|
|
|
|
selector == this.setGrantDelay.selector || |
|
|
|
|
selector == this.setFamilyAdminDelay.selector || |
|
|
|
|
selector == this.setContractClosed.selector |
|
|
|
|
) { |
|
|
|
|
// Restricted to ADMIN with no delay beside any execution delay the caller may have |
|
|
|
|
return (true, ADMIN_GROUP, 0); |
|
|
|
|
} else if ( |
|
|
|
|
selector == this.grantGroup.selector || |
|
|
|
|
selector == this.revokeGroup.selector || |
|
|
|
|
selector == this.setExecuteDelay.selector |
|
|
|
|
) { |
|
|
|
|
// First argument is a groupId. Restricted to that group's admin with no delay beside any execution delay the caller may have. |
|
|
|
|
uint64 groupId = abi.decode(data[0x04:0x24], (uint64)); |
|
|
|
|
uint64 groupAdminId = getGroupAdmin(groupId); |
|
|
|
|
return (true, groupAdminId, 0); |
|
|
|
|
} else { |
|
|
|
|
return (false, 0); |
|
|
|
|
return (false, 0, 0); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// =================================================== HELPERS ==================================================== |
|
|
|
|
/** |
|
|
|
|
* @dev An extended version of {canCall} for internal use that considers restrictions for admin functions. |
|
|
|
|
*/ |
|
|
|
|
function _canCallExtended(address caller, address target, bytes calldata data) private view returns (bool, uint32) { |
|
|
|
|
if (target == address(this)) { |
|
|
|
|
(bool isFamilyOperation, uint64 familyId) = _parseFamilyOperation(data); |
|
|
|
|
uint32 delay = getFamilyAdminDelay(familyId); |
|
|
|
|
(bool inGroup, ) = hasGroup(ADMIN_GROUP, caller); |
|
|
|
|
return (inGroup && isFamilyOperation && delay == 0, delay); |
|
|
|
|
(bool enabled, uint64 groupId, uint32 operationDelay) = _getAdminRestrictions(data); |
|
|
|
|
if (!enabled) { |
|
|
|
|
return (false, 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
(bool inGroup, uint32 executionDelay) = hasGroup(groupId, caller); |
|
|
|
|
if (!inGroup) { |
|
|
|
|
return (false, 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// downcast is safe because both options are uint32 |
|
|
|
|
uint32 delay = uint32(Math.max(operationDelay, executionDelay)); |
|
|
|
|
return (delay == 0, delay); |
|
|
|
|
} else { |
|
|
|
|
bytes4 selector = bytes4(data); |
|
|
|
|
return canCall(caller, target, selector); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @dev Returns true if a schedule timepoint is past its expiration deadline. |
|
|
|
|
*/ |
|
|
|
|
function _isExpired(uint48 timepoint) private view returns (bool) { |
|
|
|
|
return timepoint + expiration() <= Time.timestamp(); |
|
|
|
|
} |
|
|
|
|