ZeppelinOSでUpgradableなERC721トークンを実装する

## はじめに
基本的にEthereumに一度デプロイされたコントラクトはアップグレードできないため、コードにバグがあるとプロダクトにとって致命傷となってしまいます。
ですが、Proxyパターンを利用することによりアップグレード可能なコントラクトを作成することが可能になります。

Proxyパターンの理解は以下の記事が非常に分かりやすくまとめてくださっているので、まずはこちらをご一読の上、続きをご覧いただければと思います。

■アップグレード可能なスマートコントラクトを実現する具体的なアプローチ
https://zoom-blc.com/how-to-develop-upgradable-contracts

今回はZeppelinOSというアップグレード可能なコントラクトを実装するためのフレームワークを利用してERC721トークンを作っていきます。（ZeppelinOSを使わなくてもProxyContractを自分で書くことで実現できますが、その作業が不要になったりと何かと嬉しいです）

## まずは成果物
成果物はこちらに上げています。
https://github.com/shiruco/upgradable-ERC721-sample

内容を簡単に説明します。
以下、ローカル環境のプライベートチェーンで動作させるものとします。

1. まず `SampleToken` (ERC721) をデプロイ。
2. 続けてSampleTokenのアップグレード版 `SampleToken_v1` (ERC721)をデプロイ。
3. SampleTokenの実装がSampleToken_v1に変更されていることを確認

という流れです。

## 開発環境
私の開発環境は以下のようになっています。もし実際に試されるのであればバージョンを揃えていただいた方がいいかもしれません。

- ZeppelinOS 2.2.0
- Truffle v5.0.4 (core: 5.0.4)
- Solidity v0.5.0 (solc-js)
- Ganache CLI v6.3.0 (ganache-core: 2.4.0)
- Node v11.3.0

ZeppelinOSのインストールはこちら。 (私はグローバルに入れました)

```bash
npm install -g zos
or
yarn global add zos
```

## 中身の解説
contractディレクトリの中には `SampleToken` と `SampleToken_v1` というコントラクトが入っています。それぞれのコードは以下のようになっており、差分はversion情報を返す関数の返却値が違うのみです。（SampleTokenは`v0`を。SampleToken_v1は`v1`を返します）

### SampleToken

中身について軽く触れておくと、`zos-lib`と`openzeppelin-eth`というライブラリを使用しています。`zos-lib`はアップグレード可能なコントラクトを記述するためのライブラリで、constructorの代わりにinitialize関数を実装します。
openzeppelin-ethはセキュアなスマートコントラクトを実装するためのライブラリです。 `openzeppelin-solidity`との違いにちょっと混乱しましたが、リポジトリのREADMEを見ると、openzeppelin-solidityとの大きな違いはopenzeppelin-ethはアップグレードする可能性のあるコントラクトになっているようです。ZeppelinOSを利用するなら`openzeppelin-eth`を利用したほうがよいでしょう。

> The main difference is that all contracts in this package are potentially upgradeable
:
> All in all, you should use this package instead of openzeppelin-solidity if you are managing your project via ZeppelinOS.

https://github.com/OpenZeppelin/openzeppelin-eth

```js

pragma solidity ^0.5.0;

import 'zos-lib/contracts/Initializable.sol';
import 'openzeppelin-eth/contracts/token/ERC721/ERC721Full.sol';
import 'openzeppelin-eth/contracts/token/ERC721/ERC721Mintable.sol';

contract SampleToken is Initializable, ERC721Full, ERC721Mintable {

  function initialize(string memory name, string memory symbol) public initializer {
    ERC721.initialize();
    ERC721Enumerable.initialize();
    ERC721Metadata.initialize(name, symbol);
    ERC721Mintable.initialize(msg.sender);
  }

  function version() public view returns (string memory) {
    // set version
    string memory ver = "v0";

    return ver;
  }
}
```

### SampleToken_v1

