OpenZeppelin分析二

Security源码解析

Posted by Emo on August 26, 2022

OpenZeppelin

EMO’s Blog

security

主要是实现了几个security相关的通用的安全设计模式

  • Pausable 可以让合约停止服务
  • PullPayment 不自动转账而是让用户来自己领取
  • ReentrancyGuard 防止回调重入

上面三个合约的代码和功能都很简单,就直接看看代码就行了。

Pausable

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    // 两个时间,暂停和解除暂停的时候抛出
    event Paused(address account);
    event Unpaused(address account);

    // 暂停状态标识符
    bool private _paused;

    // 初始化为未暂停
    constructor() {
        _paused = false;
    }

    // 修饰器,需要处在 未暂停状态 才能进入函数
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    // 需要处在 暂停状态 才能进入函数
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    // 查看暂停状态
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * 以下两个内部函数都是为上面的 modifier 服务的
     */

    // 需要处在 未暂停状态,否则抛出异常
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    // 需要处在 暂停状态,否则抛出异常
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * 下面两个函数都是internal的,所以外部无法访问,
     * 当我们自己设计合约要依赖这个合约的时候,
     * 我们需要自己设计外部函数来调用下面这两个内部函数以控制  _paused 的切换
     */

    // 修改状态为 暂停状态,并抛出 Paused事件
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    // 修改状态为 未暂停状态,并抛出 Unpaused事件
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

PullPayment

主要的功能是靠另一个合约 Escrow.sol 去实现的。需要注意的是 Escrow.sol 也继承了 Ownable.sol ,而 Escrow.sol 的owner就是 PullPayment.sol 的地址,因为 Escrow.sol 是在其内部创建的。

PullPayment.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/PullPayment.sol)

pragma solidity ^0.8.0;

import "../utils/escrow/Escrow.sol";

abstract contract PullPayment {
    Escrow private immutable _escrow;

    constructor() {
        _escrow = new Escrow();
    }

    // 提款
    function withdrawPayments(address payable payee) public virtual {
        _escrow.withdraw(payee);
    }

    // 查看 dest地址 里的余额
    function payments(address dest) public view returns (uint256) {
        return _escrow.depositsOf(dest);
    }

    // 给deposit里存钱的函数,这是个内部函数,想要用这个,需要在设计自己的合约的时候设计这个函数的使用方式
    function _asyncTransfer(address dest, uint256 amount) internal virtual {
        _escrow.deposit{value: amount}(dest);
    }
}

Escrow.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/escrow/Escrow.sol)

pragma solidity ^0.8.0;

// 做权限控制
import "../../access/Ownable.sol";
import "../Address.sol";

contract Escrow is Ownable {
    using Address for address payable;

    event Deposited(address indexed payee, uint256 weiAmount);
    event Withdrawn(address indexed payee, uint256 weiAmount);

    // 托管钱的地方
    mapping(address => uint256) private _deposits;

    // 查看 _deposits 中的余额
    function depositsOf(address payee) public view returns (uint256) {
        return _deposits[payee];
    }

    // 给 _deposits 里存钱,payable可接受转账,只有 owner 可以使用
    function deposit(address payee) public payable virtual onlyOwner {
        uint256 amount = msg.value; // 接收到金额
        _deposits[payee] += amount; // 存进_deposits里对应的账户里
        emit Deposited(payee, amount); // 抛出 Deposited事件
    }

    // 取钱的函数,只有owner可以调用
    function withdraw(address payable payee) public virtual onlyOwner {
        uint256 payment = _deposits[payee];

        _deposits[payee] = 0;

        payee.sendValue(payment); // 调用的是Address里的方法,里面包含了转钱的方法

        emit Withdrawn(payee, payment); // 抛出 Withdraw事件
    }
}

ReentrancyGuard

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

// 就是很常规的用Modifier在里面通过对标志状态的修改和判断来防止重入
abstract contract ReentrancyGuard {
    // 两种状态
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    // 当前状态
    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");    // 重入检测
        
        _status = _ENTERED;    // 更新状态,防止重入

        _;    // 执行函数
        
        _status = _NOT_ENTERED;    // 回退状态,等待下一次进入
    }
}

{ % if page.mermaid % } { % endif % }