Michael.W基于Foundry精读Openzeppelin第37期——AccessControl.sol

  • Michael.W
  • 更新于 2023-09-28 16:52
  • 阅读 1446

AccessControl库用于管理函数的调用权限,所有继承了AccessControl的子合约均可为自己的业务函数设置调用权限。AccessControl是一个轻量级的基础库且各个role不支持在编成员的迭代导出,所以授权和撤销role成员的操作会抛出event。

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 AccessControl.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/access/AccessControl.sol

AccessControl库用于管理函数的调用权限,所有继承了AccessControl的子合约均可为自己的业务函数设置调用权限。AccessControl是一个轻量级的基础库且各个role不支持在编成员的迭代导出,所以授权和撤销role成员的操作会抛出event。role表示角色权限的种类,不同的bytes32表示不同的角色权限。各个role可以由自己的admin role在编地址管理本role的在编人员名单,并且合约内涉及的role应该由bytes32 public constant暴露给外部。

1. 目标合约

继承AccessControl成为一个可调用合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/access/MockAccessControl.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/access/AccessControl.sol";

contract MockAccessControl is AccessControl {
    constructor(){
        // set msg.sender into admin role
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    function setRoleAdmin(bytes32 role, bytes32 adminRole) external {
        _setRoleAdmin(role, adminRole);
    }

    function doSomethingWithAccessControl(bytes32 role) onlyRole(role) external {}
}

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/access/AccessControl.t.sol

2. 代码精读

2.1 supportsInterface(bytes4 interfaceId)

对外提供本合约是否实现了输入interfaceId标识的interface的查询功能。

注:此处重写了ERC165.supportsInterface(),即在全部支持的interface ids中加入IAccessControl的interface id。ERC165.supportsInterface()的细节参见:https://learnblockchain.cn/article/6286

    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        // 如果输入的interfaceId为IAccessControl或IERC165的interface id,返回true。否则返回false
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

foundry代码验证

contract AccessControlTest is Test {
    MockAccessControl private _testing = new MockAccessControl();

    function test_SupportsInterface() external {
        // support IERC165 and IAccessControl
        assertTrue(_testing.supportsInterface(type(IERC165).interfaceId));
        assertTrue(_testing.supportsInterface(type(IAccessControl).interfaceId));
    }
}

2.2 hasRole(bytes32 role, address account) && getRoleAdmin(bytes32 role) && _setRoleAdmin(bytes32 role, bytes32 adminRole)

  • hasRole(bytes32 role, address account):返回输入account地址是否被授予了角色role的权限;
  • getRoleAdmin(bytes32 role):返回输入role中的adminRole。adminRole用于管理对应role的权限授予与撤销;
  • _setRoleAdmin(bytes32 role, bytes32 adminRole):为输入role设置对应的admin role。
     // 每个role(bytes32)都对应唯一的RoleData
    struct RoleData {
        // 该role中用于记录授权地址的mapping
        mapping(address => bool) members;
        // 可管理上述mappings的管理role,默认为bytes32(0),即DEFAULT_ADMIN_ROLE
        // 注:具体地说,_roles[adminRole].members中value为true的地址具有修改本RoleData.members的权限
        bytes32 adminRole;
    }

    // 用于存放role与对应RoleData的mapping
    mapping(bytes32 => RoleData) private _roles;
    // 每个RoleData中默认的adminRole
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        // role作为key找到对应的RoleData.members,再用account作为key去得到value值
        return _roles[role].members[account];
    }

    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        // role作为key找到对应的RoleData.adminRole并返回
        return _roles[role].adminRole;
    }

    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        // 获取目前该role的admin role
        bytes32 previousAdminRole = getRoleAdmin(role);
        // 更新该role的admin role为输入的adminRole
        _roles[role].adminRole = adminRole;
        // 抛出事件
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

foundry代码验证

contract AccessControlTest is Test {
    MockAccessControl private _testing = new MockAccessControl();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    function test_HasRole_GetRoleAdmin_SetRoleAdmin() external {
        // deployer was granted default role
        assertTrue(_testing.hasRole(ROLE_DEFAULT, address(this)));
        // default admin of any role is bytes32(0)
        assertEq(_testing.getRoleAdmin(ROLE_DEFAULT), 0);
        assertEq(_testing.getRoleAdmin(ROLE_1), 0);

        // change admin role by _setRoleAdmin()
        bytes32 newAdminRole = keccak256("new admin role");
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleAdminChanged(ROLE_1, 0, newAdminRole);
        _testing.setRoleAdmin(ROLE_1, newAdminRole);
        assertEq(_testing.getRoleAdmin(ROLE_1), newAdminRole);
    }
}

