Openzeppelin学习:Ownable & AccessControl 源码学习

Ownable源码源码概览功能说明onlyOwner修饰器控制谁能调用敏感函数transferOwnership将权限转让给别人renounceOwnership放弃控制权,实现去中心化OwnableInvalidOwner/Unau

Ownable 源码

源码概览

功能 说明
onlyOwner 修饰器 控制谁能调用敏感函数
transferOwnership 将权限转让给别人
renounceOwnership 放弃控制权,实现去中心化
OwnableInvalidOwner/UnauthorizedAccount 更清晰错误信息与 gas 优化
构造传参方式 支持更灵活的部署方式(如代理部署)

源码解析

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * Ownable 是一个抽象合约,不能直接部署
 */
abstract contract Ownable is Context {
    // _owner  私有变量存储合约当前的拥有者地址
    address private _owner;

    // 自定义错误:非 owner 账户调用了仅限 owner 的方法
    error OwnableUnauthorizedAccount(address account);

    //自定义错误:表示 newOwner 是无效地址(如 `address(0)`)
    error OwnableInvalidOwner(address owner);

    // 记录 ownership 转移历史,对前端/链上分析友好
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * 设置初始owner:在构造函数中传入初始 owner,不再自动使用 `msg.sender`
     * 可以支持更灵活的部署模式(如代理合约部署时设定 owner)
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * onlyOwner 修饰器
     * 为函数添加访问权限:仅限 owner 执行
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * 返回当前合约 owner
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * 用于 `onlyOwner` 检查当前调用者是否为 owner
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * 放弃所有权
     * 1. 当前 owner 可以主动放弃权限,将 owner 设置为 address(0)
     * 2.常用于让合约去中心化(不可控)后彻底解绑 owner 权限
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * 转让所有权
     * 1. 只有当前owner可以调用
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        // 防止转给无效地址
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * 执行转移并触发事件
     * 1. 支持继承时重写
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

AccessControl

源码概览

特性 说明
灵活的角色系统 任意角色都可以被定义与管理,支持多层级权限结构。
模块化设计 _grantRole / _revokeRole / _setRoleAdmin 提供灵活可扩展的逻辑。
安全性考虑 renounceRole 设计可防止误调用或攻击者冒充操作。
事件追踪 所有角色变更都会记录事件,便于链下索引与审计。
接口兼容性 通过 ERC165 提供合约接口发现能力。

源码分析

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol)

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";

/**
 * AccessControl 是 OpenZeppelin 提供的一种 "基于角色的权限控制系统"
 * 支持为任意角色定义管理员角色,可以非常灵活地为不同功能设置不同权限
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    /**
    * RoleData:表示一个角色的数据结构。
    *     hasRole: 记录哪些地址拥有该角色。
    *     adminRole: 管理该角色的管理员角色
    */
    struct RoleData {
        mapping(address account => bool) hasRole;
        bytes32 adminRole;
    }
    // 主状态变量,存储所有角色及其状态
    mapping(bytes32 role => RoleData) private _roles;
   // 默认的管理员角色,管理其他所有角色,自己也是自己的管理员
    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * 函数修饰器
     * 所有需要角色权限的函数使用的修饰器
     * 调用 `_checkRole` 来判断调用者是否具有该角色
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * 支持 ERC165 接口,用于合约间兼容性检查
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * 角色判断函数
     */
    function hasRole(bytes32 role, address account) public view virtual returns (bool) {
        return _roles[role].hasRole[account];
    }

    /**
     * 检查当前合约调用者是否拥有role
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * 检查某个 account 是否拥有 role
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

    /**
     * 获取角色的管理员角色(谁能授予和撤销它)
     */
    function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * 授予角色
     * 1. 只能由 role 的管理员调用
     * 2. 底层使用 `_grantRole`
     */
    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);
    }

    /**
     *  设置角色管理员
     *  可改变角色的管理员角色(例如多层管理)
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * 授予角色
     * 如果账号尚未拥有该角色,写入状态,并发出 RoleGranted 事件
     */
    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;
        }
    }

    /**
     *  撤销角色(与_grandRole 对称)
     */
    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;
        }
    }
}

AccessControl的思考

问题1:角色的创建和删除?

疑问:在AccessControl 源码通篇讲到了角色的授予、撤销等,并没有讲到角色的创建、删除?这些逻辑又是如何处理的!

角色创建

