智能合约的细粒度暂停

  • Ashton
  • 更新于 2024-06-29 07:22
  • 阅读 954

本文提供了对智能合约暂停功能设计的一种改进方案

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](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol) 进行更细粒度的权限控制
ii. 可以添加一个全局的函数暂停状态 globalFunctionPause, 设置非特定合约实例的函数暂停,还是以借贷协议为例的话,这样要暂停借贷协议所有池子的某个功能时就会更方便

2. 创建一个供其它合约继承使用的类似 OpenZeppelin Pausable 的合约

```js
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);
    }
    }
点赞 0
收藏 4
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ashton
Ashton
0x53b3...c54F
专注于 EVM 和比特币生态的区块链开发者