Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solidity中的Ownable合约 #66

Open
MagicalBridge opened this issue Jun 22, 2024 · 0 comments
Open

Solidity中的Ownable合约 #66

MagicalBridge opened this issue Jun 22, 2024 · 0 comments

Comments

@MagicalBridge
Copy link
Owner

基本概念

在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。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant