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


    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    全部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");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    2.3 renounceOwnership()

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

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

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

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

  • 相关阅读:
    配置mysql8.0.27教程以及注意事项
    Ubuntu部分实用工具安装记录
    面试__编程
    WordPress如何删除前端评论中的网址字段?
    Linux学习:初识Linux
    剑指 Offer 37. 序列化二叉树
    网络层常见协议——IPV4、IPV6、ARP、ICMP、QoS
    HarmonyOS 远端状态订阅开发实例
    嵌入式Qt-实现两个窗口的切换
    shell实现文件自动归档功能
  • 原文地址:https://blog.csdn.net/michael_wgy_/article/details/132996069