LoginSignup
1

More than 3 years have passed since last update.

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

Posted at

ちょっと、何言ってるかわからない。

えっと、Ethereum スマートコントラクトの Hyperledger 上でのデバッグ方法です。いや、やっぱりわかりませんね。

こちらの記事「Hyperledger fabric の EVM を試す」で 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で動くかどうか試してみたいのは前回の記事「イーサリアムのテストネットで初めてのオリジナルトークンを公開してみる」でデプロイした"マイERC20トークン"のスマートコントラクトです。OpenZeppelinをimportしたスマートコントラクトがHyperledgerで動くなら、ERC721トークンがHyperledgerに乗っかることも可能じゃないですかっ!?っていうことで、試してみたいわけです。
簡単ではございますが、こちらです。

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);
    }
}

_decimalvalue が適当すぎるのはご愛嬌ということで・・・

対象プロジェクト

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

環境構築

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

GO言語環境の構築

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

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

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

続いてdepを入れます。

> go get -u github.com/golang/dep/cmd/dep

プロジェクトのクローン

go getでプロジェクトを取得します。

> 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を選ぶとスニペット貼り付けてくれるので、以下のように設定を変更します。

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

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

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

go test -ginkgo.v

Ginkgo について

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

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

以下は evmcc_test.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 サーバーで上記で紹介したスマートコントラクトのコードを貼り付けます。

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

テストコードの追加

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

    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するテストコードは以下のようになります。

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

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

Web3.js では。

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

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

逆は

> web3.toAscii("0x48595000000000000000000000000000")
"HYP                   "

でOKです。ちょっと後半の0000...の箇所が空白が入ってしまうのはアレですが・・・

メソッドのテストコード

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

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

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"))

transfera9059cbbbalanceOf70a08231が割り当てられており、その後ろに引数となる値(ここではuser2Addr.Word256().Bytes()1000のバイナリ値の文字列など)をくっつけて丸ごと投げています。

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

まとめ

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

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

以上!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1