```js

pragma solidity ^0.5.0;

import "zos-lib/contracts/Initializable.sol";
import 'openzeppelin-eth/contracts/token/ERC721/ERC721Full.sol';
import 'openzeppelin-eth/contracts/token/ERC721/ERC721Mintable.sol';

contract SampleToken_v1 is Initializable, ERC721Full, ERC721Mintable {

  function initialize(string memory name, string memory symbol) public initializer {
    ERC721.initialize();
    ERC721Enumerable.initialize();
    ERC721Metadata.initialize(name, symbol);
    ERC721Mintable.initialize(msg.sender);
  }

  function version() public view returns (string memory) {
    // set version
    string memory ver = "v1"; <=== ここが違う

    return ver;
  }
}
```
### zosコマンドでデプロイ
では実際にこのコントラクトをデプロイして動作を確認していきます。
npmインストール後、下記のコマンドでZeppelinOSプロジェクトを初期化します。
プロジェクト名は好きに指定してください。

```bash
$ zos init {プロジェクト名}
```

初期化が終わるとプロジェクトルート直下に`zos.json`が生成されます。zos.jsonはZeppelinOSプロジェクトのコントラクト情報やバージョン情報などが記載されています。

続けて下記コマンドでZeppelinOSプロジェクトにSampleTokenコントラクトを追加します。

```bash
$ zos add SampleToken
```
次にSampleTokenをデプロイするのですが、デプロイ先として今回はローカルでプライベートチェーンを立てておきます。ganache-cliを利用して、7545番ポートで立ち上げます。

```bash
$ ganache-cli -p 7545 -d
```

準備が整ったので、下記コマンドでデプロイします。networkにはlocalを指定します。これは`truffle-config.js`に定義されている情報を参照します。

```bash
$ zos push --network local
```

成功すると`zos.dev-{ネットワークID}.json`ファイルが生成されます。中身をみると、SampleTokenコントラクトにaddressが振られており、無事デプロイされていることが分かります。（この時点ではproxiesフィールドの中身は空です）
続いてこのコントラクトのProxyContractをデプロイしていきます。ProxyContractをデプロイするには以下のコマンドで行います。引数に`name`と`symbol`を渡しています。

```bash
$ zos create SampleToken --init initialize --args="SampleToken,STKN" --network local
```
無事にデプロイされると、`zos.dev-{ネットワークID}.json`のproxiesフィールドにProxyContractのaddressが入っているはずです。また、implementationがSampleTokenのaddressを指しています。
これでProxyContractを通してSampleTokenにアクセスできると思います。実際に試してみます。

```bash
$ truffle console --network local
truffle(local)> token = await SampleToken.at(<proxy-address>)
truffle(local)> token.version()
'v0'
```
version関数で`v0`が返ってきたのでうまくいっているようです。

ではSampleTokenをアップグレードします。SampleToken_v1がアップグレード版となっており、これを同様にデプロイします。（SampleToken_v１をSampleTokenのエイリアスとして追加し、デプロイしています。）

```bash
$ zos add SampleToken_v1:SampleToken
$ zos push --network local
```
ProxyContractはすでにデプロイされているのでcreateではなく、updateします。

```bash
$ zos update SampleToken --network local
```
これでアップグレード作業は完了です。
（`zos.dev-{ネットワークID}.json`のproxiesフィールドにあるimplementationのアドレスがSampleToken_v1のaddressを指すように変更された）

ではもう一度確認してみます。

```bash
$ truffle console --network local
truffle(local)> token = await SampleToken.at(<proxy-address>)
truffle(local)> token.version()
'v1'
```
`v1`が返ってきたので、無事アップグレードされていました。

### Truffleでデプロイ
上記はzosコマンドでデプロイしていきましたが、サンプルにはmigrationファイルが用意されており、`truffle migrate` でも確認できるようになっていますので、試してみてください。

```bash
$ zos init {プロジェクト名}
$ truffle migrate --network local
```

```bash
$ truffle console --network local
truffle(local)> token = await SampleToken.at(<proxy-address>)
truffle(local)> token.version()
'v1'
```

## 参考
https://blog.zeppelinos.org/proxy-patterns/
https://zoom-blc.com/how-to-develop-upgradable-contracts