在 Rinkeby 上建立 Gnosis Safe 之後,請複製地址,以便我們轉移所有權。
當前代理的管理員(可以執行升級)是 ProxyAdmin 合約。只有 ProxyAdmin 的所有者可以升級代理。警告:ProxyAdmin 所有權轉移時請確保轉到我們控制的地址上。
使用以下 JavaScript 在migrations目錄中建立3_transfer_ownership.js。將 gnosisSafe 的值更改為你的 Gnosis Safe 地址。
3_transfer_ownership.js
// migrations/3_transfer_ownership.js
const { admin } = require('@openzeppelin/truffle-upgrades');
module.exports = async function (deployer, network) {
// 使用你的 Gnosis Safe 地址
const gnosisSafe = '0x1c14600daeca8852BA559CC8EdB1C383B8825906';
// Don't change ProxyAdmin ownership for our test network
if (network !== 'test') {
// The owner of the ProxyAdmin can upgrade our contracts
await admin.transferProxyAdminOwnership(gnosisSafe);
}
};
我們可以在 Rinkeby 網路上執行遷移。
$ npx truffle migrate --network rinkeby
...
3_transfer_ownership.js
=======================
> Saving migration to chain.
-------------------------------------
...
實現一個新的升級版本
一段時間後,我們決定要向合約新增功能。在本文中,我們將新增一個increment函式。
注意:我們無法更改之前合約實現的儲存佈局,有關技術限制的更多詳細資訊,請參閱升級[9]。
使用以下 Solidity 程式碼在你的contracts目錄中建立新的實現BoxV2.sol 。
BoxV2.sol
// contracts/BoxV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
contract BoxV2 {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Stores a new value in the contract
function store(uint256 newValue) public {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
// Increments the stored value by 1
function increment() public {
value = value + 1;
emit ValueChanged(value);
}
}
本地測試升級的版本
為了測試我們的升級版本,我們應該為新的合約建立單元測試,併為透過代理測試互動,並檢查升級之間是否保持狀態。
我們將為新的合約實現建立單元測試。我們可以在已經建立的單元測試中新增新測試,以確保高覆蓋率。使用以下 JavaScript 在你的test目錄中建立BoxV2.test.js。
BoxV2.test.js
// test/BoxV2.test.js
// Load dependencies
const { expect } = require('chai');
// Load compiled artifacts
const BoxV2 = artifacts.require('BoxV2');
// Start test block
contract('BoxV2', function () {
beforeEach(async function () {
// Deploy a new BoxV2 contract for each test
this.boxV2 = await BoxV2.new();
});
// Test case
it('retrieve returns a value previously stored', async function () {
// Store a value
await this.boxV2.store(42);
// 測試是否返回了同一個設定的值
// 注意需要使用字串去對比256位的整數
expect((await this.boxV2.retrieve()).toString()).to.equal('42');
});
// Test case
it('retrieve returns a value previously incremented', async function () {
// Increment
await this.boxV2.increment();
// 測試是否返回了同一個設定的值
// 注意需要使用字串去對比256位的整數
expect((await this.boxV2.retrieve()).toString()).to.equal('1');
});
});
升級後,我們還可以透過代理進行互動來建立測試。注意:我們不需要在此處重複單元測試,這是為了測試代理互動和測試升級後的狀態。
使用以下 JavaScript 在你的test目錄中建立BoxV2.proxy.test.js。
BoxV2.proxy.test.js
// test/Box.proxy.test.js
// Load dependencies
const { expect } = require('chai');
const { deployProxy, upgradeProxy} = require('@openzeppelin/truffle-upgrades');
// Load compiled artifacts
const Box = artifacts.require('Box');
const BoxV2 = artifacts.require('BoxV2');
// Start test block
contract('BoxV2 (proxy)', function () {
beforeEach(async function () {
// Deploy a new Box contract for each test
this.box = await deployProxy(Box, [42], {initializer: 'store'});
this.boxV2 = await upgradeProxy(this.box.address, BoxV2);
});
// Test case
it('retrieve returns a value previously incremented', async function () {
// Increment
await this.boxV2.increment();
// Test if the returned value is the same one
// Note that we need to use strings to compare the 256 bit integers
expect((await this.boxV2.retrieve()).toString()).to.equal('43');
});
});
然後,我們可以執行測試。
$ npx truffle test
Using network 'test'.
...
Contract: Box (proxy)
✓ retrieve returns a value previously initialized (38ms)
Contract: Box
✓ retrieve returns a value previously stored (87ms)
Contract: BoxV2 (proxy)
✓ retrieve returns a value previously incremented (90ms)
Contract: BoxV2
✓ retrieve returns a value previously stored (91ms)
✓ retrieve returns a value previously incremented (86ms)
5 passing (1s)
部署新的升級版本
一旦測試了新的實現,就可以準備升級。這將驗證並部署新合約。注意:我們僅是準備升級。我們將使用 Gnosis Safe 執行實際升級。
使用以下 JavaScript 在migrations目錄中建立4_prepare_upgrade_boxv2.js。
4_prepare_upgrade_boxv2.js
// migrations/4_prepare_upgrade_boxv2.js
const Box = artifacts.require('Box');
const BoxV2 = artifacts.require('BoxV2');
const { prepareUpgrade } = require('@openzeppelin/truffle-upgrades');
module.exports = async function (deployer) {
const box = await Box.deployed();
await prepareUpgrade(box.address, BoxV2, { deployer });
};
我們可以在 Rinkeby 網路上執行遷移,以部署新的合約實現。注意:執行此遷移時,我們需要跳過之前執行過的遷移。
$ npx truffle migrate --network rinkeby
...
4_prepare_upgrade_boxv2.js
==========================
Deploying 'BoxV2'
-----------------
> transaction hash: 0x078c4c4454bb15e3791bc80396975e6e8fc8efb76c6f54c321cdaa01f5b960a7
> Blocks: 1 Seconds: 17
> contract address: 0xEc784bE1CC7F5deA6976f61f578b328E856FB72c
...
升級合約
要管理我們在 Gnosis Safe 中的升級,我們使用 OpenZeppelin 應用程式(找一下 OpenZeppelin 的 logo)。
首先,我們需要代理的地址(box.address)和新實現的地址(boxV2.address)。我們可以從 truffle 遷移的輸出或 truffle console 中獲得。
$ npx truffle console --network rinkeby
truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> boxV2 = await BoxV2.deployed()
truffle(rinkeby)> box.address
'0xF325bB49f91445F97241Ec5C286f90215a7E3BC6'
truffle(rinkeby)> boxV2.address
'0xEc784bE1CC7F5deA6976f61f578b328E856FB72c'
在“應用程式(APPS)”選項卡中,選擇“ OpenZeppelin”應用程式,然後將代理地址貼上到“合約地址(Contract address)”欄位中,然後將新實現的地址貼上到“新實現的地址( New implementation address)”欄位中。
該應用程式應顯示該合約是EIP1967[10]相容的。
仔細檢查地址,然後按“升級(Upgrade)”按鈕。
確認顯示對話方塊以提交交易。
然後,我們需要在 MetaMask(或你正使用的錢包)中籤署交易。
現在,我們可以與升級後的合約進行互動。我們需要使用代理地址與 BoxV2 進行互動。然後,我們可以呼叫新的“增量”功能,觀察到整個升級過程中都保持了狀態。
$ npx truffle console --network rinkeby
truffle(rinkeby)> box = await Box.deployed()
truffle(rinkeby)> boxV2 = await BoxV2.at(box.address)
truffle(rinkeby)> (await boxV2.retrieve()).toString()
'42'
truffle(rinkeby)> await boxV2.increment()
{ tx:
...
truffle(rinkeby)> (await boxV2.retrieve()).toString()
'43'
接下來
我們已經建立了一個可升級的合約,將升級的控制權轉移到了 Gnosis Safe,並升級了我們的合約。
可以在主網上執行相同的過程。注意:我們應該始終首先在公共測試網上測試升級。
如果你對本文有任何疑問或建議的改進,請釋出在openzeppelin 社羣論壇[11]中。
參考資料
[1]登鏈翻譯計劃: https://github.com/lbc-team/Pioneer
[2]Tiny 熊: https://learnblockchain.cn/people/15
[3]OpenZeppelin 學習指南: https://docs.openzeppelin.com/learn/developing-smart-contracts#setting-up-a-solidity-project
[4]Solidity: https://learnblockchain.cn/docs/solidity/
[5]initialize函式而不是建構函式: https://docs.openzeppelin.com/learn/upgrading-smart-contracts#initialization
[6]Truffle 遷移: https://learnblockchain.cn/docs/truffle/getting-started/running-migrations.html
[7]使用 Truffle 連線到公共測試網路: https://forum.openzeppelin.com/t/connecting-to-public-test-networks-with-truffle/2960
[8]建立 Safe Multisig: https://help.gnosis-safe.io/en/articles/3876461-create-a-safe-multisig
[9]升級: https://docs.openzeppelin.com/learn/upgrading-smart-contracts#upgrading
[10]EIP1967: https://learnblockchain.cn/docs/eips/eip-1967.html
[11]openzeppelin 社羣論壇: https://forum.openzeppelin.com/
[12]Cell Network: https://www.cellnetwork.io/?utm_souce=learnblockchain
[13]abcoathup: https://forum.openzeppelin.com/u/abcoathup