|
@@ -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();
|
|
|
}
|