本文提供了对智能合约暂停功能设计的一种改进方案
当一个协议有下面这些考虑时,一般就需要添加暂停功能了:
协议本身有一定的中心化属性 比如大部分中间人机制的跨链桥合约,RWA 这种需要链上链下互动的合约,都离不开一些偏中心化角色的参与。既然有参与的权利,就要为资金安全承担一定的责任,暂停功能可以在合约出现问题时起一定的防护作用。
协议未来有持续演进升级的需要 升级过程中有可能需要对某些功能进行暂停。
安全协作需要 笔者参与的一些项目,资金大户有明确需求,他们可以监控链上状态,当发现有异常的时候,可以直接使用暂停功能来暂停协议的运行。
用的最多的就是 OpenZeppelin 提供的 Pausable.sol 模版了。很多时候这个模版已经很好用了,但当协议变的比较复杂之后,这个模版有个最大的限制就出来了:粒度比较粗。一旦设置了暂停,就是全局暂停,意味着所有使用 whenPaused
这个 modifier 修饰的函数都将无法调用。但有的时候我们需要更细粒度的控制,比如对于借贷协议来说,某种情况下只需要暂停借款,某种情况下只需要暂停某个借贷池,如果没有一个通用的设计,就需要在业务层进入侵入性设计,将暂停功能和业务功能混在一起。
最近看到这篇文章 提到的细粒度暂停设计,感觉相当不错。 所谓细粒度暂停,是把暂停功能分为三个级别:
这样,我们可以根据需要,非常高效的进行暂停操作。比如对于借贷协议,当需要暂停借款时,可以只设置对借款函数的暂停,需要暂停某个借贷池的时候,直接对那个借贷池合约设置暂停。
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
修饰器来判断是否要暂停当前函数
使用 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);
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!