用法实例
AccessControl合约提供了权限管理功能,可以进行分组权限管理,可用实例如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
contract MyAcess is AccessControl{
//定义具备对账户进行各个身份组添加、去除的超级身份,需要设置为各个身份组的的admin
bytes32 public immutable SuperRole;
//定义身份组,用于限定某些函数只能被在该身份组下的有效账户调用
bytes32 public immutable ChangeNameRole;
//定义身份组,用于限定某些函数只能被在该身份组下的有效账户调用
bytes32 public immutable ChangeAgeRole;
string public name;
uint public age;
constructor() {
name="default";
age=10;
SuperRole=generateRole("SuperRole");
//为超级用户组自身指定admin
_setRoleAdmin(SuperRole,SuperRole);
//为超级用户组设置初始账户,否则创建之后,没有办法对任何账户进行身份组添加
_grantRole(SuperRole,msg.sender);
ChangeNameRole=generateRole("changeNameRole");
//为“改名”组指定管理员为超级组
_setRoleAdmin(ChangeNameRole,SuperRole);
//_grantRole(ChangeNameRole,msg.sender);
ChangeAgeRole=generateRole("ChangeAgeRole");
//为“改年龄”组指定管理员为超级组
_setRoleAdmin(ChangeAgeRole,SuperRole);
//_grantRole(ChangeAgeRole,msg.sender);
}
function generateRole(string memory arg) public pure returns(bytes32 role)
{
role=keccak256(abi.encodePacked(arg));
}
//通过onlyRole(ChangeNameRole)限定只有在“改名”组中的账户才有权限操作该函数
function changeName(string memory _name) public onlyRole(ChangeNameRole){
name=_name;
}
//通过onlyRole(ChangeAgeRole)限定只有在“改年龄”组中的账户才有权限操作该函数
function changeAge(uint _age) public onlyRole(ChangeAgeRole){
age=_age;
}
}
合约部署后对外暴露的接口为:
解释下继承自AccessControl的几个函数:
1、grantRole:向指定身份组授权账户地址,使得该账户地址后续可以调用约束于该身份组才能访问的函数,操作该方法的地址要在该身份的管理员身份组账户列表中;
2、renounceRole:解除自身账户地址在某个身份组中的授权,不检查是否为管理员组用户,用于紧急接触自身授权;
3、revoleRole:从指定身份组接触账户地址授权,操作该方法的地址要在该身份的管理员身份组账户列表中;
4、DEFAULT_ADMIN_ROLE:所有身份组的默认管理员id,如果身份组的管理员id未经初始化,其默认值是0,所以DEFAULT_ADMIN_ROLE的值为0x0000000000000000000000000000000000000000000000000000000000000000;
5、getRoleAdmin:获得指定身分组的管理员身份id;
6、hasRole:检查指定账户是否在指定身份组中;
说明下上述合约中的用法:
在合约初始化阶段,生成三个身份组,1)“管理员组”(SuperRole),处于该组中的账户具有授权、接触授权关联身份组账户;2)“改名组”(ChangeNameRole),后续通过modifier限定,使得只有该身份组中的地址可以调用changeName;3)“改年龄组”(ChangeAgeRole),,后续通过modifier限定,使得只有该身份组中的地址可以调用changeAge。然后令“改名组”、“改年龄组”的管理员关联“管理员组”。最后,在changeName函数上添加onlyRole(ChangeNameRole)修饰符,实现只有在“改名组”的账户可以调用该函数;在changeAge函数上添加onlyRole(ChangeAgeRole)修饰符,实现只有在“改年龄组”的账户可以调用该函数。
假设部署合约的地址为address_deploy,初始阶段,address_deploy无法调用changeName和changeAge,执行下grantRole(ChangeNameRole,address_deploy),再次调用,发现changeName调用成功,changeAge调用失败。
代码详解
先看状态变量:
//存储某个身份组中的有效账户,以及管理员身份id
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
//存储多个身份组
mapping(bytes32 role => RoleData) private _roles;
//默认管理员身份id
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
修饰符onlyRole,用于约束权限在指定身份组:
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
来看下是如何实现的身份组约束:
//检查某个身份组中是否对指定账户进行了授权
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
//获取当前合约的调用地址,然后继续检查该地址是否在指定身分组
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
//检查指定该地址是否在指定身分组
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
还有一些身份组管理方法:
//获取指定身份组的管理员身份id
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
//对指定身份组进行账户授权,要求操作该方法的地址必须存在于该身份的管理员组账户列表中
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
//对指定身份组进行账户授权解除,要求操作该方法的地址必须存在于该身份的管理员组账户列表中
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
//接触自身账户对指定身份组的授权,不验证权限,用于紧急接触,比如钱包地址暴露
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
//内部方法,为指定身份组设置管理员身份id
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
//内部方法,授权账户
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
//内部方法,接触授权
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}