Upgradable smart contract using zos


smart contract跟一般程式最大的差異,就是smart contract上了鏈就不能改了(這是區塊鏈的特性也是優點,但是如果真的有bug,麻煩就大了...)。所以今天要介紹的是,如何用ZeppelinOS來實作upgradeable smart contract,可升級的smart contract。

版本
 - zos: 2.1.0
 - Truffle: 5.0.1

OpenZeppelin對smart contract的開發者一定不陌生,提供相當多smart contract的範例程式。Zeppelin部落格有介紹如何使用proxy contract實作可升級的contract,這是他們proxy patterns的設計,之後有機會再深入介紹proxy pattern。本篇主要在介紹如何使用zos部署可升級的contract。(本篇需要有使用過truffle的經驗)

Deploy contracts

環境部分,先安裝Node.js跟npm,跟Ganache(或Ganache-cli)。
Deploy your first project有詳細地介紹如何使用zos部署。
先安裝ZeppelinOS
npm install --global zos 再來建立專案
mkdir MyProject
cd MyProject
初始化專案
npm init
zos init MyProject
npm install zos-lib
這個時候,資料夾內容就跟下過truffle init 一樣,有contracts, migrations資料夾,truffle-config.js等。以上環境就都建置完成了,再來就是寫contract了,這裡直接用官方的範例
pragma solidity ^0.4.24; import "zos-lib/contracts/Initializable.sol"; contract MyContract is Initializable { uint256 public x; string public s; function initialize(uint256 _x, string _s) initializer public { x = _x; s = _s; } }
這邊先簡單解釋一下,在zos的設計中,contract必需
 1. 繼承Initializable
 2. 不能有constructor。 而function initialize就作為constructor用
*目前zos使用0.4.24的版本,所以如果truffle 版本是5.0的,要在truffle-config.js中把compilers設為"0.4.24"

然後把contract加到專案中
zos add MyContract * 目前這個版本在Windows執行 'zos add' 會有錯誤,可以參考#608,需要在專案目錄下執行 npm install truffle 再來就是部署了,在truffle-config.js預設的host是"localhost",port是"9545",如果是用Ganache記得把port改成7545(host可能也要改成local ip,這個看Ganache的設定),然後設定zos的session zos session --network local --from <address> --expires 3600
設定truffle使用的network, 使用者的address還有這個session的有效期間(秒為單位)。接著就可以部屬了
zos push
zos push就相當於truffle migrate,把contract部署上鏈。如果在部署contract時需要帶入初始參數(就是呼叫function initialize),則部署方式如下
zos create MyContract --init initialize --args 11,hello
--init [function] : 就是在部署contract後,呼叫某個function,以上面的例子就是呼叫初始化function
--args : 後面接著要帶入function的參數,用","做分隔,且不能有空格
* 若在過程中出現這樣的錯誤,
A network name must be provided to execute the requested action

就代表session過期了,就再重設一次

zos session --network local --from <address> --expires 3600
到這裡,就完成了第一步,使用zos部署contract,目前跟使用truffle一樣沒什麼差別,接下來就是講解如何upgrade contract!

Upgrade contracts

接著我們在原本的範例中增加一個function
pragma solidity ^0.4.24; import "zos-lib/contracts/Initializable.sol"; contract MyContract is Initializable { uint256 public x; string public s; function initialize(uint256 _x, string _s) initializer public { x = _x; s = _s; } function increment() public { x += 1; } } 然後部署新的contract
zos push
最後,更新contract
zos update MyContract
登愣!這樣就好了!是不是很簡單!
需要特別注意的,contract的位址都會是同一個(因為都是proxy contract的位址)
驗證contract的狀態,可以用truffle console,然後直接對function做操作及取值,這邊就不特別說明了。

Reminder

在建立upgradeable contract時,有滿多的限制,之後有機會再深入跟proxy contract一起介紹,這邊先簡單列出來,避免大家在試的時候踩雷

  1. global變數的的順序不能改變,包括不能刪除,增加的話,只能依序往後增加。參考
  2. 不要有constructor,把constructor要做的事寫到initialize
  3. global變數的初始化要放在initialize中,直接在宣告時給値是無效的。參考
  4. 因為 initialize取代了constructor,所以要避免initialize被多次呼叫(可使用initializer這個modifier)。參考
  5. contract中創建新的contract,新的contract是無法upgrade的。比較好的方式是在外部建立一個upgradeable contract,再把地址帶入原本的contract。另一個方式是使用zos-lib中所提供的App.sol來建立,細節請參考這裏
  6. 如果contract中原本有用到OpenZeppelin的程式,最好都改用openzeppelin-eth版本的,因為eth版本的contract都是upgradeable。參考
  7. 如果parent contract繼承了Initializable,那記得要呼叫parent contract的initialize(而不是constructor)
  8. 安全考量,盡量不要使用selfdestructdelegatecall參考


避免篇幅過長,這篇著重在"如何"部署一個可以升級的contract,至於很多的"為什麼",之後再新增篇幅做解釋。

若有錯誤歡迎指正,不清楚的部分也歡迎指教。

留言

這個網誌中的熱門文章

What's New in Ethereum Serenity (2.0)

瑞士滑雪分享2 - 策馬特

動手實做零知識 - circom