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

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

      • [0. 版本](#0. 版本)
        • [0.1 AccessControlEnumerable.sol](#0.1 AccessControlEnumerable.sol)
      • [1. 目标合约](#1. 目标合约)
      • [2. 代码精读](#2. 代码精读)
        • [2.1 supportsInterface(bytes4 interfaceId)](#2.1 supportsInterface(bytes4 interfaceId))
        • [2.2 _grantRole(bytes32 role, address account)](#2.2 _grantRole(bytes32 role, address account))
        • [2.3 _revokeRole(bytes32 role, address account)](#2.3 _revokeRole(bytes32 role, address account))
        • [2.4 getRoleMember(bytes32 role, uint256 index) && getRoleMemberCount(bytes32 role)](#2.4 getRoleMember(bytes32 role, uint256 index) && getRoleMemberCount(bytes32 role))

0. 版本

openzeppelin\]:v4.8.3,\[forge-std\]:v1.5.6 ##### 0.1 AccessControlEnumerable.sol Github: AccessControlEnumerable库用于管理函数的调用权限,是AccessControl库的拓展版。与AccessControl库相比,AccessControlEnumerable支持在编成员的迭代导出,这大大方便了各个角色权限的统计查询(不用通过扫块追溯events来统计目前各角色的在编权限人员的地址)。 #### 1. 目标合约 继承AccessControlEnumerable成为一个可调用合约: Github: ```js // 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: #### 2. 代码精读 ##### 2.1 supportsInterface(bytes4 interfaceId) 对外提供本合约是否实现了输入interfaceId标识的interface的查询功能。 注:此处重写了AccessControl.supportsInterface(),即在全部支持的interface ids中加入`IAccessControlEnumerable`的interface id。AccessControl.supportsInterface()的细节参见:[](https://learnblockchain.cn/article/6632) ```js 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代码验证** ```js 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()`方法的内在逻辑也会改变。 ```js function _grantRole(bytes32 role, address account) internal virtual override { // 调用父类AccessControl._grantRole() super._grantRole(role, account); // 在输入role对应的EnumerableSet.AddressSet中注册account地址 _roleMembers[role].add(account); } ``` **foundry代码验证** ```js 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()`方法的内在逻辑也会改变。 ```js function _revokeRole(bytes32 role, address account) internal virtual override { // 调用父类AccessControl._revokeRole() super._revokeRole(role, account); // 在输入role对应的EnumerableSet.AddressSet中删除account地址 _roleMembers[role].remove(account); } ``` **foundry代码验证** ```js 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库的相关细节参见: ```js 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代码验证** ```js 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神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢! ![在这里插入图片描述](https://file.jishuzhan.net/article/1725465610462171138/6207da2f17747e384c6a20e7a331336c.webp) 公众号名称:后现代泼痞浪漫主义奠基人

相关推荐
Blockchina3 天前
第 4 章 | Solidity安全 权限控制漏洞全解析
安全·web3·区块链·智能合约·solidity
Blockchina4 天前
第十四章 | DeFi / DAO / GameFi 项目高级实战
web3·区块链·智能合约·solidity
谭光志4 天前
如何估算和优化 Gas
web3·区块链·solidity
0x派大星6 天前
打造更安全的区块链资产管理:Solidity 多重签名机制详解
安全·web3·区块链·智能合约·solidity
Blockchina6 天前
第十五章 | Layer2、Rollup 与 ZK 技术实战解析
python·web3·区块链·智能合约·solidity
Blockchina7 天前
第 2 章 | 智能合约攻击图谱全景解析
web3·区块链·智能合约·solidity·区块链安全
Blockchina7 天前
第 1 章 | 开篇词:Dapp安全 区块链安全 Web3安全 区块链合约一旦部署,安全就是生死线
安全·web3·区块链·智能合约·solidity·合约审计
Blockchina9 天前
第八章 | 函数修饰符与访问控制模式
java·python·区块链·智能合约·solidity
Blockchina9 天前
第十二章 | Solidity 智能合约前后端集成实战
java·python·区块链·智能合约·solidity