2.3 grantRole(bytes32 role, address account) && _setupRole(bytes32 role, address account)

  • grantRole(bytes32 role, address account):授予地址account关于输入role的权限。只有具有输入role的adminRole权限的地址才可调用该方法,否则revert。 注:如果输入的account原先不具备输入role的权限,会授权并抛出事件RoleGranted;
  • _setupRole(bytes32 role, address account):将输入role的权限授予给地址account。如果输入的account原先不具备role的权限,会授权并抛出事件RoleGranted。注:该函数不同于函数grantRole()的地方为:不会去验证调用方身份。所以本函数只用于constructor函数中为系统初始化对应role角色的权限。请不要在合约其他地方调用此函数。目前该函数已弃用,请使用_grantRole(bytes32 role, address account)。
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        // onlyRole(getRoleAdmin(role))是对_msgSender()具有role的adminRole的权限进行检查,而后直接调用_grantRole()方法为account授予输入role的权限
        _grantRole(role, account);
    }

    function _setupRole(bytes32 role, address account) internal virtual {
        // 直接调用_grantRole(),所以本internal函数等价于_grantRole()
        _grantRole(role, account);
    }

    // 将输入role的权限授予给地址account。如果输入的account原先不具备role的权限,会授权并抛出事件RoleGranted,否则不做任何操作。
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            // 如果输入account不具有role的权限,在该role对应roleData.member中对account标记为true
            _roles[role].members[account] = true;
            // 抛出事件
            emit RoleGranted(role, account, _msgSender());
        }
    }

foundry代码验证

contract AccessControlTest is Test {
    MockAccessControl private _testing = new MockAccessControl();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    function test_GrantRole() external {
        address account = address(1024);
        assertFalse(_testing.hasRole(ROLE_DEFAULT, account));

        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleGranted(ROLE_DEFAULT, account, address(this));
        _testing.grantRole(ROLE_DEFAULT, account);
        assertTrue(_testing.hasRole(ROLE_DEFAULT, account));

        // grant role for ROLE_1
        assertFalse(_testing.hasRole(ROLE_1, account));
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleGranted(ROLE_1, account, address(this));
        _testing.grantRole(ROLE_1, account);
        assertTrue(_testing.hasRole(ROLE_1, account));
    }

    function testFail_GrantRole_NoEventWhenGrantAgain() external {
        address account = address(1024);
        _testing.grantRole(ROLE_DEFAULT, account);

        // no emit event if grant again
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleGranted(ROLE_DEFAULT, account, address(this));
        _testing.grantRole(ROLE_DEFAULT, account);
    }
}

2.4 revokeRole(bytes32 role, address account)

撤销地址account关于输入role的权限。只有具有输入role的adminRole权限的地址才可调用该方法,否则revert。

注:如果输入的account原先已具备role的权限,会撤销权限并抛出事件RoleRevoked。

    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        // onlyRole(getRoleAdmin(role))是对_msgSender()具有role的adminRole的权限进行检查,而后直接调用_revokeRole()方法为account撤销输入role的权限
        _revokeRole(role, account);
    }

    // 撤销已授予地址account关于输入role的权限。如果输入的account原先已具备role的权限,会撤销权限并抛出事件RoleRevoked,否则不做任何操作。
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            // 如果输入account具有role的权限,在该role对应roleData.member中对account标记为false
            _roles[role].members[account] = false;
            // 抛出事件
            emit RoleRevoked(role, account, _msgSender());
        }
    }

foundry代码验证

contract AccessControlTest is Test {
    MockAccessControl private _testing = new MockAccessControl();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    function test_RevokeRole() external {
        address account = address(1024);
        _testing.grantRole(ROLE_DEFAULT, account);
        assertTrue(_testing.hasRole(ROLE_DEFAULT, account));
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_DEFAULT, account, address(this));
        _testing.revokeRole(ROLE_DEFAULT, account);
        assertFalse(_testing.hasRole(ROLE_DEFAULT, account));

        // revoke role for ROLE_1
        _testing.grantRole(ROLE_1, account);
        assertTrue(_testing.hasRole(ROLE_1, account));
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_1, account, address(this));
        _testing.revokeRole(ROLE_1, account);
        assertFalse(_testing.hasRole(ROLE_1, account));
    }

    function testFail_RevokeRole_NoEventWhenRevokeAgain() external {
        address account = address(1024);
        _testing.grantRole(ROLE_DEFAULT, account);
        _testing.revokeRole(ROLE_DEFAULT, account);

        // no emit event if revoke again
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_DEFAULT, account, address(this));
        _testing.revokeRole(ROLE_DEFAULT, account);
    }
}

