Solidity中Ownable合约的原理实践

  • Louis
  • 更新于 2024-06-21 15:19
  • 阅读 1020

在Solidity中,Ownable合约是一种设计模式,用于管理合约的所有权。它通常提供了一些基础功能,如只允许合约所有者执行某些操作,转移合约所有权等;这种权限管理合约在以太坊主网或者其他链的主网上经常会看到。

基本概念

在Solidity中,Ownable 合约是一种设计模式,用于管理合约的所有权。它通常提供了一些基础功能,如只允许合约所有者执行某些操作,转移合约所有权等;这种权限管理合约在以太坊主网或者其他链的主网上经常会看到。

实现思路、步骤

我们需要声明一个叫做 Ownable 的合约,这个合约中,有一个状态变量,表示的是合约的所有权属于谁。这个状态变量会在合约部署的时候在构造函数中初始化, 我们并不希望外部直接访问这个变量,所以使用 private修饰符。

为了逻辑复用考虑,我们需要封装一个函数,作用是转移相关权限,这个函数在初始化的时候调用,在后续需要更改合约所有权的时候也需要调用。

基于上面的基础描述,我们用代码实现一下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Ownable {

    address private _owner;

    constructor() {
        _transferOwnership(msg.sender);
    }

    function _transferOwnership(address newOwner) internal {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }

}

从上面的实现可以看到,_transferOwnership 函数被 internal 修饰,意思是不愿意被外部调用。我们需要封装一个外部可访问的函数,来调用这个内部函数, 并且,我们还需要设计一个 modifier 来限制只有合约的权限管理者才能调用这个方法。

基于这个思路,我们补充完善一下代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Ownable {

    address private _owner;

    constructor() {
        _transferOwnership(msg.sender);
    }

    modifier onlyOwner() {
        require(owner() == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    function transferOwnership(address newOwner) public onlyOwner {
        // 这里需要注意,新的地址不能是0地址,否则权限就会被永久锁死了
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    function _transferOwnership(address newOwner) internal {
        address oldOwner = _owner;
        _owner = newOwner;
    }

}

在实际的使用场景中,会有一个需求,就是合约的所有者主动放弃所有权,如果想要实现这个功能,直接将自己的所有权转让给0地址。基于这个思路,我们可以设计一个 renounceOwnership 方法。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Ownable {

    address private _owner;

    constructor() {
        _transferOwnership(msg.sender);
    }

    modifier onlyOwner() {
        require(owner() == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    function renounceOwnership() public onlyOwner {
        _transferOwnership(address(0));
    }

    function transferOwnership(address newOwner) public onlyOwner {
        // 这里需要注意,新的地址不能是0地址,否则权限就会被永久锁死了
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    function _transferOwnership(address newOwner) internal {
        address oldOwner = _owner;
        _owner = newOwner;
    }

}

可以看到 renounceOwnership 也是只有合约所有者才可以调用,并且直接调用底层的 _transferOwnership 函数,绕过了0地址的检查。

最后,我们再提供一个函数来查询最新的合约所有者的地址, 还需要添加一个事件,当合约所有者权限更改的时候,我们抛出一个事件。这样一个完整的合约就实现了。

完整代码示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/**
 * @title Ownable
 * @dev Ownable合约有一个owner地址类型的状态变量,提供了基础的权限管理函数
 */
contract Ownable {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev 初始化,将合约的部署者设置为合约的拥有者
     */
    constructor() {
        _transferOwnership(msg.sender);
    }

    /**
     * @dev 返回当前的合约所有者
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev 不是合约拥有者调用的时候抛出异常
     */
    modifier onlyOwner() {
        require(owner() == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev 当前所有者可以调用这个函数放弃所有权,这会将合约的所有者设置为零地址(`address(0)`)
     */
    function renounceOwnership() public onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev 转移合约拥有权到一个新的地址,只能被合约拥有者调用
     */
    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * 内部函数,没有权限校验
     */
    function _transferOwnership(address newOwner) internal {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

代码说明

  1. 合约初始化:在合约的构造函数中,设置部署合约的地址为合约的初始所有者。
  2. 所有者地址:使用私有变量 _owner 存储当前合约的所有者地址。
  3. 所有权转移事件:定义了 OwnershipTransferred 事件,当所有权转移时,会记录之前的所有者和新的所有者。
  4. 只允许所有者执行的修饰符:定义了一个 onlyOwner 修饰符,用于限制只有当前合约的所有者可以调用某些函数。
  5. 查看当前所有者:提供了 owner 函数,返回当前所有者的地址。
  6. 放弃所有权:提供了 renounceOwnership 函数,当前所有者可以调用这个函数放弃所有权,这会将合约的所有者设置为零地址(address(0))。
  7. 转移所有权:提供了 transferOwnership 函数,当前所有者可以将合约的所有权转移给新的地址。新地址不能是零地址。
  8. 内部所有权转移函数:使用 _transferOwnership 内部函数执行实际的所有权转移操作,并触发所有权转移事件。

总结

我们熟知的OpenZeppelin提供了最广泛使用和高度可信赖的权限管理工具,包括 OwnableAccessControl。可以帮助你轻松实现智能合约中的角色和权限管理。

此外,其他一些库如ConsenSys和DappSys也提供了有用的权限管理解决方案,但它们的使用范围和知名度不如OpenZeppelin。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
该文章收录于 Solidity从入门到进阶
5 订阅 22 篇文章

0 条评论

请先 登录 后评论
Louis
Louis
web3 developer,技术交流或者有工作机会可加VX: magicalLouis