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了,這裡直接用官方的範例
這邊先簡單解釋一下,在zos的設計中,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;
}
}
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),則部署方式如下
--init [function] : 就是在部署contract後,呼叫某個function,以上面的例子就是呼叫初始化function
--args : 後面接著要帶入function的參數,用","做分隔,且不能有空格
zos create MyContract --init initialize --args 11,hello
--args : 後面接著要帶入function的參數,用","做分隔,且不能有空格
* 若在過程中出現這樣的錯誤,
A network name must be provided to execute the requested action
就代表session過期了,就再重設一次
zos session --network local --from <address> --expires 3600
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;
}
}
然後部署新的contractzos push
最後,更新contract
zos update MyContract
登愣!這樣就好了!是不是很簡單!
需要特別注意的,contract的位址都會是同一個(因為都是proxy contract的位址)
驗證contract的狀態,可以用truffle console,然後直接對function做操作及取值,這邊就不特別說明了。
避免篇幅過長,這篇著重在"如何"部署一個可以升級的contract,至於很多的"為什麼",之後再新增篇幅做解釋。
若有錯誤歡迎指正,不清楚的部分也歡迎指教。
需要特別注意的,contract的位址都會是同一個(因為都是proxy contract的位址)
驗證contract的狀態,可以用truffle console,然後直接對function做操作及取值,這邊就不特別說明了。
Reminder
在建立upgradeable contract時,有滿多的限制,之後有機會再深入跟proxy contract一起介紹,這邊先簡單列出來,避免大家在試的時候踩雷- global變數的的順序不能改變,包括不能刪除,增加的話,只能依序往後增加。參考
- 不要有constructor,把constructor要做的事寫到initialize中
- global變數的初始化要放在initialize中,直接在宣告時給値是無效的。參考
- 因為 initialize取代了constructor,所以要避免initialize被多次呼叫(可使用initializer這個modifier)。參考
- contract中創建新的contract,新的contract是無法upgrade的。比較好的方式是在外部建立一個upgradeable contract,再把地址帶入原本的contract。另一個方式是使用zos-lib中所提供的App.sol來建立,細節請參考這裏
- 如果contract中原本有用到OpenZeppelin的程式,最好都改用openzeppelin-eth版本的,因為eth版本的contract都是upgradeable。參考
- 如果parent contract繼承了Initializable,那記得要呼叫parent contract的initialize(而不是constructor)
- 安全考量,盡量不要使用selfdestruct跟delegatecall。參考
避免篇幅過長,這篇著重在"如何"部署一個可以升級的contract,至於很多的"為什麼",之後再新增篇幅做解釋。
若有錯誤歡迎指正,不清楚的部分也歡迎指教。
留言
張貼留言