简单分析 AccessManager的基本逻辑

OpenZeppelin Contracts (last updated v5.1.0) (access/manager/AccessManager.sol)

contract AccessManager is Context, Multicall, IAccessManager {

他继承了 Multicall 代表可以在一笔交易里面同时调用它的多个方法

合约数据的主要结构

  // Structure that stores the details for a target contract.
    struct TargetConfig {
        mapping(bytes4 selector => uint64 roleId) allowedRoles;
        Time.Delay adminDelay;
        bool closed;
    }

    // Structure that stores the details for a role/account pair. This structures fit into a single slot.
    struct Access {
        // Timepoint at which the user gets the permission.
        // If this is either 0 or in the future, then the role permission is not available.
        uint48 since;
        // Delay for execution. Only applies to restricted() / execute() calls.
        Time.Delay delay;
    }

    // Structure that stores the details of a role.
    struct Role {
        // Members of the role.
        mapping(address user => Access access) members;
        // Admin who can grant or revoke permissions.
        uint64 admin;
        // Guardian who can cancel operations targeting functions that need this role.
        uint64 guardian;
        // Delay in which the role takes effect after being granted.
        Time.Delay grantDelay;
    }

    // Structure that stores the details for a scheduled operation. This structure fits into a single slot.
    struct Schedule {
        // Moment at which the operation can be executed.
        uint48 timepoint;  // 可以执行的最小时间戳
        // Operation nonce to allow third-party contracts to identify the operation.
        uint32 nonce;
    }

    mapping(address target => TargetConfig mode) private _targets; // 合约 -> 方法 -> 需要的权限
    mapping(uint64 roleId => Role) private _roles;  // 权限相关
    mapping(bytes32 operationId => Schedule) private _schedules;   //需要延迟执行的 先加入当任务 

AccessManager 是一个帮助第三方合约来管理自己合约方法调用控制的 权限管理合约。
AccessManager 作为第三方合约的Authority, 第三方合约在具体执行某一个方法时先调用 AccessManager的canCall方法检查调用者是否有调用权限,以及是否需要延迟等等
第三方合约 可以通过继承 AccessManaged 合约来加入管理

   // 需要权限控制的方法 上面 加上这个 modifier 
    modifier restricted() {
        _checkCanCall(_msgSender(), _msgData());
        _;
    }
    // 设置 AccessManager   地址
    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);
    }

    // 检查调用权限
    function _checkCanCall(address caller, bytes calldata data) internal virtual {
        (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay(
            authority(),
            caller,
            address(this),
            bytes4(data[0:4])
        );
        // immediate -> true 表示有权限 没有延迟
       // immediate  -> false delay -> 不为零 表示有权限 但是需要延迟
       //  immediate  -> false delay -> 为零 表示 没有权限
        if (!immediate) {  
            if (delay > 0) { // 有权限但是需要延迟 检查延迟时间, 这里就需要先再 AccessManager  调用 schedule,如果延迟时间没有到或者过期 consumeScheduledOp会报错
                _consumingSchedule = true;
                IAccessManager(authority()).consumeScheduledOp(caller, data);
                _consumingSchedule = false;
            } else {  // 没有权限
                revert AccessManagedUnauthorized(caller);
            }
        }
      // 有权限 不需要延迟 直接放行
    }

   //AuthorityUtils
    function canCallWithDelay(
        address authority,
        address caller,
        address target,
        bytes4 selector
    ) internal view returns (bool immediate, uint32 delay) {
        (bool success, bytes memory data) = authority.staticcall(
            abi.encodeCall(IAuthority.canCall, (caller, target, selector))
        );
        if (success) {
            if (data.length >= 0x40) {
                (immediate, delay) = abi.decode(data, (bool, uint32));
            } else if (data.length >= 0x20) {
                immediate = abi.decode(data, (bool));
            }
        }
        return (immediate, delay);
    }

canCall 是AccessManager 的一个主要方法

    function canCall(
        address caller,
        address target,
        bytes4 selector
    ) public view virtual returns (bool immediate, uint32 delay) {
        if (isTargetClosed(target)) {  // closed 表示 不能被调用了
            return (false, 0);
        } else if (caller == address(this)) {  // 如果调用者是 AccessManager  自己
            // Caller is AccessManager, this means the call was sent through {execute} and it already checked
            // permissions. We verify that the call "identifier", which is set during {execute}, is correct.
            return (_isExecuting(target, selector), 0);
        } else {
            uint64 roleId = getTargetFunctionRole(target, selector);  //获取被调用的合约的方法需要什么role
            (bool isMember, uint32 currentDelay) = hasRole(roleId, caller);  // 检查调用者是否有调用权限, 是否需要 延迟, 以及延迟的时间是多少
            return isMember ? (currentDelay == 0, currentDelay) : (false, 0); // 没有权限直接返回 (false, 0),有权限没有延迟返回(true, 0),有权限但是需要延迟(返回 false, 延迟时间) 
        }
    }

通过 AccessManager setTargetFunctionRole 方法 设置 第三方合约的方法的调用权限 加入权限管理
通过 AccessManager schedule方法 把需要延迟执行的任务加入到任务栏, 等待延迟到来。
通过 AccessManager execute方法 执行延迟时间已经到来的任务, 或者执行不需要延迟的任务。
通过 AccessManager cancel 方法取消等待延迟时间到来的任务。

还有 AccessManager 也可以管理之前 Ownable 或者 AccessManager的合约 就是把这些合约的owner权限转移给此合约。
调用具体合约的onlyOwner或者onlyRole的方法时 需要通过 调用AccessManager的execute(address target, bytes calldata data) 方法, 在execute 方法里面去调用具体合约的方法
如果需要延迟调用 应该先调用AccessManager ->schedule方法 把任务加入到延迟任务栏,等延迟时间到的时候调用AccessManager -> execute 方法执行。

AccessManager 相当于之前的 权限管理合约加上 timeLock。 可以把多个合约的权限管理放到一个合约 。

这里面 延迟时间的管理用到了 Time library 里面的Delay
Delay本身是一个 uint112 它分为3部分 从左到右 前 6个字节表示最新设置的延迟时间的生效时间,接着4个字节是原来设置的延迟时间,最后4个字节是最新设置的延迟时间,当通过get()方法来获取延迟时间的时候, 先比较当前时间与最新设置的延迟时间的生效时间, 如果当前时间大于这个生效时间 就返回最新设置的延迟时间,否则就返回原来设置的生效时间。
通过withUpdate方法来修改。每次修改先通过get()方法拿到当前的延迟时间当作原来的延迟时间 ,要修改的延迟时间当作最新需要设置的值, 最小的生效延迟+加上当前时间当作这一次修改的生效时间。按照前面说的顺序写入。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容