Michael.W基于Foundry精读Openzeppelin第38期——AccessControlEnumerable.sol
0. 版本
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
0.1 AccessControlEnumerable.sol
AccessControlEnumerable库用于管理函数的调用权限,是AccessControl库的拓展版。与AccessControl库相比,AccessControlEnumerable支持在编成员的迭代导出,这大大方便了各个角色权限的统计查询(不用通过扫块追溯events来统计目前各角色的在编权限人员的地址)。
1. 目标合约
继承AccessControlEnumerable成为一个可调用合约:
// 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测试合约:
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);
}
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神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人