• Michael.W基于Foundry精读Openzeppelin第38期——AccessControlEnumerable.sol


    0. 版本

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

    0.1 AccessControlEnumerable.sol

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

    AccessControlEnumerable库用于管理函数的调用权限,是AccessControl库的拓展版。与AccessControl库相比,AccessControlEnumerable支持在编成员的迭代导出,这大大方便了各个角色权限的统计查询(不用通过扫块追溯events来统计目前各角色的在编权限人员的地址)。

    1. 目标合约

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

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

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.0;
    
    import "openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol";
    
    contract MockAccessControlEnumerable is AccessControlEnumerable {
        constructor(){
            // set msg.sender into admin role
            _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        }
    
        function doSomethingWithAccessControl(bytes32 role) onlyRole(role) external {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    全部foundry测试合约:

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

    2. 代码精读

    2.1 supportsInterface(bytes4 interfaceId)

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

    注:此处重写了AccessControl.supportsInterface(),即在全部支持的interface ids中加入IAccessControlEnumerable的interface id。AccessControl.supportsInterface()的细节参见:

        using EnumerableSet for EnumerableSet.AddressSet;
    
        // 用于迭代各role的在编成员地址的set。这里借用了openzeppelin的EnumerableSet库中的AddressSet结构体
        mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;
    
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        	// 如果输入的interfaceId为IAccessControlEnumerable或IAccessControl或IERC165的interface id,返回true。否则返回false
            return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    foundry代码验证

    contract AccessControlEnumerableTest is Test {
        MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();
    
        function test_SupportsInterface() external {
            // support IERC165 && IAccessControl && IAccessControlEnumerable
            assertTrue(_testing.supportsInterface(type(IERC165).interfaceId));
            assertTrue(_testing.supportsInterface(type(IAccessControl).interfaceId));
            assertTrue(_testing.supportsInterface(type(IAccessControlEnumerable).interfaceId));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    2.2 _grantRole(bytes32 role, address account)

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

    注:该方法重写了父类AccessControl的同名方法,在AccessControl._grantRole()的基础上增加了在EnumerableSet.AddressSet中注册account的逻辑。同时,父类AccessControl.grantRole()方法的内在逻辑也会改变。

        function _grantRole(bytes32 role, address account) internal virtual override {
            // 调用父类AccessControl._grantRole()
            super._grantRole(role, account);
            // 在输入role对应的EnumerableSet.AddressSet中注册account地址
            _roleMembers[role].add(account);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    foundry代码验证

    contract AccessControlEnumerableTest is Test {
        MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();
    
        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 {
            // case 1: grant role for ROLE_DEFAULT
            address account = address(1024);
            assertFalse(_testing.hasRole(ROLE_DEFAULT, account));
            // deployer (address of AccessControlEnumerableTest) is already in
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 1);
    
            vm.expectEmit(true, true, true, false, address(_testing));
            emit RoleGranted(ROLE_DEFAULT, account, address(this));
            _testing.grantRole(ROLE_DEFAULT, account);
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 2);
            assertTrue(_testing.hasRole(ROLE_DEFAULT, account));
    
            // grant more accounts for ROLE_DEFAULT
            _testing.grantRole(ROLE_DEFAULT, address(2048));
            _testing.grantRole(ROLE_DEFAULT, address(4096));
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 4);
    
            // revert if msg.sender is not the admin of the role
            vm.prank(address(0));
            vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000000 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
            _testing.grantRole(ROLE_DEFAULT, account);
    
            // case 2: grant role for ROLE_1
            assertEq(_testing.getRoleMemberCount(ROLE_1), 0);
            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));
            assertEq(_testing.getRoleMemberCount(ROLE_1), 1);
    
            // grant more accounts for ROLE_1
            _testing.grantRole(ROLE_1, address(2048));
            _testing.grantRole(ROLE_1, address(4096));
            assertEq(_testing.getRoleMemberCount(ROLE_1), 3);
    
            // revert if msg.sender is not the admin of the role
            vm.prank(address(0));
            vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000000 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
            _testing.grantRole(ROLE_1, account);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    2.3 _revokeRole(bytes32 role, address account)

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

    注:该方法重写了父类AccessControl的同名方法,在AccessControl._revokeRole()的基础上增加了在EnumerableSet.AddressSet中删除account的逻辑。同时,父类AccessControl.revokeRole()方法的内在逻辑也会改变。

        function _revokeRole(bytes32 role, address account) internal virtual override {
            // 调用父类AccessControl._revokeRole()
            super._revokeRole(role, account);
            // 在输入role对应的EnumerableSet.AddressSet中删除account地址
            _roleMembers[role].remove(account);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    foundry代码验证

    contract AccessControlEnumerableTest is Test {
        MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();
    
        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 {
            // case 1: revoke role for ROLE_DEFAULT
            address account = address(1024);
            _testing.grantRole(ROLE_DEFAULT, account);
            _testing.grantRole(ROLE_DEFAULT, address(2048));
            _testing.grantRole(ROLE_DEFAULT, address(4096));
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 4);
    
            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));
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 3);
    
            _testing.revokeRole(ROLE_DEFAULT, address(2048));
            _testing.revokeRole(ROLE_DEFAULT, address(4096));
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 1);
    
            // revert if msg.sender is not the admin of the role
            vm.prank(address(1));
            vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000001 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
            _testing.revokeRole(ROLE_DEFAULT, address(this));
    
            // case 2: revoke role for ROLE_1
            _testing.grantRole(ROLE_1, account);
            _testing.grantRole(ROLE_1, address(2048));
            _testing.grantRole(ROLE_1, address(4096));
            assertEq(_testing.getRoleMemberCount(ROLE_1), 3);
    
            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));
            assertEq(_testing.getRoleMemberCount(ROLE_1), 2);
    
            _testing.revokeRole(ROLE_1, address(2048));
            _testing.revokeRole(ROLE_1, address(4096));
            assertEq(_testing.getRoleMemberCount(ROLE_1), 0);
    
            // revert if msg.sender is not the admin of the role
            vm.prank(address(1));
            vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000001 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");
            _testing.revokeRole(ROLE_1, address(this));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    2.4 getRoleMember(bytes32 role, uint256 index) && getRoleMemberCount(bytes32 role)
    • getRoleMember(bytes32 role, uint256 index):获得输入role中索引为index的在编权限地址。输入的index应该介于[0, getRoleMemberCount(role))之内。注:1. 返回的在编权限地址的index顺序与其被添加的顺序无关;2. 严格的讲,该方法与getRoleMemberCount(role)应该保证在同一个区块高度被调用,这样才能保证数据状态的一致性;
    • getRoleMemberCount(bytes32 role):返回输入role的在编权限地址的个数。注:该函数与getRoleMember()配合使用可以迭代出该role的全部在编权限地址。

    注:openzeppelin中EnumerableSet库的相关细节参见:https://learnblockchain.cn/article/6272

        function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {
        	// 直接调用role对应的EnumerableSet.AddressSet的at()方法,获取role中索引为index的在编权限地址
            return _roleMembers[role].at(index);
        }
    
        function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {
        	// 直接调用role对应的EnumerableSet.AddressSet的length()方法,获取该role的在编权限地址的个数
            return _roleMembers[role].length();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    foundry代码验证

    contract AccessControlEnumerableTest is Test {
        MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();
    
        bytes32 immutable private ROLE_DEFAULT = 0;
        bytes32 immutable private ROLE_1 = keccak256("ROLE_1");
    
        function test_GetRoleMemberAndGetRoleMemberCount() external {
            // case 1: for ROLE_DEFAULT
            _testing.grantRole(ROLE_DEFAULT, address(1024));
            _testing.grantRole(ROLE_DEFAULT, address(2048));
            _testing.grantRole(ROLE_DEFAULT, address(4096));
    
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 4);
            assertEq(_testing.getRoleMember(ROLE_DEFAULT, 0), address(this));
            assertEq(_testing.getRoleMember(ROLE_DEFAULT, 1), address(1024));
            assertEq(_testing.getRoleMember(ROLE_DEFAULT, 2), address(2048));
            assertEq(_testing.getRoleMember(ROLE_DEFAULT, 3), address(4096));
    
            // revoke
            _testing.revokeRole(ROLE_DEFAULT, address(1024));
    
            // index of account are not sorted when #revoke()
            assertEq(_testing.getRoleMemberCount(ROLE_DEFAULT), 3);
            assertEq(_testing.getRoleMember(ROLE_DEFAULT, 0), address(this));
            assertEq(_testing.getRoleMember(ROLE_DEFAULT, 1), address(4096));
            assertEq(_testing.getRoleMember(ROLE_DEFAULT, 2), address(2048));
    
            // case 2: for ROLE_1
            _testing.grantRole(ROLE_1, address(1024));
            _testing.grantRole(ROLE_1, address(2048));
            _testing.grantRole(ROLE_1, address(4096));
    
            assertEq(_testing.getRoleMemberCount(ROLE_1), 3);
            assertEq(_testing.getRoleMember(ROLE_1, 0), address(1024));
            assertEq(_testing.getRoleMember(ROLE_1, 1), address(2048));
            assertEq(_testing.getRoleMember(ROLE_1, 2), address(4096));
    
            // revoke
            _testing.revokeRole(ROLE_1, address(1024));
    
            // index of account are not sorted when #revoke()
            assertEq(_testing.getRoleMemberCount(ROLE_1), 2);
            assertEq(_testing.getRoleMember(ROLE_1, 0), address(4096));
            assertEq(_testing.getRoleMember(ROLE_1, 1), address(2048));
        }
    
        function test_onlyRole() external {
            // test for modifier onlyRole
            address account = address(1024);
            // test for ROLE_DEFAULT
            // 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

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

    在这里插入图片描述

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

  • 相关阅读:
    Postman接口自动化测试实例
    苯硫酚荧光探针 激发波长465 nm
    移动通信网络规划:面、线、点覆盖规划
    使用ADS进行serdes仿真时,Tx_Diff中EQ的设置对发送端波形的影响。
    伦敦银现货白银走势如何应对
    【在英伟达nvidia的jetson-orin-nx上使用调试摄像头-初步调试USB摄像头与Camera Conn.#0/#1接口-基础测试】
    浅记录一下MATLAB安装心得
    通过内网穿透远程控制家中Home Assistant智能家居系统
    HashMap 这套八股,不得背个十来遍?
    思科三层交换机vlan间的通信
  • 原文地址:https://blog.csdn.net/michael_wgy_/article/details/134468210