Access Manager (#4416)
Co-authored-by: Ernesto García <ernestognw@gmail.com> Co-authored-by: Francisco Giordano <fg@frang.io>
This commit is contained in:
@ -1,9 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import "../../utils/Context.sol";
|
||||
import "./IAuthority.sol";
|
||||
import {IAuthority} from "./IAuthority.sol";
|
||||
import {AuthorityUtils} from "./AuthorityUtils.sol";
|
||||
import {IAccessManager} from "./IAccessManager.sol";
|
||||
import {IAccessManaged} from "./IAccessManaged.sol";
|
||||
import {Context} from "../../utils/Context.sol";
|
||||
|
||||
/**
|
||||
* @dev This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be
|
||||
@ -13,10 +16,17 @@ import "./IAuthority.sol";
|
||||
* IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public`
|
||||
* functions, and ideally only used in `external` functions. See {restricted}.
|
||||
*/
|
||||
contract AccessManaged is Context {
|
||||
event AuthorityUpdated(address indexed sender, IAuthority indexed newAuthority);
|
||||
abstract contract AccessManaged is Context, IAccessManaged {
|
||||
address private _authority;
|
||||
|
||||
IAuthority private _authority;
|
||||
bool private _consumingSchedule;
|
||||
|
||||
/**
|
||||
* @dev Initializes the contract connected to an initial authority.
|
||||
*/
|
||||
constructor(address initialAuthority) {
|
||||
_setAuthority(initialAuthority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Restricts access to a function as defined by the connected Authority for this contract and the
|
||||
@ -24,9 +34,9 @@ contract AccessManaged is Context {
|
||||
*
|
||||
* [IMPORTANT]
|
||||
* ====
|
||||
* In general, this modifier should only be used on `external` functions. It is okay to use it on `public` functions
|
||||
* that are used as external entry points and are not called internally. Unless you know what you're doing, it
|
||||
* should never be used on `internal` functions. Failure to follow these rules can have critical security
|
||||
* In general, this modifier should only be used on `external` functions. It is okay to use it on `public`
|
||||
* functions that are used as external entry points and are not called internally. Unless you know what you're
|
||||
* doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security
|
||||
* implications! This is because the permissions are determined by the function that entered the contract, i.e. the
|
||||
* function at the bottom of the call stack, and not the function where the modifier is visible in the source code.
|
||||
* ====
|
||||
@ -35,53 +45,76 @@ contract AccessManaged is Context {
|
||||
* ====
|
||||
* Selector collisions are mitigated by scoping permissions per contract, but some edge cases must be considered:
|
||||
*
|
||||
* * If the https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function[`receive()`] function is restricted,
|
||||
* any other function with a `0x00000000` selector will share permissions with `receive()`.
|
||||
* * Similarly, if there's no `receive()` function but a `fallback()` instead, the fallback might be called with empty `calldata`,
|
||||
* sharing the `0x00000000` selector permissions as well.
|
||||
* * For any other selector, if the restricted function is set on an upgradeable contract, an upgrade may remove the restricted
|
||||
* function and replace it with a new method whose selector replaces the last one, keeping the previous permissions.
|
||||
* * If the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`] function
|
||||
* is restricted, any other function with a `0x00000000` selector will share permissions with `receive()`.
|
||||
* * Similarly, if there's no `receive()` function but a `fallback()` instead, the fallback might be called with
|
||||
* empty `calldata`, sharing the `0x00000000` selector permissions as well.
|
||||
* * For any other selector, if the restricted function is set on an upgradeable contract, an upgrade may remove
|
||||
* the restricted function and replace it with a new method whose selector replaces the last one, keeping the
|
||||
* previous permissions.
|
||||
* ====
|
||||
*/
|
||||
modifier restricted() {
|
||||
_checkCanCall(_msgSender(), msg.sig);
|
||||
_checkCanCall(_msgSender(), _msgData());
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Initializes the contract connected to an initial authority.
|
||||
*/
|
||||
constructor(IAuthority initialAuthority) {
|
||||
_setAuthority(initialAuthority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the current authority.
|
||||
*/
|
||||
function authority() public view virtual returns (IAuthority) {
|
||||
function authority() public view virtual returns (address) {
|
||||
return _authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfers control to a new authority. The caller must be the current authority.
|
||||
*/
|
||||
function setAuthority(IAuthority newAuthority) public virtual {
|
||||
require(_msgSender() == address(_authority), "AccessManaged: not current authority");
|
||||
function setAuthority(address newAuthority) public virtual {
|
||||
address caller = _msgSender();
|
||||
if (caller != authority()) {
|
||||
revert AccessManagedUnauthorized(caller);
|
||||
}
|
||||
if (newAuthority.code.length == 0) {
|
||||
revert AccessManagedInvalidAuthority(newAuthority);
|
||||
}
|
||||
_setAuthority(newAuthority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true only in the context of a delayed restricted call, at the moment that the scheduled operation is
|
||||
* being consumed. Prevents denial of service for delayed restricted calls in the case that the contract performs
|
||||
* attacker controlled calls.
|
||||
*/
|
||||
function isConsumingScheduledOp() public view returns (bool) {
|
||||
return _consumingSchedule;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfers control to a new authority. Internal function with no access restriction.
|
||||
*/
|
||||
function _setAuthority(IAuthority newAuthority) internal virtual {
|
||||
function _setAuthority(address newAuthority) internal virtual {
|
||||
_authority = newAuthority;
|
||||
emit AuthorityUpdated(_msgSender(), newAuthority);
|
||||
emit AuthorityUpdated(newAuthority);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Reverts if the caller is not allowed to call the function identified by a selector.
|
||||
*/
|
||||
function _checkCanCall(address caller, bytes4 selector) internal view virtual {
|
||||
require(_authority.canCall(caller, address(this), selector), "AccessManaged: authority rejected");
|
||||
function _checkCanCall(address caller, bytes calldata data) internal virtual {
|
||||
(bool allowed, uint32 delay) = AuthorityUtils.canCallWithDelay(
|
||||
authority(),
|
||||
caller,
|
||||
address(this),
|
||||
bytes4(data)
|
||||
);
|
||||
if (!allowed) {
|
||||
if (delay > 0) {
|
||||
_consumingSchedule = true;
|
||||
IAccessManager(authority()).consumeScheduledOp(caller, data);
|
||||
_consumingSchedule = false;
|
||||
} else {
|
||||
revert AccessManagedUnauthorized(caller);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,54 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "./AccessManager.sol";
|
||||
import "./AccessManaged.sol";
|
||||
|
||||
/**
|
||||
* @dev This contract can be used to migrate existing {Ownable} or {AccessControl} contracts into an {AccessManager}
|
||||
* system.
|
||||
*
|
||||
* Ownable contracts can have their ownership transferred to an instance of this adapter. AccessControl contracts can
|
||||
* grant all roles to the adapter, while ideally revoking them from all other accounts. Subsequently, the permissions
|
||||
* for those contracts can be managed centrally and with function granularity in the {AccessManager} instance the
|
||||
* adapter is connected to.
|
||||
*
|
||||
* Permissioned interactions with thus migrated contracts must go through the adapter's {relay} function and will
|
||||
* proceed if the function is allowed for the caller in the AccessManager instance.
|
||||
*/
|
||||
contract AccessManagerAdapter is AccessManaged {
|
||||
bytes32 private constant _DEFAULT_ADMIN_ROLE = 0;
|
||||
|
||||
/**
|
||||
* @dev Initializes an adapter connected to an AccessManager instance.
|
||||
*/
|
||||
constructor(AccessManager manager) AccessManaged(manager) {}
|
||||
|
||||
/**
|
||||
* @dev Relays a function call to the target contract. The call will be relayed if the AccessManager allows the
|
||||
* caller access to this function in the target contract, i.e. if the caller is in a team that is allowed for the
|
||||
* function, or if the caller is the default admin for the AccessManager. The latter is meant to be used for
|
||||
* ad hoc operations such as asset recovery.
|
||||
*/
|
||||
function relay(address target, bytes memory data) external payable {
|
||||
bytes4 sig = bytes4(data);
|
||||
AccessManager manager = AccessManager(address(authority()));
|
||||
require(
|
||||
manager.canCall(msg.sender, target, sig) || manager.hasRole(_DEFAULT_ADMIN_ROLE, msg.sender),
|
||||
"AccessManagerAdapter: caller not allowed"
|
||||
);
|
||||
(bool ok, bytes memory result) = target.call{value: msg.value}(data);
|
||||
assembly {
|
||||
let result_pointer := add(32, result)
|
||||
let result_size := mload(result)
|
||||
switch ok
|
||||
case true {
|
||||
return(result_pointer, result_size)
|
||||
}
|
||||
default {
|
||||
revert(result_pointer, result_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
contracts/access/manager/AuthorityUtils.sol
Normal file
31
contracts/access/manager/AuthorityUtils.sol
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IAuthority} from "./IAuthority.sol";
|
||||
|
||||
library AuthorityUtils {
|
||||
/**
|
||||
* @dev Since `AccessManager` implements an extended IAuthority interface, invoking `canCall` with backwards compatibility
|
||||
* for the preexisting `IAuthority` interface requires special care to avoid reverting on insufficient return data.
|
||||
* This helper function takes care of invoking `canCall` in a backwards compatible way without reverting.
|
||||
*/
|
||||
function canCallWithDelay(
|
||||
address authority,
|
||||
address caller,
|
||||
address target,
|
||||
bytes4 selector
|
||||
) internal view returns (bool allowed, uint32 delay) {
|
||||
(bool success, bytes memory data) = authority.staticcall(
|
||||
abi.encodeCall(IAuthority.canCall, (caller, target, selector))
|
||||
);
|
||||
if (success) {
|
||||
if (data.length >= 0x40) {
|
||||
(allowed, delay) = abi.decode(data, (bool, uint32));
|
||||
} else if (data.length >= 0x20) {
|
||||
allowed = abi.decode(data, (bool));
|
||||
}
|
||||
}
|
||||
return (allowed, delay);
|
||||
}
|
||||
}
|
||||
17
contracts/access/manager/IAccessManaged.sol
Normal file
17
contracts/access/manager/IAccessManaged.sol
Normal file
@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
interface IAccessManaged {
|
||||
event AuthorityUpdated(address authority);
|
||||
|
||||
error AccessManagedUnauthorized(address caller);
|
||||
error AccessManagedRequiredDelay(address caller, uint32 delay);
|
||||
error AccessManagedInvalidAuthority(address authority);
|
||||
|
||||
function authority() external view returns (address);
|
||||
|
||||
function setAuthority(address) external;
|
||||
|
||||
function isConsumingScheduledOp() external view returns (bool);
|
||||
}
|
||||
110
contracts/access/manager/IAccessManager.sol
Normal file
110
contracts/access/manager/IAccessManager.sol
Normal file
@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {IAccessManaged} from "./IAccessManaged.sol";
|
||||
import {Time} from "../../utils/types/Time.sol";
|
||||
|
||||
interface IAccessManager {
|
||||
/**
|
||||
* @dev A delayed operation was scheduled.
|
||||
*/
|
||||
event OperationScheduled(bytes32 indexed operationId, uint48 schedule, address caller, address target, bytes data);
|
||||
|
||||
/**
|
||||
* @dev A scheduled operation was executed.
|
||||
*/
|
||||
event OperationExecuted(bytes32 indexed operationId, uint48 schedule);
|
||||
|
||||
/**
|
||||
* @dev A scheduled operation was canceled.
|
||||
*/
|
||||
event OperationCanceled(bytes32 indexed operationId, uint48 schedule);
|
||||
|
||||
event GroupLabel(uint64 indexed groupId, string label);
|
||||
event GroupGranted(uint64 indexed groupId, address indexed account, uint48 since, uint32 delay);
|
||||
event GroupRevoked(uint64 indexed groupId, address indexed account);
|
||||
event GroupExecutionDelayUpdated(uint64 indexed groupId, address indexed account, uint32 delay, uint48 from);
|
||||
event GroupAdminChanged(uint64 indexed groupId, uint64 indexed admin);
|
||||
event GroupGuardianChanged(uint64 indexed groupId, uint64 indexed guardian);
|
||||
event GroupGrantDelayChanged(uint64 indexed groupId, uint32 delay, uint48 from);
|
||||
|
||||
event ContractFamilyUpdated(address indexed target, uint64 indexed familyId);
|
||||
event ContractClosed(address indexed target, bool closed);
|
||||
|
||||
event FamilyFunctionGroupUpdated(uint64 indexed familyId, bytes4 selector, uint64 indexed groupId);
|
||||
event FamilyAdminDelayUpdated(uint64 indexed familyId, uint32 delay, uint48 from);
|
||||
|
||||
error AccessManagerAlreadyScheduled(bytes32 operationId);
|
||||
error AccessManagerNotScheduled(bytes32 operationId);
|
||||
error AccessManagerNotReady(bytes32 operationId);
|
||||
error AccessManagerExpired(bytes32 operationId);
|
||||
error AccessManagerLockedGroup(uint64 groupId);
|
||||
error AccessManagerInvalidFamily(uint64 familyId);
|
||||
error AccessManagerAccountAlreadyInGroup(uint64 groupId, address account);
|
||||
error AccessManagerAccountNotInGroup(uint64 groupId, address account);
|
||||
error AccessManagerBadConfirmation();
|
||||
error AccessManagerUnauthorizedAccount(address msgsender, uint64 groupId);
|
||||
error AccessManagerUnauthorizedCall(address caller, address target, bytes4 selector);
|
||||
error AccessManagerCannotCancel(address msgsender, address caller, address target, bytes4 selector);
|
||||
|
||||
function canCall(
|
||||
address caller,
|
||||
address target,
|
||||
bytes4 selector
|
||||
) external view returns (bool allowed, uint32 delay);
|
||||
|
||||
function expiration() external returns (uint32);
|
||||
|
||||
function getContractFamily(address target) external view returns (uint64 familyId, bool closed);
|
||||
|
||||
function getFamilyFunctionGroup(uint64 familyId, bytes4 selector) external view returns (uint64);
|
||||
|
||||
function getFamilyAdminDelay(uint64 familyId) external view returns (uint32);
|
||||
|
||||
function getGroupAdmin(uint64 groupId) external view returns (uint64);
|
||||
|
||||
function getGroupGuardian(uint64 groupId) external view returns (uint64);
|
||||
|
||||
function getGroupGrantDelay(uint64 groupId) external view returns (uint32);
|
||||
|
||||
function getAccess(uint64 groupId, address account) external view returns (uint48, uint32, uint32, uint48);
|
||||
|
||||
function hasGroup(uint64 groupId, address account) external view returns (bool, uint32);
|
||||
|
||||
function labelGroup(uint64 groupId, string calldata label) external;
|
||||
|
||||
function grantGroup(uint64 groupId, address account, uint32 executionDelay) external;
|
||||
|
||||
function revokeGroup(uint64 groupId, address account) external;
|
||||
|
||||
function renounceGroup(uint64 groupId, address callerConfirmation) external;
|
||||
|
||||
function setExecuteDelay(uint64 groupId, address account, uint32 newDelay) external;
|
||||
|
||||
function setGroupAdmin(uint64 groupId, uint64 admin) external;
|
||||
|
||||
function setGroupGuardian(uint64 groupId, uint64 guardian) external;
|
||||
|
||||
function setGrantDelay(uint64 groupId, uint32 newDelay) external;
|
||||
|
||||
function setFamilyFunctionGroup(uint64 familyId, bytes4[] calldata selectors, uint64 groupId) external;
|
||||
|
||||
function setFamilyAdminDelay(uint64 familyId, uint32 newDelay) external;
|
||||
|
||||
function setContractFamily(address target, uint64 familyId) external;
|
||||
|
||||
function setContractClosed(address target, bool closed) external;
|
||||
|
||||
function getSchedule(bytes32 id) external returns (uint48);
|
||||
|
||||
function schedule(address target, bytes calldata data, uint48 when) external returns (bytes32);
|
||||
|
||||
function relay(address target, bytes calldata data) external payable;
|
||||
|
||||
function cancel(address caller, address target, bytes calldata data) external;
|
||||
|
||||
function consumeScheduledOp(address caller, bytes calldata data) external;
|
||||
|
||||
function updateAuthority(address target, address newAuthority) external;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
/**
|
||||
* @dev Standard interface for permissioning originally defined in Dappsys.
|
||||
|
||||
18
contracts/mocks/AccessManagedTarget.sol
Normal file
18
contracts/mocks/AccessManagedTarget.sol
Normal file
@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {AccessManaged} from "../access/manager/AccessManaged.sol";
|
||||
|
||||
abstract contract AccessManagedTarget is AccessManaged {
|
||||
event CalledRestricted(address caller);
|
||||
event CalledUnrestricted(address caller);
|
||||
|
||||
function fnRestricted() public restricted {
|
||||
emit CalledRestricted(msg.sender);
|
||||
}
|
||||
|
||||
function fnUnrestricted() public {
|
||||
emit CalledUnrestricted(msg.sender);
|
||||
}
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import "../access/manager/IAuthority.sol";
|
||||
import "../access/manager/AccessManaged.sol";
|
||||
|
||||
contract SimpleAuthority is IAuthority {
|
||||
address _allowedCaller;
|
||||
address _allowedTarget;
|
||||
bytes4 _allowedSelector;
|
||||
|
||||
function setAllowed(address allowedCaller, address allowedTarget, bytes4 allowedSelector) public {
|
||||
_allowedCaller = allowedCaller;
|
||||
_allowedTarget = allowedTarget;
|
||||
_allowedSelector = allowedSelector;
|
||||
}
|
||||
|
||||
function canCall(address caller, address target, bytes4 selector) external view override returns (bool) {
|
||||
return caller == _allowedCaller && target == _allowedTarget && selector == _allowedSelector;
|
||||
}
|
||||
}
|
||||
|
||||
abstract contract AccessManagedMock is AccessManaged {
|
||||
event RestrictedRan();
|
||||
|
||||
function restrictedFunction() external restricted {
|
||||
emit RestrictedRan();
|
||||
}
|
||||
|
||||
function otherRestrictedFunction() external restricted {
|
||||
emit RestrictedRan();
|
||||
}
|
||||
}
|
||||
139
contracts/utils/types/Time.sol
Normal file
139
contracts/utils/types/Time.sol
Normal file
@ -0,0 +1,139 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Math} from "../math/Math.sol";
|
||||
import {SafeCast} from "../math/SafeCast.sol";
|
||||
|
||||
/**
|
||||
* @dev This library provides helpers for manipulating time-related objects.
|
||||
*
|
||||
* It uses the following types:
|
||||
* - `uint48` for timepoints
|
||||
* - `uint32` for durations
|
||||
*
|
||||
* While the library doesn't provide specific types for timepoints and duration, it does provide:
|
||||
* - a `Delay` type to represent duration that can be programmed to change value automatically at a given point
|
||||
* - additional helper functions
|
||||
*/
|
||||
library Time {
|
||||
using Time for *;
|
||||
|
||||
/**
|
||||
* @dev Get the block timestamp as a Timepoint.
|
||||
*/
|
||||
function timestamp() internal view returns (uint48) {
|
||||
return SafeCast.toUint48(block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the block number as a Timepoint.
|
||||
*/
|
||||
function blockNumber() internal view returns (uint48) {
|
||||
return SafeCast.toUint48(block.number);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Check if a timepoint is set, and in the past.
|
||||
*/
|
||||
function isSetAndPast(uint48 timepoint, uint48 ref) internal pure returns (bool) {
|
||||
return timepoint != 0 && timepoint <= ref;
|
||||
}
|
||||
|
||||
// ==================================================== Delay =====================================================
|
||||
/**
|
||||
* @dev A `Delay` is a uint32 duration that can be programmed to change value automatically at a given point in the
|
||||
* future. The "effect" timepoint describes when the transitions happens from the "old" value to the "new" value.
|
||||
* This allows updating the delay applied to some operation while keeping so guarantees.
|
||||
*
|
||||
* In particular, the {update} function guarantees that is the delay is reduced, the old delay still applies for
|
||||
* some time. For example if the delay is currently 7 days to do an upgrade, the admin should not be able to set
|
||||
* the delay to 0 and upgrade immediately. If the admin wants to reduce the delay, the old delay (7 days) should
|
||||
* still apply for some time.
|
||||
*
|
||||
*
|
||||
* The `Delay` type is 128 bits long, and packs the following:
|
||||
* [000:031] uint32 for the current value (duration)
|
||||
* [032:063] uint32 for the pending value (duration)
|
||||
* [064:111] uint48 for the effect date (timepoint)
|
||||
*
|
||||
* NOTE: The {get} and {update} function operate using timestamps. Block number based delays should use the
|
||||
* {getAt} and {withUpdateAt} variants of these functions.
|
||||
*/
|
||||
type Delay is uint112;
|
||||
|
||||
/**
|
||||
* @dev Wrap a duration into a Delay to add the one-step "update in the future" feature
|
||||
*/
|
||||
function toDelay(uint32 duration) internal pure returns (Delay) {
|
||||
return Delay.wrap(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the value at a given timepoint plus the pending value and effect timepoint if there is a scheduled
|
||||
* change after this timepoint. If the effect timepoint is 0, then the pending value should not be considered.
|
||||
*/
|
||||
function getFullAt(Delay self, uint48 timepoint) internal pure returns (uint32, uint32, uint48) {
|
||||
(uint32 oldValue, uint32 newValue, uint48 effect) = self.unpack();
|
||||
return effect.isSetAndPast(timepoint) ? (newValue, 0, 0) : (oldValue, newValue, effect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the
|
||||
* effect timepoint is 0, then the pending value should not be considered.
|
||||
*/
|
||||
function getFull(Delay self) internal view returns (uint32, uint32, uint48) {
|
||||
return self.getFullAt(timestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the value the Delay will be at a given timepoint.
|
||||
*/
|
||||
function getAt(Delay self, uint48 timepoint) internal pure returns (uint32) {
|
||||
(uint32 delay, , ) = getFullAt(self, timepoint);
|
||||
return delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get the current value.
|
||||
*/
|
||||
function get(Delay self) internal view returns (uint32) {
|
||||
return self.getAt(timestamp());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Update a Delay object so that a new duration takes effect at a given timepoint.
|
||||
*/
|
||||
function withUpdateAt(Delay self, uint32 newValue, uint48 effect) internal view returns (Delay) {
|
||||
return pack(self.get(), newValue, effect);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Update a Delay object so that it takes a new duration after at a timepoint that is automatically computed
|
||||
* to enforce the old delay at the moment of the update.
|
||||
*/
|
||||
function withUpdate(Delay self, uint32 newValue, uint32 minSetback) internal view returns (Delay) {
|
||||
uint32 value = self.get();
|
||||
uint32 setback = uint32(Math.max(minSetback, value > newValue ? value - newValue : 0));
|
||||
return self.withUpdateAt(newValue, timestamp() + setback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Split a delay into its components: oldValue, newValue and effect (transition timepoint).
|
||||
*/
|
||||
function unpack(Delay self) internal pure returns (uint32, uint32, uint48) {
|
||||
uint112 raw = Delay.unwrap(self);
|
||||
return (
|
||||
uint32(raw), // oldValue
|
||||
uint32(raw >> 32), // newValue
|
||||
uint48(raw >> 64) // effect
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev pack the components into a Delay object.
|
||||
*/
|
||||
function pack(uint32 oldValue, uint32 newValue, uint48 effect) internal pure returns (Delay) {
|
||||
return Delay.wrap(uint112(oldValue) | (uint112(newValue) << 32) | (uint112(effect) << 64));
|
||||
}
|
||||
}
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@ -45,7 +45,7 @@
|
||||
"solhint": "^3.3.6",
|
||||
"solhint-plugin-openzeppelin": "file:scripts/solhint-custom",
|
||||
"solidity-ast": "^0.4.25",
|
||||
"solidity-coverage": "^0.8.0",
|
||||
"solidity-coverage": "^0.8.4",
|
||||
"solidity-docgen": "^0.6.0-beta.29",
|
||||
"undici": "^5.22.1",
|
||||
"web3": "^1.3.0",
|
||||
@ -12796,9 +12796,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/solidity-coverage/node_modules/@solidity-parser/parser": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.0.tgz",
|
||||
"integrity": "sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==",
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz",
|
||||
"integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"antlr4ts": "^0.5.0-alpha.4"
|
||||
@ -25272,9 +25272,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@solidity-parser/parser": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.0.tgz",
|
||||
"integrity": "sha512-ESipEcHyRHg4Np4SqBCfcXwyxxna1DgFVz69bgpLV8vzl/NP1DtcKsJ4dJZXWQhY/Z4J2LeKBiOkOVZn9ct33Q==",
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.1.tgz",
|
||||
"integrity": "sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"antlr4ts": "^0.5.0-alpha.4"
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
const {
|
||||
expectEvent,
|
||||
expectRevert,
|
||||
constants: { ZERO_ADDRESS },
|
||||
} = require('@openzeppelin/test-helpers');
|
||||
|
||||
const AccessManaged = artifacts.require('$AccessManagedMock');
|
||||
const SimpleAuthority = artifacts.require('SimpleAuthority');
|
||||
|
||||
contract('AccessManaged', function (accounts) {
|
||||
const [authority, other, user] = accounts;
|
||||
it('construction', async function () {
|
||||
const managed = await AccessManaged.new(authority);
|
||||
expectEvent.inConstruction(managed, 'AuthorityUpdated', {
|
||||
oldAuthority: ZERO_ADDRESS,
|
||||
newAuthority: authority,
|
||||
});
|
||||
expect(await managed.authority()).to.equal(authority);
|
||||
});
|
||||
|
||||
describe('setAuthority', function () {
|
||||
it(`current authority can change managed's authority`, async function () {
|
||||
const managed = await AccessManaged.new(authority);
|
||||
const set = await managed.setAuthority(other, { from: authority });
|
||||
expectEvent(set, 'AuthorityUpdated', {
|
||||
sender: authority,
|
||||
newAuthority: other,
|
||||
});
|
||||
expect(await managed.authority()).to.equal(other);
|
||||
});
|
||||
|
||||
it(`other account cannot change managed's authority`, async function () {
|
||||
const managed = await AccessManaged.new(authority);
|
||||
await expectRevert(managed.setAuthority(other, { from: other }), 'AccessManaged: not current authority');
|
||||
});
|
||||
});
|
||||
|
||||
describe('restricted', function () {
|
||||
const selector = web3.eth.abi.encodeFunctionSignature('restrictedFunction()');
|
||||
|
||||
it('allows if authority returns true', async function () {
|
||||
const authority = await SimpleAuthority.new();
|
||||
const managed = await AccessManaged.new(authority.address);
|
||||
await authority.setAllowed(user, managed.address, selector);
|
||||
const restricted = await managed.restrictedFunction({ from: user });
|
||||
expectEvent(restricted, 'RestrictedRan');
|
||||
});
|
||||
|
||||
it('reverts if authority returns false', async function () {
|
||||
const authority = await SimpleAuthority.new();
|
||||
const managed = await AccessManaged.new(authority.address);
|
||||
await expectRevert(managed.restrictedFunction({ from: user }), 'AccessManaged: authority rejected');
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,5 @@ module.exports = {
|
||||
ProposalState: Enum('Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'),
|
||||
VoteType: Enum('Against', 'For', 'Abstain'),
|
||||
Rounding: Enum('Floor', 'Ceil', 'Trunc', 'Expand'),
|
||||
AccessMode: Enum('Custom', 'Closed', 'Open'),
|
||||
OperationState: Enum('Unset', 'Waiting', 'Ready', 'Done'),
|
||||
};
|
||||
|
||||
5
test/helpers/methods.js
Normal file
5
test/helpers/methods.js
Normal file
@ -0,0 +1,5 @@
|
||||
const { soliditySha3 } = require('web3-utils');
|
||||
|
||||
module.exports = {
|
||||
selector: signature => soliditySha3(signature).substring(0, 10),
|
||||
};
|
||||
Reference in New Issue
Block a user