Michael.W基于Foundry精读Openzeppelin第35期——Ownable.sol

  • Michael.W
  • 更新于 2023-09-18 20:45
  • 阅读 1178

Ownable库提供了一种基本的访问控制机制——设置一个owner具有对某些函数特殊的访问权限。通常owner就是本合约的deployer。合约部署后可通过函数transferOwnership()进行owner的修改。本库还提供了modifier onlyOwner,用于为函数限定访问权限。

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 Ownable.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/access/Ownable.sol

Ownable库提供了一种基本的访问控制机制——设置一个owner具有对某些函数特殊的访问权限。通常owner就是本合约的deployer。合约部署后可通过函数transferOwnership()进行owner的修改。本库还提供了modifier onlyOwner,用于为函数限定访问权限。

1. 目标合约

继承Ownable成为一个可调用合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/access/MockOwnable.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/access/Ownable.sol";

contract MockOwnable is Ownable {
    uint public i;

    function checkOwner() external view {
        _checkOwner();
    }

    function transferOwnershipInternal(address newOwner) external {
        _transferOwnership(newOwner);
    }

    function setI(uint value) external onlyOwner {
        i = value;
    }
}

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/access/Ownable.t.sol

2. 代码精读

2.1 _checkOwner() && owner() && modifier onlyOwner()

  • _checkOwner():检查当前_msgSender()是否为合约的owner;
  • owner():返回当前合约的owner地址;
  • modifier onlyOwner():用于对外函数访问权限限制的modifier。访问权限:只有当前合约的owner才具有访问权限。
    // 当前合约的owner地址
    address private _owner;

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

    // 合约部署时,默认将deployer设置为本合约的owner
    constructor() {
        _transferOwnership(_msgSender());
    }

    modifier onlyOwner() {
        // 检查当前_msgSender()是否为合约owner,如果不是直接revert
        _checkOwner();
        _;
    }

    function owner() public view virtual returns (address) {
        // 直接返回状态变量_owner
        return _owner;
    }

    function _checkOwner() internal view virtual {
        // 检查当前_msgSender()是否为合约的owner,如果不是直接revert
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

foundry代码验证

contract OwnableTest is Test {
    MockOwnable private _testing;

    function setUp() external {
        _testing = new MockOwnable();
    }

    function test_CheckOwnerAndOnlyOwner() external {
        assertEq(address(this), _testing.owner());
        // test for internal function: _checkOwner
        // case 1: pass
        _testing.checkOwner();
        // case 2: revert if the msgSender() is not owner
        vm.prank(address(1024));
        vm.expectRevert("Ownable: caller is not the owner");
        _testing.checkOwner();

        // test for modifier: onlyOwner
        // case 1: pass the check of modifier
        assertEq(0, _testing.i());
        _testing.setI(1024);
        assertEq(1024, _testing.i());
        // case 2: revert if the msgSender() is not owner
        vm.prank(address(1024));
        vm.expectRevert("Ownable: caller is not the owner");
        _testing.setI(1024);
    }
}

2.2 _transferOwnership(address newOwner) && transferOwnership(address newOwner)

  • _transferOwnership(address newOwner):转移本合约owner身份给一个新的地址。注:该internal函数没有对访问权限的限制;
  • transferOwnership(address newOwner):当前合约owner将owner身份转移给新地址newOwner。
    function transferOwnership(address newOwner) public virtual onlyOwner {
        // modifier onlyOwner对_msgSender()进行检查(必须为当前合约owner)
        // 检查新owner地址不可为0地址,否则revert
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        // 调用_transferOwnership()将newOwner设置为合约的owner
        _transferOwnership(newOwner);
    }

    function _transferOwnership(address newOwner) internal virtual {
        // 合约当前owner地址存储到局部变量oldOwner中
        address oldOwner = _owner;
        // 将_owner状态变量设置为新的owner地址
        _owner = newOwner;
        // 抛出event
        emit OwnershipTransferred(oldOwner, newOwner);
    }

foundry代码验证

contract OwnableTest is Test {
    MockOwnable private _testing;

    function setUp() external {
        _testing = new MockOwnable();
    }

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

    function test_TransferOwnership() external {
        // test for public function: transferOwnership
        assertEq(address(this), _testing.owner());
        // case 1: pass
        vm.expectEmit(true, true, false, false, address(_testing));
        emit OwnershipTransferred(address(this), address(1024));
        _testing.transferOwnership(address(1024));
        assertEq(address(1024), _testing.owner());
        // case 2: revert if new owner is 0
        vm.prank(address(1024));
        vm.expectRevert("Ownable: new owner is the zero address");
        _testing.transferOwnership(address(0));

        // test for internal function: _transferOwnership
        // case 1: pass with any address of new owner
        vm.expectEmit(true, true, false, false, address(_testing));
        emit OwnershipTransferred(address(1024), address(2048));
        _testing.transferOwnershipInternal(address(2048));
        assertEq(address(2048), _testing.owner());
        vm.expectEmit(true, true, false, false, address(_testing));
        emit OwnershipTransferred(address(2048), address(0));
        _testing.transferOwnershipInternal(address(0));
        assertEq(address(0), _testing.owner());
    }
}

2.3 renounceOwnership()

当前合约owner在不设置新owner的条件下主动放弃owner身份。

注:当执行该函数后,合约内所有被onlyOwner修饰的函数都将无法被任何人调用。

    function renounceOwnership() public virtual onlyOwner {
        // modifier onlyOwner对_msgSender()进行检查(必须为当前合约owner)
        // 将合约owner身份转移给零地址
        _transferOwnership(address(0));
    }

foundry代码验证

contract OwnableTest is Test {
    MockOwnable private _testing;

    function setUp() external {
        _testing = new MockOwnable();
    }

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

    function test_RenounceOwnership() external {
        assertEq(address(this), _testing.owner());
        vm.expectEmit(true, true, false, false, address(_testing));
        emit OwnershipTransferred(address(this), address(0));
        _testing.renounceOwnership();
        assertEq(address(0), _testing.owner());
    }
}

ps: 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

公众号名称:后现代泼痞浪漫主义奠基人

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Michael.W
Michael.W
0x93E7...0000
狂热的区块链爱好者