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

  • Michael.W
  • 更新于 2023-11-17 17:34
  • 阅读 728

AccessControlEnumerable库用于管理函数的调用权限,是AccessControl库的拓展版。与AccessControl库相比,AccessControlEnumerable支持在编成员的迭代导出,这大大方便了各个角色权限的统计查询。

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 {}
}

全部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()的细节参见:https://learnblockchain.cn/article/6632

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

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

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

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

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

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

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

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

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

1.jpeg

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

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

0 条评论

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