2.5 renounceRole(bytes32 role, address account)

account账户自己主动放弃关于输入role的权限。要求必须由account地址来调用该函数。

注:如果输入的account原先已具备role的权限,会撤销权限并抛出事件RoleRevoked。

    function renounceRole(bytes32 role, address account) public virtual override {
        // 要求_msgSender()为输入account地址
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");
    // 撤销account关于输入role的权限
        _revokeRole(role, account);
    }

foundry代码验证

contract AccessControlTest is Test {
    MockAccessControl private _testing = new MockAccessControl();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    function test_RenounceRole() external {
        address account = address(1024);
        _testing.grantRole(ROLE_DEFAULT, account);
        assertTrue(_testing.hasRole(ROLE_DEFAULT, account));
        vm.prank(account);
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_DEFAULT, account, account);
        _testing.renounceRole(ROLE_DEFAULT, account);
        assertFalse(_testing.hasRole(ROLE_DEFAULT, account));

        // renounce role for ROLE_1
        _testing.grantRole(ROLE_1, account);
        assertTrue(_testing.hasRole(ROLE_1, account));
        vm.prank(account);
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_1, account, account);
        _testing.renounceRole(ROLE_1, account);
        assertFalse(_testing.hasRole(ROLE_1, account));

        // case 1: revert if account != _msgSender()
        vm.expectRevert("AccessControl: can only renounce roles for self");
        _testing.renounceRole(ROLE_DEFAULT, account);
    }

    function testFail_RenounceRole_NoEventWhenRenounceAgain() external {
        _testing.renounceRole(ROLE_DEFAULT, address(this));

        // no emit event if renounce again
        vm.expectEmit(true, true, true, false, address(_testing));
        emit RoleRevoked(ROLE_DEFAULT, address(this), address(this));
        _testing.renounceRole(ROLE_DEFAULT, address(this));
    }
}

2.6 modifier onlyRole(bytes32 role)

modifier onlyRole(bytes32 role)用于确认当前_msgSender()已被授予了角色role的权限。否则,直接以格式化字符串信息revert。

    modifier onlyRole(bytes32 role) {
        // 调用_checkRole()进行_msgSender()权限检查
        _checkRole(role);
        _;
    }

    // 确认_msgSender()已被授予了角色role的权限。否则,直接以格式化字符串信息revert
    function _checkRole(bytes32 role) internal view virtual {
        // 调用_checkRole()来确认_msgSender()已被授予了角色role的权限
        _checkRole(role, _msgSender());
    }

    // 确认输入account地址已被授予了角色role的权限。否则,直接以格式化字符串信息revert
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            // 如果account未被授予角色role的权限,则以格式化字符串信息revert
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(account),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

foundry代码验证

contract AccessControlTest is Test {
    MockAccessControl private _testing = new MockAccessControl();

    bytes32 immutable private ROLE_DEFAULT = 0;
    bytes32 immutable private ROLE_1 = keccak256("ROLE_1");

    function test_onlyRole() external {
        // test for modifier onlyRole
        address account = address(1024);
        // test for default role
        // pass
        assertTrue(_testing.hasRole(ROLE_DEFAULT, address(this)));
        _testing.doSomethingWithAccessControl(ROLE_DEFAULT);
        // case 1: revert
        assertFalse(_testing.hasRole(ROLE_DEFAULT, account));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000400 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
        vm.prank(account);
        _testing.doSomethingWithAccessControl(ROLE_DEFAULT);

        // test for role_1
        // case 2: revert
        assertFalse(_testing.hasRole(ROLE_1, account));
        vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000400 is missing role 0x00e1b9dbbc5c12d9bbd9ed29cbfd10bab1e01c5e67a7fc74a02f9d3edc5ad0a8");
        vm.prank(account);
        _testing.doSomethingWithAccessControl(ROLE_1);
        // grant role_1 to account
        _testing.grantRole(ROLE_1,account);
        vm.prank(account);
        _testing.doSomethingWithAccessControl(ROLE_1);
    }
}

ps: 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

公众号名称:后现代泼痞浪漫主义奠基人

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Michael.W
Michael.W
0x93E7...0000
狂热的区块链爱好者