OpenZeppelin 合約昇級插件 1/9
在寫了幾個合約之後,對於同一個合約的昇級問題,一直覺得不是很好管理。
Ethereum 的智能合約,創建了之後無法修改原合約,但是可以刪除。所以合約的更新,一般順序就是部署新的合約,然後把所有關的應用地址更改為新合約的地址,最後再把舊合約刪除。當然這中間會包括新舊合約內資產的轉移之類的相關操作。
這裡產生的問題,就是在更改相關應用的合約地址時,不可遺漏。甚至如果有合約調用之類的操作,那整個更新的動作將會變得十分繁複,並且容易出錯。
因此,我選用了 OpenZeppelin 的合約昇級插件來學習,這個系列文章按照官網的文檔順序來寫,會加上我自己實作的結果及心得,所以這不是原文的翻譯,不可以做為原文的替代品。
20201109 補充
我在本系列文章,把 migration 、migrate 翻譯為”部署”的原因有兩個,第一個是比較符合行為的意義,一般我們說部署是指,把編譯好的程式放到生產或測試環境之中,也就是把各種檔案放到它應該去的地方。可是在智能合約的開發,這是分為兩個步驟,編譯好之後再使用 0交易發布到鏈上。另一個原因是已經有其他文章將 migrate 翻譯為部署。綜合考量這兩個因素,再加上 migrate 動作,與其”遷移”不如”部署”。在最後一章,因為 migrate 跟 deploy 出現次數很多,因此才分開各以原文書寫。
合約昇級插件
這個插件可以把昇級合約這件事加到你現有的工作流裡面。這個插件讓 Buidler 和 Truffle 兩個整合開發環境可以在以太坊上部署及管理可昇級的合約。
本插件的目標是以下四項
- 部署可昇級的合約
- 昇級已部署的合約
- 管理代理管理員(proxy admin) 權限
- 容易測試
總覽
安裝方式
Buidler 的安裝方式
$ npm install --save-dev @openzeppelin/buidler-upgrades @nomiclabs/buidler-ethers ethers
以上命令安裝了本插件及必要的依賴程式庫
Truffle 安裝
$ npm install --save-dev @openzeppelin/truffle-upgrades
用法
詳細的用法請各自參考 Truffle 及 Buidler ,以下是簡單的用法程式片段。
Buidler的用法
Buidler的用戶可以寫腳本來部署或昇級合約,也可以管理代理管理員權限。
const { ethers, upgrades } = require("@nomiclabs/buidler"); async function main() { // Deploying const Box = await ethers.getContractFactory("Box"); const instance = await upgrades.deployProxy(Box, [42]); await instance.deployed(); // Upgrading const BoxV2 = await ethers.getContractFactory("BoxV2"); const upgraded = await upgrades.upgradeProxy(instance.address, BoxV2); } main();
Truffle 用法
Truffle 的用戶,可以寫稱為 migrations 的設定檔,來使用插件。可以部署,昇級,管理代理管理員權限。
const { deployProxy, upgradeProxy } = require('@openzeppelin/truffle-upgrades'); const Box = artifacts.require('Box'); const BoxV2 = artifacts.require('BoxV2'); module.exports = async function (deployer) { const instance = await deployProxy(Box, [42], { deployer }); const upgraded = await upgradeProxy(instance.address, BoxV2, { deployer }); }
測試用法
無論用的是 Buidler 或 Truffle ,都可以用這個插件來測試,以確定在預期中運作。
it('works before and after upgrading', async function () { const instance = await upgrades.deployProxy(Box, [42]); assert.strictEqual(await instance.retrieve(), 42); await upgrades.upgradeProxy(instance.address, BoxV2); assert.strictEqual(await instance.retrieve(), 42); });
插件是如何運作的
插件提供兩個主要的函數,deployProxy
和 upgradeProxy
。負責管理合約的可昇級部署。
在調用 deployProxy
函數時,完成以下四件事
- 驗証合約的實作是可以安全昇級的
- 為你的專案部署一個代理管理員(proxy admin) 合約
- 部署功能實作合約
- 建立及初始化代理合約
在調用 upgradeProxy
函數時,完成以下三件事
- 驗証新的功能實作合約是不是可安全昇級,是不是跟前一版本相容
- 檢查功能實作合約的 bytecode 是不是跟前一版本相同,不相同才部署
- 昇級代理合約,使用新的功能實作合約
插件會持續的追蹤所有在專案根目錄底下的 .openzeppelin 目錄下的所有功能實作合約及代理管理員。在目錄裡面一個每個網路名稱會配一個檔案,建議針對所有的網路都做版本管理。
管理所有權
所有的代理都會定義管理員地址,只有管理員有權限更新。預設是代理管理員合約,可以調用 admin.changeAdminForProxy
函數來更改代理管理員。記得代理管理員只能更新代理合約,不能和功能實作合約交互。
代理管理員合約也定義了擁有者地址,這個地址有操作權限。預設這個地址是部署時的外部帳戶。可以調用 admin.transferProxyAdminOwnership
函數來修改代理管理員的擁有者地址。注意這個會改變更新任何代理合約的權力,要小心使用。
修改了更新的權限的地址,還是可以用本地的設定來驗証和部署功能實作合約。插件裡的 prepareUpgrade 函數可以驗証新的功能實作合約是不是可以安全昇級,和之前的合約是不是相容。然後用本地的以太坊帳戶部署。最後用 admin 地址來執行更新。
在下一篇中我會先說明文中的特定名詞,如代理管理員,可以安全昇級的合約,功能實作合約
參考
https://docs.openzeppelin.com/upgrades-plugins/1.x/
https://buidler.dev/
https://www.trufflesuite.com/truffle