如何編寫一個可升級的智慧合約

Tiny熊發表於2018-05-16

區塊鏈信任基礎的資料不可修改的特性,讓它傳統應用程式有一個很大的不同的地方是一經釋出於區塊鏈上就無法修改(不能直接在原有的合約上直接修改再重新發布)。

寫在前面

閱讀本文前,你應該對以太坊、智慧合約及Solidity語言有所瞭解,如果你還不瞭解,建議你先看以太坊是什麼

當智慧合約出現bug

一方面正式由於智慧合約的不可修改的特性,因為只要規則確定之後,沒人能夠修改它,大家才能夠信任它。但另一方面,如果規則的實現有Bug, 可能會造成代幣被盜,或是呼叫消耗大量的gas。這時就需要我們去修復錯誤。

我們知道一個智慧合約包含兩部分: 程式碼邏輯和資料,而程式碼邏輯又是最容易出問題的部分, 如在實現如下合約時,由於手抖在寫addTen()時,10寫成了11。

pragma solidity ^0.4.18;

contract MyContract {
    mapping (address => uint256) public balanceOf;
    
    function setBlance(address _address,uint256 v) public {
        balanceOf[_address] = v;
    }

    function addTen(address addr) public returns (uint){
        return balanceOf[addr] + 11;
    }
}

假如我們在部署之後發現了這個問題,想要修復這個bug的話,只好重新部署合約,可是這時會有一個尷尬的問題,原來的合約已經有很多人使用,如果部署新的合約,老合約的資料將會丟失。

資料合約及控制合約

那麼如何解決上面的問題了,一個解決方案是分離合約中的資料,使用一個單獨的合約來儲存資料(下文稱資料合約),使用一個單獨的合約寫業務邏輯(下文稱控制合約)。
我們來看看程式碼如何實現。

pragma solidity ^0.4.18;

contract DataContract {
    mapping (address => uint256) public balanceOf;

    function setBlance(address _address,uint256 v) public {
        balanceOf[_address] = v;
    }
}

contract ControlContract {

    DataContract dataContract;

    function ControlContract(address _dataContractAddr) public {
        dataContract = DataContract(_dataContractAddr);
    }

    function addTen(address addr) public returns (uint){
        return dataContract.balanceOf(addr) + 11;
    }
}

現在我們有兩個合約DataContract 專門用來存資料,ControlContract用來處理邏輯,並利用DataContract來讀寫資料。通過這樣的設計,可以在更新控制合約後保持資料合約不變,這樣就不會丟失資料,也不用遷移資料。

讀寫控制

通過DataContract我們可以單獨更新合約邏輯,不過你也許發現了一個新的問題,DataContract的資料不僅僅可以被ControlContract讀寫,還可以被其他的合約讀寫,因此需要對DataContract新增讀寫控制。我們給DataContract新增一個mapping, 用來控制哪些地址可以訪問資料,同時新增了修飾器及設定訪問的方法,程式碼如下:

pragma solidity ^0.4.18;

contract DataContract {
    mapping (address => uint256) public balanceOf;
    mapping (address => bool) accessAllowed;
    
    function DataContract() public {
        accessAllowed[msg.sender] = true;
    }

    function setBlance(address _address,uint256 v) public {
        balanceOf[_address] = v;
    }
    
    modifier platform() {
        require(accessAllowed[msg.sender] == true);
        _;
    }
    
    function allowAccess(address _addr) platform public {
        accessAllowed[_addr] = true;
    }
    
    function denyAccess(address _addr) platform public {
        accessAllowed[_addr] = false;
    }
}

...

訂閱我的小專欄可參看合約的完整程式碼。

部署方法如下:

  1. 先部署DataContract合約
  2. 使用DataContract合約地址作為部署ControlContract合約的引數
  3. 用ControlContract合約地址作為引數呼叫DataContract合約的allowAccess方法。

如果需要更新控制合約(如修復了addTen)則重新執行第2-3步,同時對老的控制合約執行denyAccess()。

更多

當我們在實現資料合約時,它包含的邏輯應該越少越好,並且應該是嚴格測試過的,因為一旦資料合約部署之後,就沒法更改。
大多數情況下,和使用者互動的是DApp, 因此當控制合約升級之後,需要升級DApp,使之關聯新的控制合約。

儘管合約可以通過本文的方式升級,但我們依然要謹慎升級,因為升級表示你可以重寫邏輯,會降低使用者對你的信任度。
本文介紹升級方法更多的是一種思路,實際專案中可能會對應多個控制合約及資料合約。

深入淺出區塊鏈 – 系統學習區塊鏈,打造最好的區塊鏈技術部落格。

☛ 我的知識星球為各位解答區塊鏈技術問題,歡迎加入討論。

☛ 關注公眾號“深入淺出區塊鏈技術”第一時間獲取區塊鏈技術資訊。

相關文章