【2019年4月版】Solidityでビルドしたバイナリがちゃんと動くかHyperledgerのUTでデバッグしてみる。

# ちょっと、何言ってるかわからない。
えっと、Ethereum スマートコントラクトの Hyperledger 上でのデバッグ方法です。いや、やっぱりわかりませんね。

こちらの記事「[Hyperledger fabric の EVM を試す](https://qiita.com/koinori/items/a76bc513459f6d69fa34)」で Hyperledger 上で EVM が動かせて、Solidity でビルドしたスマートコントラクトがデプロイできてちゃんと動作する、というのはご紹介いたしました。

ところがですね・・・~~デプロイ手順がめんどくさい~~web3.js経由のデプロイしてしっかりブロックチェーンに書き込まれると後戻りができないのでその手前でごにょごにょしてみたいわけです。

ま、要するにデプロイ手順がめんどくさいのです！！

## 今回のゴール

hyperledger-fabric で EVM のチェインコード(Hyperledger流のスマートコントラクト)を実行する実際のモジュールである "fabic-chaincode-evm" のUTコードに自前のスマートコントラクトを追加してデバッグしてみる、という試みです。

## 対象環境

OS: Windows 10
IDE: VSCode
言語： GO 1.12.1

VSCodeはエディタですがもはやIDEでしょうということで。

## 対象のスマートコントラクト

実際にEVMで動くかどうか試してみたいのは前回の記事「[イーサリアムのテストネットで初めてのオリジナルトークンを公開してみる](https://qiita.com/koinori/items/ceb97858d378d5a3daf2)」でデプロイした"マイERC20トークン"のスマートコントラクトです。OpenZeppelinをimportしたスマートコントラクトがHyperledgerで動くなら、ERC721トークンがHyperledgerに乗っかることも可能じゃないですかっ！？っていうことで、試してみたいわけです。
簡単ではございますが、こちらです。

```solidity
pragma solidity ^0.5.2;

import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "https://github.com/OpenZeppelin/openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol";

contract MyTokenERC20Sample is ERC20, ERC20Detailed {

  string private _name = "Hyperledger Token";
  string private _symbol = "HYP";
  uint8 private _decimals = 18;

  uint value = 1000000;

  constructor()
	ERC20Detailed(_name, _symbol, _decimals)
	public
	{
		_mint(msg.sender, value);
	}
}
```
`_decimal` や `value` が適当すぎるのはご愛嬌ということで・・・
## 対象プロジェクト

Hyperledger配下には多数のプロジェクトがございますが、今回のターゲットは以下です。

- [hyperledger/fabric-chaincode-evm](https://github.com/hyperledger/fabric-chaincode-evm)

## 環境構築

それではさっそくデバッグ環境の構築を行います。

### GO言語環境の構築

`fabric-chaincode-evm` の開発環境であるGo言語での開発環境構築を整えます。

Go言語バージョン `1.12.1`のインストーラーを以下のダウンロードサイトからダウンロード＆インストールをお願いします。

- [Golang Download](https://golang.org/dl/)

インストール後、`$GOPATH/bin`を`PATH`に追加しておきます。

続いて`dep`を入れます。

```console
> go get -u github.com/golang/dep/cmd/dep
```
### プロジェクトのクローン
`go get`でプロジェクトを取得します。

```console
> go get -u github.com/hyperledger/fabric-chaincode-evm
```
うまくいかない場合は`mkdir`で自前でフォルダ作成し、階層ずれないように注意しつつ`git clone`します。

```
> mkdir $GOPATH/src/github.com/hyperledger
> cd $GOPATH/src/github.com/hyperledger
> git clone https://github.com/hyperledger/fabric-chaincode-evm.git
> cd fabric-chaincode-evm
```

つづいて依存モジュールのインストール`dep`で一発です。

```
> dep ensure
```

### VSCode の起動

VS Codeを起動し、Go言語用のプラグインをインストールしてください。Extentionの検索で`go`を調べるとMicrosoft提供のGoプラグインが見つかると思います。

### ターゲットの階層へ移動

いよいよ確信へと迫ります。

```
> cd evmcc
```

まずは試しにターミナルから `go test` と打って通常のUTが動くかどうか、確かめます。いや、動いてくれなきゃ困るけど・・・

```
> go test
```

### VSCodeのデバッグ設定

続きましてVSCodeから単体テストのデバッグ起動を行うための設定をします。

デバッグ画面から ⚙ ボタンクリックによって`launch.json`が開きます。エディタの右下の`Add Configuration...`ボタンがありがたいです。
`Add Configuration...`ボタンで`Go: Launch test function`を選ぶとスニペット貼り付けてくれるので、以下のように設定を変更します。

```json
    {
      "name": "Launch test function",
      "type": "go",
      "request": "launch",
      "mode": "test",
      "program": "${workspaceFolder}/evmcc",
      "args": ["-ginkgo.v", "-ginkgo.trace"]
    },
```

上記のように `go test` から `ginkgo` にフラグなどの引数を渡したい場合は`-ginkgo.v`のように記述します。

- [How to pass arguments to ginkgo if I run go test? #381](https://github.com/onsi/ginkgo/issues/381)

例えばログを詳細に出したい場合は以下のようにするわけです。

```
go test -ginkgo.v
```
### Ginkgo について

さらっと Ginkgo についてご紹介すると GO言語での BDD ベースのテストフレームワークでございます。

- [本家 Github サイト](https://github.com/onsi/ginkgo)
- [本家の Doc サイト](http://onsi.github.io/ginkgo/)
- [Go 言語で Ginkgo を利用して BDD してみた](https://re-engines.com/2018/10/29/go%E8%A8%80%E8%AA%9E%E3%81%A7ginkgo%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6bdd%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F/)

他言語での BDD フレームワークに親しんでいる方は特に引っかかることもないと思います。`Describe`、`Context`の 2 段階構成で`It`,`Expect`と書けます。
またアサーションは標準で`gomega`がおすすめなようです。

以下は `evmcc_test.go` の一部です。

```go:evmcc_test.go
	Describe("Init", func() {
		It("returns an OK response", func() {
			res := evmcc.Init(stub)
			Expect(res.Status).To(Equal(int32(shim.OK)))
			Expect(res.Payload).To(Equal([]byte(nil)))
		})
	})
```

## テストケースの作成

さて環境構築は完了です。いよいよテストケースを追加してみます。

### Solidity でビルドしたバイナリの準備

いつもの通り、[Remix サーバー](https://remix.ethereum.org)で上記で紹介したスマートコントラクトのコードを貼り付けます。

OpenZeppelin のコードが`solidity ^0.5.2;`の縛りが入っているのでこちらもそれに合わせます。
コンパイラを`0.5.2`以上のものを選択し、コンパイラのロードが終了後`Ctrl+S`などでコンパイルします。
コンパイルが無事に終わったようであれば`Bytecode`ボタンをクリックでバイナリを取得し、適当なテキストエディタに貼り付けておきます。

### テストコードの追加

他のテストケースの冒頭部分を拝借して以下のようなコードを追加いたしました。
ここでは単にデプロイするだけのチェックです。

```go
	Describe("ERC20 with OpenZeppelin Dapp", func() {
		var (
			user0Cert = `-----BEGIN CERTIFICATE-----
MIIB/zCCAaWgAwIBAgIRAKaex32sim4PQR6kDPEPVnwwCgYIKoZIzj0EAwIwaTEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
cmFuY2lzY28xFDASBgNVBAoTC2V4YW1wbGUuY29tMRcwFQYDVQQDEw5jYS5leGFt
cGxlLmNvbTAeFw0xNzA3MjYwNDM1MDJaFw0yNzA3MjQwNDM1MDJaMEoxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp
c2NvMQ4wDAYDVQQDEwVwZWVyMDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPzs
BSdIIB0GrKmKWn0N8mMfxWs2s1D6K+xvTvVJ3wUj3znNBxj+k2j2tpPuJUExt61s
KbpP3GF9/crEahpXXRajTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA
MCsGA1UdIwQkMCKAIEvLfQX685pz+rh2q5yCA7e0a/a5IGDuJVHRWfp++HThMAoG
CCqGSM49BAMCA0gAMEUCIH5H9W3tsCrti6tsN9UfY1eeTKtExf/abXhfqfVeRChk
AiEA0GxTPOXVHo0gJpMbHc9B73TL5ZfDhujoDyjb8DToWPQ=
-----END CERTIFICATE-----`

			creator = marshalCreator("TestOrg", []byte(user0Cert))

			deployCode  = []byte("60c0604052601160808190527f48... b3068c0029")
		)

		BeforeEach(func() {
			// Set contract creator
			stub.GetCreatorReturns(creator, nil)
		})

		It("will create and store the runtime bytecode from the deploy bytecode and a user account", func() {
			// zero address, and deploy code is contract creation
			stub.GetArgsReturns([][]byte{[]byte(crypto.ZeroAddress.String()), deployCode})
			res := evmcc.Invoke(stub)
			Expect(res.Status).To(Equal(int32(shim.OK)))

		})
	})
```

そして最後の`Expect`の箇所にブレークポイントを設定し、デバッグを走らせてみましょう。
ちゃんとブレークポイント決まりました！(よね？！)

これでSolidityのビルドバイナリを貼り付ければダイレクトに実行中のEVM(の外側)がデバッグできる環境ができました。

### プロパティ(引数なしメソッド)のテストコード

このスマートコントラクトは`ERC20 Details`を継承していますので`symbol`などのプロパティがあります。

これを取得して`Assert`するテストコードは以下のようになります。

```golang:evmcc_test.go
stub.GetArgsReturns([][]byte{[]byte(contractAddress.String()), []byte("95d89b41"})
res := evmcc.Invoke(stub)
Expect(res.Status).To(Equal(int32(shim.OK)))
// encoded bytes for "HYP"
// It consists of three elements which take byte32 each:
// - 0x32 the location of data
// - 0x3  the length of array
// - 0x728980 left-aligned 'HYP'
// Payload is "00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000372898000000000000000000000000000000000000000000000000000000000000"
Expect(string(res.Payload[64:67])).To(Equal("HYP"))
```

まず1行目の`95d89b41`は`symbol`のメソッドがある番地です。これはRemixからスマートコントラクトを動かしてみて、`symbol`の値を取得するとそのトランザクションログが流れますが、その`input`の欄からコピーできます。
(というかそれ以外に計算方法などないのだろうか・・・？？)

またSolidityは文字列の扱いが強烈に大変です。生UTF-8バイナリがbytes32に入っている、という型としてきます。なんだかよくわからないけどそんなもんです。。。
なので上記のテストケースも`res.Payload[64:67]`を`string`にする、という方法で`HYP`と比較しています。

#### Web3.js では。

Web3.js ではこのHexな文字列と元の文字列との交換は簡単で以下のように行います。

```javascript
> web3.padRight(web3.fromAscii('HYP'), 34)
"0x48595000000000000000000000000000"
```

逆は

```javascript
> web3.toAscii("0x48595000000000000000000000000000")
"HYP                   "
```
でOKです。ちょっと後半の`0000...`の箇所が空白が入ってしまうのはアレですが・・・

### メソッドのテストコード

引数付きのメソッドもだいたい似たようなものですが、メソッドの番地の後ろに引数のbyte値を**つなげて**呼び出す必要があります。これもRemixで叩いてみて、ログが流れますので`input`欄からコピーすれば丸ごと取得できます。

実際には以下のようなコードとなります。

```golang:evmcc_test.go
// 1000 -> 0x00000000000000000000000000000000000000000000000000000000000003e8
stub.GetArgsReturns([][]byte{[]byte(contractAddress.String()), []byte(transfer + hex.EncodeToString(user2Addr.Word256().Bytes()) + "00000000000000000000000000000000000000000000000000000000000003e8")})
res := evmcc.Invoke(stub)
Expect(res.Status).To(Equal(int32(shim.OK)))

stub.GetArgsReturns([][]byte{[]byte(contractAddress.String()), []byte(balanceOf + hex.EncodeToString(user2Addr.Word256().Bytes()))})
res = evmcc.Invoke(stub)
Expect(res.Status).To(Equal(int32(shim.OK)))
// 1000 -> 0x00000000000000000000000000000000000000000000000000000000000003e8
Expect(hex.EncodeToString(res.Payload)).To(Equal("00000000000000000000000000000000000000000000000000000000000003e8"))
```
`transfer`は`a9059cbb`、`balanceOf`は`70a08231`が割り当てられており、その後ろに引数となる値(ここでは`user2Addr.Word256().Bytes()`や`1000`のバイナリ値の文字列など)をくっつけて丸ごと投げています。

戻り値が数値の場合も諦めてHexな文字列で比較してしまっています。

## まとめ

以下が実際に追加した箇所のテストコードになります。

- [evmcc_test.go](https://gist.github.com/Yoshinori-Koide-PRO/10c0255d0513ac47a39a3e9718eeb3ee)

SolidityのTypeのエンコードや文字列の扱い(生UTF-8のbytes32)などかなり大変ですが、リアルにスマートコントラクトが動くのがわかるので、そしてどんだけ大きくてもガス代取られないしバグっても問題ないので、トライ＆エラーするには大変有効であると思います。

以上！