【技术分析】简单了解 AccessControl

当我们开发一个智能合约,但是里面有一些函数不能随便让别人调用,只能"拥有权限"的管理员能够调用,那么这时候我们会用到权限管理机制。

实现起来也很简单,设置一个 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 种方案:

  1. Ownable:只要单一的 owner 权限,owner 权限可以转移,放弃(置为 0 地址)。
  2. Ownable2Step:在 Ownable 基础上避免了将 owner 权限转移到了错误的地址导致权限丢失,Ownable2Step 在权限转移这个环节,添加了接受权限的地址需要调用 acceptOwnership() 进行领取的步骤。
  3. AccessControl:不再采用单一的 owner 进行权限管理,而是更细粒度地细分到了多种权限账户。

在接下来的文章中会对 AccessControl 进行展开介绍。

AccessControl

代码实现:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.2.0/contracts/access/AccessControl.sol

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

权限角色管理

而当需要通过权限管理员来管理权限角色时:

  1. 通过 grantRole(bytes32 role, address account) 来授予 account 地址 role 权限角色。

  2. 通过 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
    }
}

这段代码实现了以下的功能:

  1. 设置默认管理员 DEFAULT_ADMIN_ROLE 为合约部署者 deployer
  2. 设置 address(0x02)MINTER_ROLE 权限角色
  3. 设置 address(0x01)MINTER_ROLE 权限的管理员 ADMIN_ROLE,可以移除或添加 MINTER_ROLE 权限角色。
  4. DEFAULT_ADMIN_ROLE 默认作为 ADMIN_ROLE 的管理员,可以移除或添加 ADMIN_ROLE 权限角色。
相关推荐
Blockchina1 天前
第八章 | 函数修饰符与访问控制模式
java·python·区块链·智能合约·solidity
Blockchina2 天前
第十二章 | Solidity 智能合约前后端集成实战
java·python·区块链·智能合约·solidity
Blockchina2 天前
第十一章 | 智能合约主网部署与验证详解
区块链·智能合约·编程语言·solidity·区块链开发
Blockchina2 天前
第三章 | 初识 Solidity:开发环境搭建 & 第一个智能合约{介绍篇}
区块链·智能合约·solidity
我是前端小学生9 天前
一文了解solidity中的常量、状态变量和不可改变量的区别
solidity
阿菜ACai9 天前
【技术分析】EIP-7702 场景下 EOA 授权签名的安全探讨
区块链·智能合约·solidity
yunteng5211 个月前
solidity之Foundry安装配置(一)
web3·区块链·solidity·foundry
杰哥的技术杂货铺2 个月前
Solidity07 常数 constant和immutable
区块链·智能合约·solidity·immutable·常数·constant
罗_三金2 个月前
(10)深入浅出智能合约OpenZeppelin开源框架
web3·区块链·智能合约·solidity·openzeppelin·dapp