スマートコントラクトのアップグレード (ZeppelinOS編)

更新可能性について

自分の実装で、初歩的な機能分離のみに頼った更新可能なスマートコントラクトのみを作っていたので、デファクトないしベストプラクティスに沿った実装をすべく、OpenZeppelinが提供するライブラリを使ったスマートコントラクトの実装を行ってみました。

オブジェクト指向プログラミングに馴染みのある人向けに、ざっくり言ってしまうと、スマートコントラクトの更新は、スマートコントラクトを継承する、スマートコントラクトを作成することと実質同じです。

環境準備

Zeppelin OSをグローバルにインストール

$ sudo npm i -g zos --unsafe-perm=true --allow-root

オプション--unsafe-perm=true --allow-rootは怪しげなので、皆さんの環境に合わせて慎重に利用するかどうか選択してください。

プロジェクトフォルダを適当に作ったら

$ truffle init
$ npm init

または

$ zos init updatable

します。後者にすると、zos.jsonというファイルをプロジェうとのルートに作ってくれます。私は使い慣れているというだけの理由で、前者で実行して後から以下のzos.jsonを追加しました。

{
  "zosversion": "2.2",
  "contracts": {},
  "dependencies": {},
  "name": "updatable",
  "version": "1.0.0"
}

ローカルの環境でテストするので、
truffle-config.jsの中の記述が、GanacheやTruffleの環境に合わせて、

module.exports = {
  /**
   * Networks define how you connect to your ethereum client and let you set the
   * defaults web3 uses to send transactions. If you don't specify one truffle
   * will spin up a development blockchain for you on port 9545 when you
   * run `develop` or `test`. You can ask a truffle command to use a specific
   * network from the command line, e.g
   *
   * $ truffle test --network <network-name>
   */

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache-cli, geth or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    develop: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },

となるように調整します。ポート番号は人によって変わるかもしれません。各人の環境に合わせて設定する必要があります。

さて、今度はローカルのプロジェクトにzos-lib(ZeppelinOSのライブラリ)を入れます。

 npm install zos-lib

また、ローカルの開発用チェーンとして、Truffleの環境を立ち上げておきます

truffle develop --log

更新可能なスマートコントラクト実装

更新可能なスマートコントラクトを作成していきます。このコントラクトには、コンストラクタがありません。これは更新可能性と関連しています。

pragma solidity >0.5.0 <0.6.0;

//ipmort "zos-lib/contracts/migrations/Migratable.sol";
import "zos-lib/contracts/Initializable.sol";

contract Base is Initializable {
    address internal _owner;
    bool internal __isOperational;

    function initialize(bool operational) initializer public {
        _owner = msg.sender;
        __isOperational = operational;
    }

    modifier _isOperational() {
        assert(isOperational());
        _;
    }

    function setOperational(bool _setOperational)
        public
        _isOperational()
    {
        __isOperational = _setOperational;
    }

    function isOperational()
        public
        returns(bool)
    {
        return __isOperational;
    }
}

'truffle compile'でBase.jsonファイルを生成したあと、以下を実行します。

zos init Base 1.0.0

以下の内容の、zos.jsonというファイルが生成されます。スマートコントラクトを更新する際に、変数”version"でスマートコントラクトのバージョンを管理します。

{
  "zosversion": "2.2",
  "contracts": {},
  "dependencies": {},
  "name": "Base",
  "version": "1.0.0"
}

上記のように、スマートコントラクトはまだzosに登録されていないので、以下のコマンドを実行して追加します。

zos add Base

zos.jsonの中身が更新され、スマートコントラクトが登録されます。

  "contracts": {
    "Base": "Base"
  },

ローカルのブロックチェーンにスマートコントラクトをデプロイします。

zos push --network develop

truffuleのコンソールには以下が出力されます。

  develop:ganache   Transaction: 0x7f9e22880ca034f9e5b6ce3290cf5c5c3e7fe9ffeec8edb771833e194b46692a +0ms
  develop:ganache   Contract created: 0x1e169345e167bb71097448488bcfc107afaba47e +0ms

そして、zos.dev-5777.jsonというファイル(実行環境により名前は変わります)が作られ、以下のエントリーを見て取れます。

