Michael.W基于Foundry精读Openzeppelin第38期——AccessControlEnumerable.sol
- 0. 版本
- 0.1 AccessControlEnumerable.sol
- 1. 目标合约
- 2. 代码精读
- 2.1 supportsInterface(bytes4 interfaceId)
- 2.2 _grantRole(bytes32 role, address account)
- 2.3 _revokeRole(bytes32 role, address account)
- 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: 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()的细节参见:
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。否则返回falsereturn interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);}
foundry代码验证
contract AccessControlEnumerableTest is Test {MockAccessControlEnumerable private _testing = new MockAccessControlEnumerable();function test_SupportsInterface() external {// support IERC165 && IAccessControl && IAccessControlEnumerableassertTrue(_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_DEFAULTaddress account = address(1024);assertFalse(_testing.hasRole(ROLE_DEFAULT, account));// deployer (address of AccessControlEnumerableTest) is already inassertEq(_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 rolevm.prank(address(0));vm.expectRevert("AccessControl: account 0x0000000000000000000000000000000000000000 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000");_testing.grantRole(ROLE_DEFAULT, account);// case 2: grant role for ROLE_1assertEq(_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 rolevm.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_DEFAULTaddress 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 rolevm.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 rolevm.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 onlyRoleaddress account = address(1024);// test for ROLE_DEFAULT// passassertTrue(_testing.hasRole(ROLE_DEFAULT, address(this)));_testing.doSomethingWithAccessControl(ROLE_DEFAULT);// case 1: revertassertFalse(_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: revertassertFalse(_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神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人