3.OpenZeppelinSDKの基本
はじめに
本連載では、スマートコントラクトのUpgradeabilityに焦点を当てています。スマートコントラクトは、普通のアプリケーションとは違い、一度デプロイしたコントラクトのコードは修正できないという不変性を持っています。この不変性を特殊な方法を用いて、回避することで、Developer Experienceの向上を図ることができる、「Upgradeability」のコンセプトをわかりやすく解説するのが本連載の目的です。
今回は、「3.OpenZeppelinSDKの基本」です。コントラクトのアップグレードツールであるOpenZeppelinSDKを利用していくための、環境構築・利用の仕方・注意点について解説していきます。
関連する記事
第1回 SolidityのUpgradeabilityとOpenZeppelinSDKとは
第2回 SolidityのUpgradeabilityとOpenZeppelinSDKとは
第4回 SolidityのUpgradeabilityとOpenZeppelinSDKとは
OpenZeppelinSDKとは
OpenZeppelinSDKは、OpenZeppelinが提供するSmart Contract開発ツールです。truffleを利用せずにSmart Contractのcompile、upgrading、deployが簡単に行えます。またCLIが充実しており、関数の実行も簡単に行えます。またアップグレード可能なコントラクトのパッケージであるOpenZeppelin-contracts-ethereum-packageを利用することで、簡単にアップグレーダブルなコントラクトを作成できます。
OpenZeppelinSDKの導入
以下のドキュメントを参考にしながら、OpenZeppelinSDKを実行していきます。
Your first project
https://docs.openzeppelin.com/sdk/2.6/first
実行環境
PC : Mac
OS : macOS Mojave
homebrewがインストール済み
git : インストール済み
Node.js : v8.15.0
npm : v6.4.1
Ganache CLI : v6.7.0
インストール
まず任意のディレクトリを作成し、カレントディレクトリを移動させてください。またnpmで初期化を行います。
$ mkdir project1115 && cd project1115
project1115$ npm init
その後カレントディレクトリに@openzeppelin/cliをインストールします。そしてoz initで初期化を行います。今回インストールするのは、最新バージョンのv2.6です。(2019年12月の執筆時点)バージョンにより操作方法が変わってくるので注意してください。バージョンを指定する場合は、「@2.6」をモジュール名の後に繋げて入力します。
project1115$ npm install --global @openzeppelin/cli
project1115$ oz init
? Welcome to the OpenZeppelin SDK! Choose a name for your project project1115
? Initial project version 1.0.0
? Would you like to contribute anonymous usage data to help us improve the OpenZeppelin CLI? Learn more at https://zpl.in
/telemetry Yes
Project initialized. Write a new contract in the contracts folder and run 'openzeppelin create' to deploy it.
これでOpenZeppelinSDKを利用する準備ができたので、利用していきます。
簡単なコントラクトをアップグレードする
まずcontractsディレクトリ配下に次の簡易なコントラクトを準備します。
contracts/Counter.sol
pragma solidity ^0.5.0;
contract Counter {
uint256 public value;
function increase() public {
value++;
}
}
コンパイルします。
project1115$ oz compile
✓ Compiled contracts with solc 0.5.13 (commit.5b0b510c)
ターミナルをもう1つ開き、そこでganache-cliを起動します。
project1115$ ganache-cli
You can improve web3's peformance when running Node.js versions older than 10.5.0 by installing the (deprecated) scrypt package in your project
Ganache CLI v6.7.0 (ganache-core: 2.8.0)
Available Accounts
==================
(0) 0x9C27ed188B55fA57a3f2ADD432E37747E0CF550F (100 ETH)
(1) 0x41687D9E3a13fa38BBf627d33D3b23B12540f6E4 (100 ETH)
(2) 0x5dcfB34EfDA0fccfC2d62668244a7DeD7b96f1A5 (100 ETH)
(3) 0x6c33C8045C32985Ea02247eFDa89Ef42D788f4F7 (100 ETH)
(4) 0xDfF7E63B96547D7e6DB2b556996e752dA595724e (100 ETH)
(5) 0xc3a9F8cFd9f1dff96AD61E11413877D174b42Ca9 (100 ETH)
(6) 0x800937a68A764EE548378CD189b9f36bA8cFb60d (100 ETH)
(7) 0xD64f8f50Bc514A677017Ae8D832BA40978a71Ca8 (100 ETH)
(8) 0xC889EE4Aa318aDE7ff16cdBB2fF200bF4930Fe6D (100 ETH)
(9) 0xf410F2c4Eba8Ba346365186342bD01ACe8C9C2d9 (100 ETH)
Private Keys
==================
(0) 0x0afbeecca16a906dc701d1ff6b347dbb81d03ed57acca5f09f57c08354f1bc0f
(1) 0x0f856fda1741cc5f3d34138fa35f050f2fee3fa1df4ea7c184b01ed1cbd6c27c
(2) 0x3b47d1921a1c0c31f4a2457c9322e1a3c8cd5af38dbfb042d43a6882869851f7
(3) 0xa1f92150eb1df66d01d2d74e8b745bab2a3cb5be91a5b1b180161644df2dfee2
(4) 0x248e53f0250f39d2bffe8652f1b899a54d799d9f8aba87965edb319662b1c2ac
(5) 0xc62c08ff39c17ec533dd0588bc55d134ec9cf9ec8b08fedd88d26a524cd5c064
(6) 0x44157c4e5c400e888cdf729b69451c02f4a1d36360312b47f9f943588b41d00c
(7) 0x35667d9364c558629c474f78a4dd4c348c846e2f1a5641a27fe66decf0e1cc41
(8) 0xdba1840691f5b4978647036991bd1960443c2ad5ea64430e4aa935cec5aa0146
(9) 0x27c51cf31310813b3bb44ff99aa471f5feaa6070000f2256ceb53b736e833d66
HD Wallet
==================
Mnemonic: scissors erupt shell angle cross dignity desert brush furnace route choice sell
Base HD Path: m/44'/60'/0'/0/{account_index}
Gas Price
==================
20000000000
Gas Limit
==================
6721975
Listening on 127.0.0.1:8545
ganache-cliのブロックチェーンにコンパイル済みのコントラクトをデプロイします。3回質問があるので、以下のように答えてください。答えるときは、選択後にenterを押してます。
project1115$ oz create
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate Counter
? Pick a network development
✓ Added contract Counter
✓ Contract Counter deployed
All contracts have been deployed
? Call a function to initialize the instance after creating it? No
✓ Setting everything up to create contract instances
✓ Instance created at 0x88aE9Dd0e219344ff4F3Fd75472843A62518050D
0x88aE9Dd0e219344ff4F3Fd75472843A62518050D
Counter.solをデプロイすることができました。次は、デプロイしたコントラクトの関数を実行していきます。まずコントラクトの状態を変更する関数を実行します。状態が変更する場合はトランザクションが発生します。ネットワーク・コントラクトのインスタンス・実行する関数を選択します。
project1115$ oz send-tx
? Pick a network development
? Pick an instance Counter at 0x88aE9Dd0e219344ff4F3Fd75472843A62518050D
? Select which function increase()
✓ Transaction successful. Transaction hash: 0x001656de2d5ab56638793b4bea47c9e9e9fe9446b77c0cdc00aa3782d9abb689
次はコントラクトの状態が変更されていることを確認します。状態を読み取るだけの場合は、トランザクションは発生しません。「oz call」を実行します。
project1115$ oz call
? Pick a network development
? Pick an instance Counter at 0x88aE9Dd0e219344ff4F3Fd75472843A62518050D
? Select which function value()
✓ Method 'value()' returned: 1
1
valueが1増えていることが確認できました。このLogic Contractはやや簡単すぎるのでincrease()のロジックを一部変更してみたいと思います。
contract/Counter.sol
contract/Counter.sol
pragma solidity ^0.5.0;
contract Counter {
uint256 public value;
function increase(uint256 amount) public {
value += amount; //変更点
}
}
エディタからCounter.solをそのまま変更・保存してください。そして「oz upgrade」実行します。
project1115$ oz upgrade
? Pick a network development
✓ Compiled contracts with solc 0.5.13 (commit.5b0b510c)
✓ Contract Counter deployed
All contracts have been deployed
? Which instances would you like to upgrade? All instances
✓ Instance upgraded at 0x88aE9Dd0e219344ff4F3Fd75472843A62518050D. Transaction receipt: 0x198e9f18e8d7b1baa143f344eda06528e9633999c2d63e80f54377db446f79dc
✓ Instance at 0x88aE9Dd0e219344ff4F3Fd75472843A62518050D upgraded
コントラクトアドレスが変更されていないのは、これがProxy Contractのアドレスだからです。変更されたロジックを実行してみます。
project1115$ oz send-tx
? Pick a network development
? Pick an instance Counter at 0x88aE9Dd0e219344ff4F3Fd75472843A62518050D
? Select which function increase(amount: uint256)
? amount (uint256): 100
✓ Transaction successful. Transaction hash: 0x3437bd7d01ee5000a92a32134f154e50a381aa0c72da47ab1d4342c8db1de58f
project1115$ oz call
? Pick a network development
? Pick an instance Counter at 0x88aE9Dd0e219344ff4F3Fd75472843A62518050D
? Select which function value()
✓ Method 'value()' returned: 101
101
Logic Contractのアップグレードがうまくいっていることが確認できました。
アップグレードする際の注意点・ルール
Proxy Contractは万能ではないため、完璧にLogic Contractをアップグレードすることはできないです。アップグレードする際の注意点・ルールについて確認していきます。以下のドキュメントが参考になります。
Modifying your contracts
https://docs.openzeppelin.com/sdk/2.6/writing-contracts#modifying-your-contracts
Logic Contract
Logic Contractはアクセス制限がない限り誰でもアクセスが可能です。なので、selfdestructやdelegatecallがLogic Contractに実装されていると、悪意のある誰かによって、Contractを破壊される恐れがあります。
Contract間のストレージ衝突
新しいをProxy Contractから呼び出す場合に、slotに保存される状態変数の順番が以下のように変更されてしまうと、ストレージの衝突が起こります。これによりバージョン0で保持していた状態が消失してしまいます。
出典 : OpenZeppelin SDK Upgrades Pattern
https://docs.openzeppelin.com/sdk/2.5/pattern#transparent-proxies-and-function-clashes
新しいバージョンのLogic Contractに変数を追加する場合は、以下のようにslotの順番を確認しながら行う必要があります。
出典 : OpenZeppelin SDK Upgrades Pattern
https://docs.openzeppelin.com/sdk/2.5/pattern#transparent-proxies-and-function-clashes
変更の際の注意点・ルール
宣言した状態変数の型を変更することはできません。
contract MyContract {
uint256 private x;
string private y;
}
contract MyContract {
string private x; // 型変更はできない。
string private y;
}
宣言した状態変数の順番を入れ替えることはできません。
contract MyContract {
uint256 private x;
string private y;
}
contract MyContract {
string private y; // 順番を入れ替えてはいけない。
uint256 private x;
}
新しい状態変数を既存の変数の前で宣言してはいけない。
contract MyContract {
uint256 private x;
string private y;
}
contract MyContract {
bytes private a; // ここで宣言してはいけない
uint256 private x;
string private y;
}
宣言ずみの状態変数を削除できない。
contract MyContract {
uint256 private x;
string private y;
}
contract MyContract {
string private y;
}
状態変数を追加する場合は、既存の状態変数の後で、宣言しなければならない。
contract MyContract {
uint256 private x;
string private y;
bytes private z; // ここでなら宣言が可能。
}
状態変数の名前を変更する場合、保持する値は変更されない点に注意が必要です。
contract MyContract {
uint256 private x;
string private z; // starts with the value from `y`
}
変数を削除しても、同じslotに変数の値が残るため、その後また変数を追加した場合、衝突が起こる。
contract MyContract {
uint256 private x;
}
// Then upgraded to...
contract MyContract {
uint256 private x;
string private z; // starts with the value from `y`
}
継承しているContractの順番を入れ替えると変数の格納方法が変更されるため、衝突が発生します。
contract A {
uint256 a;
}
contract B {
uint256 b;
}
contract MyContract is A, B { }
contract MyContract is B, A { } // 順番を入れ替えると変数の値が崩れる。
子コントラクトに状態変数がある場合、親コントラクトの変数は一切変更することができない。ただし将来変更することを見越して、利用予定のない状態変数を子コントラクトに宣言し、slotを予約しておくことが可能です。
contract Base {
uint256 base1;
}
contract Child is Base {
uint256 child;
}
contract Base {
uint256 base1;
uint256 base2; // 親コントラクトに変数を追加することはできない。
}
まとめ
「3.OpenZeppelinSDKの基本」では、コントラクトのアップグレードツールであるOpenZeppelinSDKを利用していくための、環境構築・利用の仕方・注意点について解説しました。次回「4.OpenZeppelinSDKの応用」です。コントラクトのアップグレードツールであるOpenZeppelinSDKを利用して、より高度なUpgradeable contractついて解説していきます。