{
  "contracts": {
    "Base": {
      "address": "0x1e169345e167BB71097448488BCFC107AfAba47E",
      "constructorCode": "608060405234801561001057600080fd5b506102d9806100206000396000f3fe",
      "bodyBytecodeHash": "69834d00b3bfdfd681b3f31f5496d54b43370da96cadd4b46c0010586d4ef8bb",
      "localBytecodeHash": "1eef437f6910a4384313bf8ed73ed6d2ee53d6e8e5bd3c07b8fb246573fa04f1",
      "deployedBytecodeHash": "1eef437f6910a4384313bf8ed73ed6d2ee53d6e8e5bd3c07b8fb246573fa04f1",
      "types": {

デプロイされたスマートコントラクトのアドレスが管理されています。

address(0)のアドレスを使って、以下を実行します。

 zos create Base --network develop --init --args 0x5268f2232368ddba43541aa7a16a9c569d0f9d4f

先のファイルzos.dev-5777.jsonにzosからアクセスすべきスマートコントラクトを管理するためのProxiの項目が追加されます。

  "proxies": {
    "Base/Base": [
      {
        "address": "0xD2CFd001Ba9e1c6c469Da2528b2E8bcEaD71EaFe",
        "version": "1.0.0",
        "implementation": "0x1e169345e167BB71097448488BCFC107AfAba47E",
        "admin": "0x84EB303fcb2e5BCE99cA5C8E01cFa82e241055b9",
        "kind": "Upgradeable"
      }
    ]
  },
  "zosversion": "2.2",
  "version": "1.0.0",
  "proxyAdmin": {
    "address": "0x84EB303fcb2e5BCE99cA5C8E01cFa82e241055b9"
  }

スマートコントラクトを更新

先のスマートコントラクトはまずい点があります。一度、setOperationalでfalseを設定してしまうと、二度とOperationalになりません。そこで、無理やりですが、再度Operationalにするために、forceOperations()という関数そ追加して、スマートコントラクトを更新することにします(というシナリオ)。
新しいソースファイルBaseUp.solを作ります。

pragma solidity >0.5.0 <0.6.0;

import "./Base.sol";

contract BaseUp is Base {
  function forceOperational() public {
      __isOperational = true;
  }
}

本来は、スマートコントラクトのオーナーなどを設定して、オーナーのみが実行できるように、といった配慮をすべきところですが、そういったものは割愛しています。

zosにバージョンアップを指示します。

zos bump 2.0.0

zos.jsonの"version"の中身が1.0.0から2.0.0に書き換わります。

zosにBaseの更新版としてのBaseUpを追加します。

zos add BaseUp:Base

ここでもまた、zos.jsonの中身が書き換わりました

  "contracts": {
    "Base": "BaseUp"
  },

更新するBaseUpをデプロイします。

zos push --network develop

Truffleのコンソールに以下が出力されます。

  develop:ganache   Transaction: 0xe0cc2ae43e59f285317fac2a4fcb07c2b988b8eea9f87b30ca69b42c3bf19fd4 +0ms
  develop:ganache   Contract created: 0xe6f88bf5346c0823d854ccf460a8cf6f0823871c +0ms

また、zos.dev-5777.json中に記述される、スマートコントラクトBaseのアドレスが置き換わります。

  "contracts": {
    "Base": {
      "address": "0xE6f88bf5346C0823d854cCf460a8cf6f0823871c",

上記の情報をもとに、zosのプロキシを更新します。ただ、これは"tokenの設定”をしないとエラーになってしまいます。


/home/hajime/git/BlockchainDevND/lessons/updatable/contracts/Base.sol:27:5: Warning: Function state mutability can be restricted to view
    function isOperational()
    ^ (Relevant source part starts here and spans across multiple lines).

> Artifacts written to /home/hajime/git/BlockchainDevND/lessons/updatable/build/contracts
> Compiled successfully using:
   - solc: 0.5.12+commit.7709ece9.Emscripten.clang

All contracts are up to date
? Do you want to call a function on the instance after upgrading it? Yes
? Select which function * initialize(operational: bool)
? operational (bool): true
✖ Upgrading instance at 0xD2CFd001Ba9e1c6c469Da2528b2E8bcEaD71EaFe and cal
ling 'initialize' with:
- operational (bool): true
✖ Upgrading instance at 0xD2CFd001Ba9e1c6c469Da2528b2E8bcEaD71EaFe
Proxy Base/Base at 0xD2CFd001Ba9e1c6c469Da2528b2E8bcEaD71EaFe failed to upgrade with error: Error: Returned error: execution error: revert

「tokenの設定」をやってみたものの

「tokenの設定」なるものを進めてみようと思います。以下を実施します。

zos link openzeppelin-zos

ただ、以下のエラーで成功しません。

✓ Dependency openzeppelin-zos installed
Could not find a zos.json file for 'openzeppelin-zos'. (zos version identifier not found in /home/hajime/git/BlockchainDevND/lessons/updatable/node_modules/openzeppelin-zos/zos.json. This means the project was built with an older version of zos (1.x), and needs to be upgraded. Please refer to the documentation at https://docs.zeppelinos.org for more info.)

zosのバージョンの今日インストールしたばかりなので、1.xではありません。

$ zos --version
2.4.2

こんなIssue#1057を見つけました。まだ解決されていないってことですかね?

解決を待って以下を実行したいと思います。

zos link openzeppelin-zos
zos push --deploy-stdlib --network develop
zos create BaseUp --init --args \"$BASE_ADDRESS\",\"BaseToken\",\"BASE\" --network develop

更新可能性は実務レベルでは必須な機能と考えられますが、今のところは、開発者がそれぞれ注意して実装したほうが良さそうです。

参照
https://docs.zeppelinos.org/docs/1.0.0/basil

コメント

タイトルとURLをコピーしました