AccessControl库用于管理函数的调用权限,所有继承了AccessControl的子合约均可为自己的业务函数设置调用权限。AccessControl是一个轻量级的基础库且各个role不支持在编成员的迭代导出,所以授权和撤销role成员的操作会抛出event。
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
AccessControl库用于管理函数的调用权限,所有继承了AccessControl的子合约均可为自己的业务函数设置调用权限。AccessControl是一个轻量级的基础库且各个role不支持在编成员的迭代导出,所以授权和撤销role成员的操作会抛出event。role表示角色权限的种类,不同的bytes32表示不同的角色权限。各个role可以由自己的admin role在编地址管理本role的在编人员名单,并且合约内涉及的role应该由bytes32 public constant
暴露给外部。
继承AccessControl成为一个可调用合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/access/AccessControl.sol";
contract MockAccessControl is AccessControl {
constructor(){
// set msg.sender into admin role
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function setRoleAdmin(bytes32 role, bytes32 adminRole) external {
_setRoleAdmin(role, adminRole);
}
function doSomethingWithAccessControl(bytes32 role) onlyRole(role) external {}
}
全部foundry测试合约:
对外提供本合约是否实现了输入interfaceId标识的interface的查询功能。
注:此处重写了ERC165.supportsInterface(),即在全部支持的interface ids中加入IAccessControl
的interface id。ERC165.supportsInterface()的细节参见:https://learnblockchain.cn/article/6286
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
// 如果输入的interfaceId为IAccessControl或IERC165的interface id,返回true。否则返回false
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
foundry代码验证
contract AccessControlTest is Test {
MockAccessControl private _testing = new MockAccessControl();
function test_SupportsInterface() external {
// support IERC165 and IAccessControl
assertTrue(_testing.supportsInterface(type(IERC165).interfaceId));
assertTrue(_testing.supportsInterface(type(IAccessControl).interfaceId));
}
}
hasRole(bytes32 role, address account)
:返回输入account地址是否被授予了角色role的权限;getRoleAdmin(bytes32 role)
:返回输入role中的adminRole。adminRole用于管理对应role的权限授予与撤销;_setRoleAdmin(bytes32 role, bytes32 adminRole)
:为输入role设置对应的admin role。 // 每个role(bytes32)都对应唯一的RoleData
struct RoleData {
// 该role中用于记录授权地址的mapping
mapping(address => bool) members;
// 可管理上述mappings的管理role,默认为bytes32(0),即DEFAULT_ADMIN_ROLE
// 注:具体地说,_roles[adminRole].members中value为true的地址具有修改本RoleData.members的权限
bytes32 adminRole;
}
// 用于存放role与对应RoleData的mapping
mapping(bytes32 => RoleData) private _roles;
// 每个RoleData中默认的adminRole
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
// role作为key找到对应的RoleData.members,再用account作为key去得到value值
return _roles[role].members[account];
}
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
// role作为key找到对应的RoleData.adminRole并返回
return _roles[role].adminRole;
}
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
// 获取目前该role的admin role
bytes32 previousAdminRole = getRoleAdmin(role);
// 更新该role的admin role为输入的adminRole
_roles[role].adminRole = adminRole;
// 抛出事件
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
foundry代码验证
contract AccessControlTest is Test {
MockAccessControl private _testing = new MockAccessControl();
bytes32 immutable private ROLE_DEFAULT = 0;
bytes32 immutable private ROLE_1 = keccak256("ROLE_1");
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
function test_HasRole_GetRoleAdmin_SetRoleAdmin() external {
// deployer was granted default role
assertTrue(_testing.hasRole(ROLE_DEFAULT, address(this)));
// default admin of any role is bytes32(0)
assertEq(_testing.getRoleAdmin(ROLE_DEFAULT), 0);
assertEq(_testing.getRoleAdmin(ROLE_1), 0);
// change admin role by _setRoleAdmin()
bytes32 newAdminRole = keccak256("new admin role");
vm.expectEmit(true, true, true, false, address(_testing));
emit RoleAdminChanged(ROLE_1, 0, newAdminRole);
_testing.setRoleAdmin(ROLE_1, newAdminRole);
assertEq(_testing.getRoleAdmin(ROLE_1), newAdminRole);
}
}
grantRole(bytes32 role, address account)
:授予地址account关于输入role的权限。只有具有输入role的adminRole权限的地址才可调用该方法,否则revert。 注:如果输入的account原先不具备输入role的权限,会授权并抛出事件RoleGranted;_setupRole(bytes32 role, address account)
:将输入role的权限授予给地址account。如果输入的account原先不具备role的权限,会授权并抛出事件RoleGranted。注:该函数不同于函数grantRole()的地方为:不会去验证调用方身份。所以本函数只用于constructor函数中为系统初始化对应role角色的权限。请不要在合约其他地方调用此函数。目前该函数已弃用
,请使用_grantRole(bytes32 role, address account)。 function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
// onlyRole(getRoleAdmin(role))是对_msgSender()具有role的adminRole的权限进行检查,而后直接调用_grantRole()方法为account授予输入role的权限
_grantRole(role, account);
}
function _setupRole(bytes32 role, address account) internal virtual {
// 直接调用_grantRole(),所以本internal函数等价于_grantRole()
_grantRole(role, account);
}
// 将输入role的权限授予给地址account。如果输入的account原先不具备role的权限,会授权并抛出事件RoleGranted,否则不做任何操作。
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
// 如果输入account不具有role的权限,在该role对应roleData.member中对account标记为true
_roles[role].members[account] = true;
// 抛出事件
emit RoleGranted(role, account, _msgSender());
}
}
foundry代码验证
contract AccessControlTest is Test {
MockAccessControl private _testing = new MockAccessControl();
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 {
address account = address(1024);
assertFalse(_testing.hasRole(ROLE_DEFAULT, account));
vm.expectEmit(true, true, true, false, address(_testing));
emit RoleGranted(ROLE_DEFAULT, account, address(this));
_testing.grantRole(ROLE_DEFAULT, account);
assertTrue(_testing.hasRole(ROLE_DEFAULT, account));
// grant role for ROLE_1
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));
}
function testFail_GrantRole_NoEventWhenGrantAgain() external {
address account = address(1024);
_testing.grantRole(ROLE_DEFAULT, account);
// no emit event if grant again
vm.expectEmit(true, true, true, false, address(_testing));
emit RoleGranted(ROLE_DEFAULT, account, address(this));
_testing.grantRole(ROLE_DEFAULT, account);
}
}
撤销地址account关于输入role的权限。只有具有输入role的adminRole权限的地址才可调用该方法,否则revert。
注:如果输入的account原先已具备role的权限,会撤销权限并抛出事件RoleRevoked。
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
// onlyRole(getRoleAdmin(role))是对_msgSender()具有role的adminRole的权限进行检查,而后直接调用_revokeRole()方法为account撤销输入role的权限
_revokeRole(role, account);
}
// 撤销已授予地址account关于输入role的权限。如果输入的account原先已具备role的权限,会撤销权限并抛出事件RoleRevoked,否则不做任何操作。
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
// 如果输入account具有role的权限,在该role对应roleData.member中对account标记为false
_roles[role].members[account] = false;
// 抛出事件
emit RoleRevoked(role, account, _msgSender());
}
}
foundry代码验证
contract AccessControlTest is Test {
MockAccessControl private _testing = new MockAccessControl();
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 {
address account = address(1024);
_testing.grantRole(ROLE_DEFAULT, account);
assertTrue(_testing.hasRole(ROLE_DEFAULT, account));
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));
// revoke role for ROLE_1
_testing.grantRole(ROLE_1, account);
assertTrue(_testing.hasRole(ROLE_1, account));
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));
}
function testFail_RevokeRole_NoEventWhenRevokeAgain() external {
address account = address(1024);
_testing.grantRole(ROLE_DEFAULT, account);
_testing.revokeRole(ROLE_DEFAULT, account);
// no emit event if revoke again
vm.expectEmit(true, true, true, false, address(_testing));
emit RoleRevoked(ROLE_DEFAULT, account, address(this));
_testing.revokeRole(ROLE_DEFAULT, account);
}
}
account账户自己主动放弃关于输入role的权限。要求必须由account地址来调用该函数。
注:如果输入的account原先已具备role的权限,会撤销权限并抛出事件RoleRevoked。
function renounceRole(bytes32 role, address account) public virtual override {
// 要求_msgSender()为输入account地址
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
// 撤销account关于输入role的权限
_revokeRole(role, account);
}
foundry代码验证
contract AccessControlTest is Test {
MockAccessControl private _testing = new MockAccessControl();
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_RenounceRole() external {
address account = address(1024);
_testing.grantRole(ROLE_DEFAULT, account);
assertTrue(_testing.hasRole(ROLE_DEFAULT, account));
vm.prank(account);
vm.expectEmit(true, true, true, false, address(_testing));
emit RoleRevoked(ROLE_DEFAULT, account, account);
_testing.renounceRole(ROLE_DEFAULT, account);
assertFalse(_testing.hasRole(ROLE_DEFAULT, account));
// renounce role for ROLE_1
_testing.grantRole(ROLE_1, account);
assertTrue(_testing.hasRole(ROLE_1, account));
vm.prank(account);
vm.expectEmit(true, true, true, false, address(_testing));
emit RoleRevoked(ROLE_1, account, account);
_testing.renounceRole(ROLE_1, account);
assertFalse(_testing.hasRole(ROLE_1, account));
// case 1: revert if account != _msgSender()
vm.expectRevert("AccessControl: can only renounce roles for self");
_testing.renounceRole(ROLE_DEFAULT, account);
}
function testFail_RenounceRole_NoEventWhenRenounceAgain() external {
_testing.renounceRole(ROLE_DEFAULT, address(this));
// no emit event if renounce again
vm.expectEmit(true, true, true, false, address(_testing));
emit RoleRevoked(ROLE_DEFAULT, address(this), address(this));
_testing.renounceRole(ROLE_DEFAULT, address(this));
}
}
modifier onlyRole(bytes32 role)用于确认当前_msgSender()已被授予了角色role的权限。否则,直接以格式化字符串信息revert。
modifier onlyRole(bytes32 role) {
// 调用_checkRole()进行_msgSender()权限检查
_checkRole(role);
_;
}
// 确认_msgSender()已被授予了角色role的权限。否则,直接以格式化字符串信息revert
function _checkRole(bytes32 role) internal view virtual {
// 调用_checkRole()来确认_msgSender()已被授予了角色role的权限
_checkRole(role, _msgSender());
}
// 确认输入account地址已被授予了角色role的权限。否则,直接以格式化字符串信息revert
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
// 如果account未被授予角色role的权限,则以格式化字符串信息revert
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
foundry代码验证
contract AccessControlTest is Test {
MockAccessControl private _testing = new MockAccessControl();
bytes32 immutable private ROLE_DEFAULT = 0;
bytes32 immutable private ROLE_1 = keccak256("ROLE_1");
function test_onlyRole() external {
// test for modifier onlyRole
address account = address(1024);
// test for default role
// 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神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!