AccessControl 的设计中,其实并没有明确的 “创建角色” 的函数。角色的创建是隐式的,也就是说:

只要某个角色的 bytes32 被使用了,它就被认为是“存在”的角色。

具体表现如下:

调用 grantRole(role, account) 之前:

  • _roles[role] 不存在(mapping 的默认值),但不会报错

  • 调用 grantRole(role, account) 时:

    • getRoleAdmin(role) 会读取默认的 adminRole(为0)。
    • _grantRole() 会为该角色映射 hasRole[account] = true
    • 此时就相当于“创建”了该角色。

📌 也就是说:

bytes32 public constant EDITOR_ROLE = keccak256("EDITOR_ROLE");

grantRole(EDITOR_ROLE, someUser); // 这就是“创建” + “赋予”
角色删除

OpenZeppelin 的 AccessControl 并没有提供“删除角色” 的功能。原因主要有两个:

  1. Solidity 中 mapping 无法删除 key,只能“重置值”

例如:

_roles[role].hasRole[account] = false; // 只是撤销,不是删除整个角色
  1. 权限控制是基于事件日志状态检查
  • 如果某个角色没有人拥有,那它就形同“无效”;
  • 如果你不再使用该 roleonlyRole(role) 修饰器,它就不会产生任何影响;
  • 所以不需要显式 “删除角色”

问题2:管理员角色

追问:从未出现过的角色第一次调用 grantRole(role, account),这个调用者是否默认就是该角色的“管理员”?

答:不是 任何人都不能直接授予一个新角色,除非他已经拥有该角色的“管理员角色”。

默认情况下,新角色的管理员是 DEFAULT_ADMIN_ROLE(即 0x00),只有拥有该 DEFAULT_ADMIN_ROLE 的账户,才有权限调用 grantRole

问题3:管理员角色设置

追问:所有角色默认管理员角色都是 DEFAULT_ADMIN_ROLE_setRoleAdmin(role, newAdminRole)internal 方法,是否任何人都能设置,会不会造成混乱?

_setRoleAdmin() 是 internal 方法

function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual
  • 说明:它不能被外部用户直接调用,只有你自己在合约代码里显式调用它才行
  • 所以,不存在“任何人都能设置”的问题。
  • 它的设计目标是:在合约部署初始化或特定场景下由开发者调用,用于构建复杂的权限结构。

问题4: 角色怎么有了层级结构?

追问:从上面的内容分析看,角色是没有层级结构的,为什么说可以设计多级角色体系?

表面上看,OpenZeppelin 的 AccessControl 的确没有强制的角色层级结构:角色本身是平级的,但其实它是支持通过 “角色的管理员角色”机制手动构建“软层级”结构的。

层级例子
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
bytes32 public constant SUPER_ADMIN = keccak256("SUPER_ADMIN");
bytes32 public constant ADMIN = keccak256("ADMIN");
bytes32 public constant WRITER = keccak256("WRITER");
bytes32 public constant READER = keccak256("READER");

// 你可以这样初始化
constructor() {
    _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    _grantRole(SUPER_ADMIN, msg.sender);

    // 构建“软层级”结构
    _setRoleAdmin(SUPER_ADMIN, DEFAULT_ADMIN_ROLE); // msg.sender 可管 SUPER_ADMIN
    _setRoleAdmin(ADMIN, SUPER_ADMIN);              // SUPER_ADMIN 可管 ADMIN
    _setRoleAdmin(WRITER, ADMIN);                   // ADMIN 可管 WRITER
    _setRoleAdmin(READER, ADMIN);                   // ADMIN 可管 READER

    // 如此:就构建出了一个:四层的权限结构树
}
和 RBAC 系统对比
系统设计方式 是否强制层级 是否支持多级 授权是否自动传递 管理方式
AccessControl ❌ 否 ✅ 可构建 ❌ 不自动继承权限 手动设置 _setRoleAdmin
RBAC(比如 RBAC2) ✅ 有角色层级 ✅ 有继承 ✅ 权限自动继承 声明式结构
总结
现象 解答
所有角色是否是平级? ✅ 是的,默认平级没有强约束的层级系统
能不能构建层级结构? ✅ 可以!通过 _setRoleAdmin 手动构建“管理层级”
角色权限是否自动继承? ❌ 不会,需要你自己写逻辑来授权控制
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Henry Wei
Henry Wei
Web3 探索者