{"author":{"address":"0x53b322DcdFEb07de5510facc37797D013292c54F","user":"https://learnblockchain.cn/people/29"},"content":{"body":"## 0x01 为什么需要暂停功能\r\n当一个协议有下面这些考虑时，一般就需要添加暂停功能了：\r\n1. 协议本身有一定的中心化属性\r\n比如大部分中间人机制的跨链桥合约，RWA 这种需要链上链下互动的合约，都离不开一些偏中心化角色的参与。既然有参与的权利，就要为资金安全承担一定的责任，暂停功能可以在合约出现问题时起一定的防护作用。\r\n\r\n2. 协议未来有持续演进升级的需要\r\n升级过程中有可能需要对某些功能进行暂停。\r\n\r\n3. 安全协作需要\r\n笔者参与的一些项目，资金大户有明确需求，他们可以监控链上状态，当发现有异常的时候，可以直接使用暂停功能来暂停协议的运行。\r\n\r\n## 0x02 常见的暂停功能设计\r\n用的最多的就是 OpenZeppelin 提供的 [Pausable.sol 模版](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Pausable.sol)了。很多时候这个模版已经很好用了，但当协议变的比较复杂之后，这个模版有个最大的限制就出来了：粒度比较粗。一旦设置了暂停，就是全局暂停，意味着所有使用 `whenPaused` 这个 modifier 修饰的函数都将无法调用。但有的时候我们需要更细粒度的控制，比如对于借贷协议来说，某种情况下只需要暂停借款，某种情况下只需要暂停某个借贷池，如果没有一个通用的设计，就需要在业务层进入侵入性设计，将暂停功能和业务功能混在一起。\r\n\r\n## 0x03 细粒度暂停\r\n\r\n最近看到[这篇文章](https://flawsomedev.com/blog/granular-pausing-solidity) 提到的细粒度暂停设计，感觉相当不错。\r\n所谓细粒度暂停，是把暂停功能分为三个级别：\r\n1. 全局暂停\r\n2. 合约级别的暂停\r\n3. 函数级别的暂停\r\n\r\n这样，我们可以根据需要，非常高效的进行暂停操作。比如对于借贷协议，当需要暂停借款时，可以只设置对借款函数的暂停，需要暂停某个借贷池的时候，直接对那个借贷池合约设置暂停。\r\n\r\n## 0x04 细粒度暂停的基本实现\r\n1. 我们用一个合约 `GlobalPauseController` 来管理相关状态\r\n```js\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n \r\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\r\n \r\ncontract GlobalPPauseController is Ownable {\r\n    bool public globalPause;\r\n    mapping(address =\u003e bool) public contractPause;\r\n    mapping(address =\u003e mapping(bytes4 =\u003e bool)) public functionUnpause;\r\n \r\n    event GlobalPauseSet(bool status);\r\n    event ContractPauseSet(address indexed contractAddress, bool status);\r\n    event FunctionUnpauseSet(address indexed contractAddress, bytes4 indexed functionSig, bool status);\r\n \r\n    function setGlobalPause(bool _status) external onlyOwner {\r\n        globalPause = _status;\r\n        emit GlobalPauseSet(_status);\r\n    }\r\n \r\n    function setContractPause(address _contract, bool _status) external onlyOwner {\r\n        contractPause[_contract] = _status;\r\n        emit ContractPauseSet(_contract, _status);\r\n    }\r\n \r\n    function setFunctionUnpause(address _contract, bytes4 _functionSig, bool _status) \r\n        external \r\n        onlyOwner \r\n    {\r\n        functionUnpause[_contract][_functionSig] = _status;\r\n        emit FunctionUnpauseSet(_contract, _functionSig, _status);\r\n    }\r\n \r\n    /// @dev When the protocol or a contract is paused, we cannot unpause a function, so return `false`\r\n    /// @dev Otherwise check if the given function is unpaused. \r\n    function isPaused(address _contract, bytes4 _functionSig) \r\n        external \r\n        view \r\n        returns (bool) \r\n    {\r\n        if (!globalPause \u0026\u0026 !contractPause[_contract]) {\r\n            return false;\r\n        }\r\n        return !functionUnpause[_contract][_functionSig];\r\n    }\r\n}\r\n```\r\n这个合约允许通过 owner 权限来设置全局/合约级别/合约函数级别的暂停状态，isPaused 来判断是不是需要暂停。这个合约我感觉有两点可以根据需要写的更灵活一些：\r\ni. Ownerable 可以换为 [AccessControl](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol) 进行更细粒度的权限控制\r\nii. 可以添加一个全局的函数暂停状态 globalFunctionPause, 设置非特定合约实例的函数暂停，还是以借贷协议为例的话，这样要暂停借贷协议所有池子的某个功能时就会更方便\r\n\r\n2. 创建一个供其它合约继承使用的类似 OpenZeppelin Pausable 的合约\r\n\r\n\r\n\r\n```js\r\nabstract contract Pausable {\r\n    GlobalPauseController public gpc;\r\n    error Paused();\r\n    constructor(address _gpc) {\r\n        gpc = GlobalPauseController(_gpc);\r\n    }\r\n    modifier whenNotPaused() {\r\n        if(gpc.isPaused(address(this), msg.sig)) \r\n            revert Paused();\r\n        _;\r\n    }\r\n}\r\n```\r\n这个合约最主要的就是提供了 `whenNotPaused` 修饰器来判断是否要暂停当前函数\r\n\r\n3. 使用 Pausable 合约，下面是个 Demo\r\n```js\r\ncontract LendingPool is Pausable {\r\n    mapping(address =\u003e uint256) public balances;\r\n \r\n    constructor(address _pauseController) Pausable(_gpc) {}\r\n \r\n    function deposit() external payable whenNotPaused {\r\n        balances[msg.sender] += msg.value;\r\n    }\r\n \r\n    function withdraw(uint256 amount) external whenNotPaused {\r\n        require(balances[msg.sender] \u003e= amount, \"Insufficient balance\");\r\n        balances[msg.sender] -= amount;\r\n        payable(msg.sender).transfer(amount);\r\n    }\r\n}\r\n```","title":"智能合约的细粒度暂停"},"history":null,"timestamp":1719616967,"version":1}