智能合约的细粒度暂停

  • A+
所属分类:以太坊ETH

chatGPT账号

智能合约的细粒度暂停

0x01 为什么需要暂停功能

当一个协议有下面这些考虑时,一般就需要添加暂停功能了:

  1. 协议本身有一定的中心化属性
    比如大部分中间人机制的跨链桥合约,RWA 这种需要链上链下互动的合约,都离不开一些偏中心化角色的参与。既然有参与的权利,就要为资金安全承担一定的责任,暂停功能可以在合约出现问题时起一定的防护作用。
  2. 协议未来有持续演进升级的需要
    升级过程中有可能需要对某些功能进行暂停。
  3. 安全协作需要
    笔者参与的一些项目,资金大户有明确需求,他们可以监控链上状态,当发现有异常的时候,可以直接使用暂停功能来暂停协议的运行。

0x02 常见的暂停功能设计

用的最多的就是 OpenZeppelin 提供的 Pausable.sol 模版了。很多时候这个模版已经很好用了,但当协议变的比较复杂之后,这个模版有个最大的限制就出来了:粒度比较粗。一旦设置了暂停,就是全局暂停,意味着所有使用 whenPaused 这个 modifier 修饰的函数都将无法调用。但有的时候我们需要更细粒度的控制,比如对于借贷协议来说,某种情况下只需要暂停借款,某种情况下只需要暂停某个借贷池,如果没有一个通用的设计,就需要在业务层进入侵入性设计,将暂停功能和业务功能混在一起。

0x03 细粒度暂停

最近看到这篇文章 提到的细粒度暂停设计,感觉相当不错。
所谓细粒度暂停,是把暂停功能分为三个级别:

  1. 全局暂停
  2. 合约级别的暂停
  3. 函数级别的暂停

这样,我们可以根据需要,非常高效的进行暂停操作。比如对于借贷协议,当需要暂停借款时,可以只设置对借款函数的暂停,需要暂停某个借贷池的时候,直接对那个借贷池合约设置暂停。

0x04 细粒度暂停的基本实现

  1. 我们用一个合约 GlobalPauseController 来管理相关状态
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
import "@openzeppelin/contracts/access/Ownable.sol";
 
contract GlobalPPauseController is Ownable {
    bool public globalPause;
    mapping(address => bool) public contractPause;
    mapping(address => mapping(bytes4 => bool)) public functionUnpause;
 
    event GlobalPauseSet(bool status);
    event ContractPauseSet(address indexed contractAddress, bool status);
    event FunctionUnpauseSet(address indexed contractAddress, bytes4 indexed functionSig, bool status);
 
    function setGlobalPause(bool _status) external onlyOwner {
        globalPause = _status;
        emit GlobalPauseSet(_status);
    }
 
    function setContractPause(address _contract, bool _status) external onlyOwner {
        contractPause[_contract] = _status;
        emit ContractPauseSet(_contract, _status);
    }
 
    function setFunctionUnpause(address _contract, bytes4 _functionSig, bool _status) 
        external 
        onlyOwner 
    {
        functionUnpause[_contract][_functionSig] = _status;
        emit FunctionUnpauseSet(_contract, _functionSig, _status);
    }
 
    /// @dev When the protocol or a contract is paused, we cannot unpause a function, so return `false`
    /// @dev Otherwise check if the given function is unpaused. 
    function isPaused(address _contract, bytes4 _functionSig) 
        external 
        view 
        returns (bool) 
    {
        if (!globalPause && !contractPause[_contract]) {
            return false;
        }
        return !functionUnpause[_contract][_functionSig];
    }
}

这个合约允许通过 owner 权限来设置全局/合约级别/合约函数级别的暂停状态,isPaused 来判断是不是需要暂停。这个合约我感觉有两点可以根据需要写的更灵活一些:
i. Ownerable 可以换为 AccessControl 进行更细粒度的权限控制
ii. 可以添加一个全局的函数暂停状态 globalFunctionPause, 设置非特定合约实例的函数暂停,还是以借贷协议为例的话,这样要暂停借贷协议所有池子的某个功能时就会更方便

  1. 创建一个供其它合约继承使用的类似 OpenZeppelin Pausable 的合约
abstract contract Pausable {
    GlobalPauseController public gpc;
    error Paused();
    constructor(address _gpc) {
        gpc = GlobalPauseController(_gpc);
    }
    modifier whenNotPaused() {
        if(gpc.isPaused(address(this), msg.sig)) 
            revert Paused();
        _;
    }
}

这个合约最主要的就是提供了 whenNotPaused 修饰器来判断是否要暂停当前函数

  1. 使用 Pausable 合约,下面是个 Demo
contract LendingPool is Pausable {
    mapping(address => uint256) public balances;
 
    constructor(address _pauseController) Pausable(_gpc) {}
 
    function deposit() external payable whenNotPaused {
        balances[msg.sender] += msg.value;
    }
 
    function withdraw(uint256 amount) external whenNotPaused {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}
免责声明

免责声明:

本文不代表知点网立场,且不构成投资建议,请谨慎对待。用户由此造成的损失由用户自行承担,与知点网没有任何关系;

知点网不对网站所发布内容的准确性,真实性等任何方面做任何形式的承诺和保障;

网站内所有涉及到的区块链(衍生)项目,知点网对项目的真实性,准确性等任何方面均不做任何形式的承诺和保障;

网站内所有涉及到的区块链(衍生)项目,知点网不对其构成任何投资建议,用户由此造成的损失由用户自行承担,与知点网没有任何关系;

知点区块链研究院声明:知点区块链研究院内容由知点网发布,部分来源于互联网和行业分析师投稿收录,内容为知点区块链研究院加盟专职分析师独立观点,不代表知点网立场。

本文是全系列中第230 / 239篇:行业技术

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的电报
  • 这是我的电报扫一扫
  • weinxin
chatGPT账号
知点

发表评论

您必须登录才能发表评论!