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時,完成以下四件事
驗証合約的實作是可以安全昇級的(下一篇 QA中說明什麼是可以安全昇級的合約)
為你的專案部署一個代理管理員(proxy admin) 合約
部署功能實作合約
建立及初始化代理合約
在調用 upgradeProxy 時,完成以下三件事
驗証新的功能實作合約是不是可安全昇級,是不是跟前一版本相容
檢查功能實作合約的 bytecode 是不是跟前一版本相同,不相同才部署
昇級代理合約,使用新的功能實作合約
插件會持續的追蹤所有在專案根目錄底下的 .openzeppelin 目錄下的所有功能實作合約及代理管理員。在目錄裡面一個每個網路名稱會配一個檔案,建議針對所有的網路都做版本管理。
管理所有權
所有的代理都會定義管理員地址,只有管理員有權限更新。預設是代理管理員合約,可以調用 admin.changeAdminForProxy 函數來更改代理管理員。記得代理管理員只能更新代理合約,不能和功能實作合約交互。
代理管理員合約也定義了擁有者地址,這個地址有操作權限。預設這個地址是部署時的外部帳戶。可以調用 admin.transferProxyAdminOwnership 函數來修改代理管理員的擁有者地址。注意這個會改變更新任何代理合約的權力,要小心使用。
修改了更新的權限的地址,還是可以用本地的設定來驗証和部署功能實作合約。插件裡的 prepareUpgrade 函數可以驗証新的功能實作合約是不是可以安全昇級,和之前的合約是不是相容。然後用本地的以太坊帳戶部署。最後用 admin 地址來執行更新。
在下一篇中我會先說明文中的特定名詞,如代理管理員,可以安全昇級的合約,功能實作合約