智能合約的細粒度暫停設(shè)計

0x01 為什么需要暫停功能

當一個協(xié)議有下面這些考慮時,一般就需要添加暫停功能了:

  1. 協(xié)議本身有一定的中心化屬性
    比如大部分中間人機制的跨鏈橋合約把篓,RWA 這種需要鏈上鏈下互動的合約婉刀,都離不開一些偏中心化角色的參與贤重。既然有參與的權(quán)利飞蛹,就要為資金安全承擔一定的責任漾肮,暫停功能可以在合約出現(xiàn)問題時起一定的防護作用厂抖。

  2. 協(xié)議未來有持續(xù)演進升級的需要
    升級過程中有可能需要對某些功能進行暫停。

  3. 安全協(xié)作需要
    筆者參與的一些項目克懊,資金大戶有明確需求忱辅,他們可以監(jiān)控鏈上狀態(tài)七蜘,當發(fā)現(xiàn)有異常的時候,可以直接使用暫停功能來暫停協(xié)議的運行墙懂。

0x02 常見的暫停功能設(shè)計

用的最多的就是 OpenZeppelin 提供的 Pausable.sol 模版了橡卤。很多時候這個模版已經(jīng)很好用了,但當協(xié)議變的比較復(fù)雜之后损搬,這個模版有個最大的限制就出來了:粒度比較粗碧库。一旦設(shè)置了暫停,就是全局暫停巧勤,意味著所有使用 whenPaused 這個 modifier 修飾的函數(shù)都將無法調(diào)用嵌灰。但有的時候我們需要更細粒度的控制,比如對于借貸協(xié)議來說颅悉,某種情況下只需要暫停借款沽瞭,某種情況下只需要暫停某個借貸池,如果沒有一個通用的設(shè)計剩瓶,就需要在業(yè)務(wù)層進入侵入性設(shè)計驹溃,將暫停功能和業(yè)務(wù)功能混在一起。

0x03 細粒度暫停

最近看到這篇文章 提到的細粒度暫停設(shè)計延曙,感覺相當不錯豌鹤。
所謂細粒度暫停,是把暫停功能分為三個級別:

  1. 全局暫停
  2. 合約級別的暫停
  3. 函數(shù)級別的暫停

這樣枝缔,我們可以根據(jù)需要布疙,非常高效的進行暫停操作。比如對于借貸協(xié)議魂仍,當需要暫停借款時拐辽,可以只設(shè)置對借款函數(shù)的暫停拣挪,需要暫停某個借貸池的時候擦酌,直接對那個借貸池合約設(shè)置暫停。

0x04 細粒度暫停的基本實現(xiàn)

  1. 我們用一個合約 GlobalPauseController 來管理相關(guān)狀態(tài)
// 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 權(quán)限來設(shè)置全局/合約級別/合約函數(shù)級別的暫停狀態(tài)菠劝,isPaused 來判斷是不是需要暫停赊舶。這個合約我感覺有兩點可以根據(jù)需要寫的更靈活一些:
i. Ownerable 可以換為 AccessControl 進行更細粒度的權(quán)限控制
ii. 可以添加一個全局的函數(shù)暫停狀態(tài) globalFunctionPause, 設(shè)置非特定合約實例的函數(shù)暫停,還是以借貸協(xié)議為例的話赶诊,這樣要暫停借貸協(xié)議所有池子的某個功能時就會更方便

  1. 創(chuàng)建一個供其它合約繼承使用的類似 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 修飾器來判斷是否要暫停當前函數(shù)

  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);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舔痪,隨后出現(xiàn)的幾起案子寓调,更是在濱河造成了極大的恐慌,老刑警劉巖锄码,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夺英,死亡現(xiàn)場離奇詭異晌涕,居然都是意外死亡,警方通過查閱死者的電腦和手機痛悯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門余黎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人载萌,你說我怎么就攤上這事惧财。” “怎么了扭仁?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵垮衷,是天一觀的道長。 經(jīng)常有香客問我斋枢,道長帘靡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任瓤帚,我火速辦了婚禮描姚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戈次。我一直安慰自己轩勘,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布怯邪。 她就那樣靜靜地躺著绊寻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悬秉。 梳的紋絲不亂的頭發(fā)上澄步,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音和泌,去河邊找鬼村缸。 笑死,一個胖子當著我的面吹牛武氓,可吹牛的內(nèi)容都是我干的梯皿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼县恕,長吁一口氣:“原來是場噩夢啊……” “哼东羹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忠烛,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤属提,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后美尸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冤议,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡旬迹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了求类。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奔垦。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尸疆,靈堂內(nèi)的尸體忽然破棺而出椿猎,到底是詐尸還是另有隱情,我是刑警寧澤寿弱,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布犯眠,位于F島的核電站,受9級特大地震影響症革,放射性物質(zhì)發(fā)生泄漏筐咧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一噪矛、第九天 我趴在偏房一處隱蔽的房頂上張望量蕊。 院中可真熱鬧,春花似錦艇挨、人聲如沸残炮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽势就。三九已至,卻和暖如春脉漏,著一層夾襖步出監(jiān)牢的瞬間苞冯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工侧巨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舅锄,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓刃泡,卻偏偏與公主長得像巧娱,于是被迫代替她去往敵國和親碉怔。 傳聞我的和親對象是個殘疾皇子烘贴,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容