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) 公众号名称:后现代泼痞浪漫主义奠基人

相关推荐
天涯学馆1 天前
Solidity代理合约:解锁区块链代码的灵活升级大法
智能合约·solidity·以太坊
cainiaoeba1 天前
Solidity 学习历程
solidity
木西3 天前
使用 Hardhat V3 框架构建智能合约项目全指南
web3·智能合约·solidity
许强0xq4 天前
Robinhood的再进化:从零佣金交易到链上金融超级应用
金融·web3·区块链·智能合约·solidity·dapp·去平台化时代
天涯学馆7 天前
Solidity自毁合约:让你的区块链代码优雅谢幕
智能合约·solidity·以太坊
小攻城狮长成ing7 天前
从0开始学区块链第10天—— 写第二个智能合约 FundMe
web3·区块链·智能合约·solidity
野老杂谈8 天前
【Solidity 从入门到精通】第3章 Solidity 基础语法详解
web3·solidity
野老杂谈8 天前
【Solidity 从入门到精通】第2章 Solidity 语言概览与环境搭建
web3·区块链·智能合约·solidity·remix ide
野老杂谈9 天前
【Solidity 从入门到精通】前言
web3·智能合约·solidity·以太坊·dapp
许强0xq10 天前
Solidity 的十年与重生:从 Classic 到 Core
web3·区块链·智能合约·solidity·以太坊