当我们开发一个智能合约,但是里面有一些函数不能随便让别人调用,只能"拥有权限"的管理员能够调用,那么这时候我们会用到权限管理机制。
实现起来也很简单,设置一个 owner
变量,通过 modifier onlyOwner()
检查调用 onlyOwnerCanCall()
的地址是否等于预先设置好的 woner
,这样就可以保证只有 owner
能够调用这个函数。
contract AccessControl {
address private owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Caller is not the owner");
_;
}
function onlyOwnerCanCall() public onlyOwner {
// ...
}
}
由于权限控制这个机制被广泛应用在不同的场景中,为了避免重复开发以及保证实现质量,开发者可以从 openzeppelin 的标准库中使用 openzeppelin-contracts/contracts/access
目录下的标准库进行实现。
目前为 5.2.0 版本:https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v5.2.0/contracts/access
主要分为 3 种方案:
- Ownable:只要单一的 owner 权限,owner 权限可以转移,放弃(置为 0 地址)。
- Ownable2Step:在 Ownable 基础上避免了将 owner 权限转移到了错误的地址导致权限丢失,Ownable2Step 在权限转移这个环节,添加了接受权限的地址需要调用 acceptOwnership() 进行领取的步骤。
- AccessControl:不再采用单一的 owner 进行权限管理,而是更细粒度地细分到了多种权限账户。
在接下来的文章中会对 AccessControl 进行展开介绍。
AccessControl
AccessControl 中存储所有管理员信息的全局变量是 _roles
,其次是代表默认管理员的 DEFAULT_ADMIN_ROLE
。

权限检查
当需要对某个函数进行调用权限的限制时,可以通过添加 modifier onlyRole(bytes32 role)
的方式来检查 msg.sender
是否满足指定权限角色 ROLE
的要求。
function privilegedFunctions() public onlyRole(ROLE){...}
modifier onlyRole(bytes32 role)
会调用以下的函数来进行检查
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
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);
}
}
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
当用户调用需要 ROLE
权限的函数时,modifier onlyRole(bytes32 role)
会检查 _roles[ROLE].hasRole[mag.sender]
是否为 true
。

权限角色管理
而当需要通过权限管理员来管理权限角色时:
-
通过
grantRole(bytes32 role, address account)
来授予 account 地址 role 权限角色。 -
通过
revokeRole(bytes32 role, address account)
来移除 account 地址 role 权限角色。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 _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) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}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;
}
}
当需要添加 ROLE 的权限时,则需要通过 onlyRole(getRoleAdmin(role)
检查 _roles[_roles[role].adminRole].hasRole[mag.sender]
是否为 true
。也就是说每一个权限角色(ROLE)的管理员,都是对应着另一个权限角色(ROLE_ADMIN)。

默认管理员
那么权限角色有管理员,管理员还有他自己的管理员,那岂不是无穷尽也?这个时候就用到 DEFAULT_ADMIN_ROLE
角色了,由于它的值被设定为 0x00
,所以只要你不为某一个权限角色设置 adminRole
,那么 adminRole
的值就会指向 DEFAULT_ADMIN_ROLE(0x00)
。

应用实例
根据上面的内容,实现了一个简单的 Demo 协助读者理解(仅供参考,请勿用作生产代码)。
contract RoleDemo is AccessControl {
// Define roles
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() {
// Grant DEFAULT_ADMIN_ROLE to contract deployer
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
// Grant ADMIN_ROLE to contract deployer
_grantRole(ADMIN_ROLE, address(0x01));
// Grant MINTER_ROLE to contract deployer
_grantRole(MINTER_ROLE, address(0x02));
// Set ADMIN_ROLE as the admin role for MINTER_ROLE
_setRoleAdmin(MINTER_ROLE, ADMIN_ROLE);
}
// Function can only be called by addresses with DEFAULT_ADMIN_ROLE
function defultAdminFunction() public onlyRole(DEFAULT_ADMIN_ROLE) {
// Default admin function implementation
}
// Function can only be called by addresses with ADMIN_ROLE
function adminFunction() public onlyRole(ADMIN_ROLE) {
// Admin function implementation
}
// Function can only be called by addresses with MINTER_ROLE
function minterFunction() public onlyRole(MINTER_ROLE) {
// Minter function implementation
}
}
这段代码实现了以下的功能:
- 设置默认管理员
DEFAULT_ADMIN_ROLE
为合约部署者deployer
- 设置
address(0x02)
为MINTER_ROLE
权限角色 - 设置
address(0x01)
为MINTER_ROLE
权限的管理员ADMIN_ROLE
,可以移除或添加MINTER_ROLE
权限角色。 DEFAULT_ADMIN_ROLE
默认作为ADMIN_ROLE
的管理员,可以移除或添加ADMIN_ROLE
权限角色。