第3回 SolidityのUpgradeabilityとOpenZeppelinSDKとは

第3回 SolidityのUpgradeabilityとOpenZeppelinSDKとは

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ついて解説していきます。
     

免責事項

本記事に掲載されている記事の内容につきましては、正しい情報を提供することに務めてはおりますが、提供している記事の内容及び参考資料からいかなる損失や損害などの被害が発生したとしても、弊社では責任を負いかねます。実施される際には、法律事務所にご相談ください。

技術・サービス・実装方法等のレビュー、その他解説・分析・意見につきましてはblock-chani.jp運営者の個人的見解です。正確性・正当性を保証するものではありません。本記事掲載の記事内容のご利用は読者様個人の判断により自己責任でお願いいたします。

     

コンセンサス・ベイス(株)とブロックチェーン事業を行なってみませんか?

当サイトを運営するコンセンサス・ベイス株式会社は、2015年設立の国内で最も古いブロックチェーン専門企業です。これまでに、大手企業の顧客を中心に、日本トップクラスのブロックチェーンの開発・コンサルティング実績があります。

ブロックチェーンに関わるビジネスコンサル・システム開発・教育・講演などご希望でしたら、お気軽にお問い合わせください。

     
     

ブロックチェーン学習に最適の書籍の紹介

図解即戦力 ブロックチェーンのしくみと開発がこれ1冊でしっかりわかる教科書

ブロックチェーン イーサリアムへの入り口 第二版 (ブロックチェーン技術書籍)

本書は、ブロックチェーン技術に興味を持ったエンジニアや、その仕組みを学び、自分の仕事に活かしたいビジネスパーソンを対象にして、ブロックチェーンのコア技術とネットワーク維持の仕組みを平易な言葉で解説しています。この本を読んだうえで、実際にコードを書くような専門書、ブロックチェーンビジネスの解説書を読むことで、理解度が飛躍的に高まるでしょう。(はじめにより)

イーサリアム(Ethereum)カテゴリの最新記事