Ethereum
solidity
truffle
Ganache

Truffleを使ってEthereumでペットショップアプリを作る〜前半:コントラクトの実装&テスト〜

はじめに

Truffleのチュートリアルをひたすらやっていくだけです。
ETHEREUM PET SHOP

事前に用意しておくもの
Node.js(6以上)

Truffleのインストールとプロジェクトの作成

Truffle=トリュフ です。
絵柄的にきのこのトリュフじゃなくて、チョコだと思います。

npmを使ってインストールします。

$ npm install-g truffle

今回はworkフォルダの下にpet-shopフォルダを作ってプロジェクトとします。
通常はtruffle initでプロジェクトを作成するのですが、Truffle Boxという、テンプレみたいなのがあるらしく、その中にチュートリアルのペットショップ用のものがあるようなので今回はこれにします。
Truffle Boxes

$ cd work
$ mkdir pet-shop
$ cd pet-shop
$ truffle unbox pet-shop

pet-shopフォルダの中にいろんなものができました。

Ganacheのインストール

開発環境用のチェーンを作ったりGUIで見えるようにするやつだそうです。
ガナッシュがなんなのかわかりませんが、きっとお菓子なのでしょう。
gethだけでやってるときはターミナル画面にひたすらマトリックスみたいなのが流れて、アカウントなどはひたすらeth.accountsとかを打ってましたがそれが見えるようになるなんて楽ですね。

こちらからダウンロードします。
Ganache

インストールしたら起動しておきます。
ポートはデフォルトで7545が開放されているかと思うのでこれはこのままで。

コントラクトの記述

コントラクトはcontractsフォルダの中に入れていきます。
adoption.solを作成して、記述していきます。

$ touch contracts/Adoption.sol
Adoption.sol
pragma solidity ^0.4.2;

contract Adoption{

    // 変数宣言
    address[16] public adopters;
}

pragma~Migration.solにあわせて^0.4.2にしてみました。
Solidity?スマートコントラクト?Ethereum?の「お作法」に則り、コントラクトに関わるものは大文字で記述します。
ちなみにadoptは「採用する」というイメージが強いですが、ペットを飼うという意味もあるみたいです。

今回は「16匹が売り場に並んでおり、それぞれに飼い主がつく」という想定らしく、アドレス型で16個のarrayが宣言されています。
adopters[0]からadopters[15]まで作成されました。
publicなのでどこからでも呼び出せます。

このコントラクトの中に関数を書いていきます。

Adoption.sol
    // 整数型のpetIdを入れると結果が整数型で返ってくる関数
    function adopt(uint petId) public returns (uint){
        // ペットは16匹しかいないので、範囲外のリクエストが来たら終わりとする
        require(petId>=0 && petId<=15);

        adopters[petId] = msg.sender; //トランザクション実行者のアドレスを入れ込む

        return petId; // petIdを返す
    }

msg.senderはトランザクション実行者のアドレスを呼び出すメソッドですね。

ペットが買われていくとそれに応じて販売状況を更新しないといけないので現在の状況を取得するための関数を作ります。

Adoption.sol
    // adoptersの状況を取得する関数
    function getAdopters() public view returns (address[16]){

        return adopters;
    }

viewによって読み取り専用にしています。以前はconstantを使用していたようで、時々constantを使用したものを見ることがありますが、現在はviewでいいそうで。

コンパイル

ここまでは人間が読めるコードで書いて来ましたが、Ethereumに載せるにはバイトコードにしないといけないのでコンパイルします。

アプリケーションが入ったフォルダでTruffleのコンパイルを実行します。

$ truffle compile

完了すると最後にWriting artifacts to ./build/contractsと出てきますが、その通り、buildというディレクトリにJSON形式でのコントラクトが生成されました。
以下のような感じでコンパイルされました。

Adoption.json
"bytecode": "0x6060604052341561000f57600080fd5b6102dd8061001e6000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633de4eb171461005c57806343ae80d3146100ad5780638588b2c514610110575b600080fd5b341561006757600080fd5b61006f610147565b6040518082601060200280838360005b8381101561009a57808201518184015260208101905061007f565b5050505090500191505060405180910390f35b34156100b857600080fd5b6100ce60048080359060200190919050506101c8565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561011b57600080fd5b61013160048080359060200190919050506101fd565b6040518082815260200191505060405180910390f35b61014f610272565b60006010806020026040519081016040528092919082601080156101be576020028201915b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610174575b5050505050905090565b6000816010811015156101d757fe5b016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008082101580156102105750600f8211155b151561021b57600080fd5b3360008360108110151561022b57fe5b0160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550819050919050565b610200604051908101604052806010905b600073ffffffffffffffffffffffffffffffffffffffff1681526020019060019003908161028357905050905600a165627a7a7230582006abed684f9643fae8cd0377c8f2066f8c3226711091ba957fb093580ac96dd00029",
  "deployedBytecode": "0x606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633de4eb171461005c57806343ae80d3146100ad5780638588b2c514610110575b600080fd5b341561006757600080fd5b61006f610147565b6040518082601060200280838360005b8381101561009a57808201518184015260208101905061007f565b5050505090500191505060405180910390f35b34156100b857600080fd5b6100ce60048080359060200190919050506101c8565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561011b57600080fd5b61013160048080359060200190919050506101fd565b6040518082815260200191505060405180910390f35b61014f610272565b60006010806020026040519081016040528092919082601080156101be576020028201915b8160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610174575b5050505050905090565b6000816010811015156101d757fe5b016000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008082101580156102105750600f8211155b151561021b57600080fd5b3360008360108110151561022b57fe5b0160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550819050919050565b610200604051908101604052806010905b600073ffffffffffffffffffffffffffffffffffffffff1681526020019060019003908161028357905050905600a165627a7a7230582006abed684f9643fae8cd0377c8f2066f8c3226711091ba957fb093580ac96dd00029",
  "sourceMap": "25:635:0:-;;;;;;;;;;;;;;;;;",
  "deployedSourceMap": "25:635:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;567:90;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;567:90:0;;;;;;;;;;;;;;;;48:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;164:351;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;567:90;611:11;;:::i;:::-;642:8;635:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;567:90;:::o;48:27::-;;;;;;;;;;;;;;;;;;;;;;;;;:::o;164:351::-;207:4;348:1;341:5;:8;;:21;;;;;360:2;353:5;:9;;341:21;333:30;;;;;;;;391:10;373:8;382:5;373:15;;;;;;;;;;;:28;;;;;;;;;;;;;;;;;;485:5;478:12;;164:351;;;:::o;25:635::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o",
  "source": "pragma solidity ^0.4.2;\n\ncontract Adoption{\n    address[16] public adopters;\n\n    // 整数型のpetIdを入れると結果が整数型で返ってくる関数\n    function adopt(uint petId) public returns (uint){\n\n        // ペットは16匹しかいないので、範囲外のリクエストが来たら終わりとする\n        require(petId>=0 && petId<=15);\n        adopters[petId] = msg.sender; //トランザクション実行者のアドレスを入れ込む\n\n        return petId; // petIdを返す\n    }\n\n    // adoptersの状況を取得する関数\n    function getAdopters() public view returns (address[16]){\n \n        return adopters;\n    }\n\n}",

メインチェーンにデプロイするとバイトコードは誰でも見ようとすれば見れる状態になってしまいます。
したがって、コード上に直接秘密鍵を書いてしまうと読み取られてしまうらしいので要注意

マイグレーション

migrationsの中に2_deploy_contracts.jsを作成してマイグレーションに必要なコードを記述します。
シンプルなのでささっとvimで作成&記述。

$ vi migrations/2_deploy_contracts.js
2_deploy_contracts.js
var Adoption = artifacts.require("Adoption");

module.exports = function(deployer){
    deployer.deploy(Adoption);
};

意味はさっぱりわかりません。

そしてmigrateします。

$ truffle migrate

マイグレートされ、コントラクトがチェーン(今回はプライベートネット)の中にデプロイされました。
Ganacheを確認すると、index0のアドレスのetherが減り、BLOCKSのメニューを見るとなにやらブロックができ、GASが使用されていることがわかります。
Ganache.png

スマートコントラクトのテスト

ちゃんと出来てるかチェックします。
性質上、デプロイするともう未来永劫そのままで修正不可能なのでテストは必須だからなのか、Truffleにはご丁寧に、テスト用のあれこれが用意されています。
testフォルダができていると思うのでここにあれこれテスト用のものを作っていきます。

$ touch test/TestAdoption.sol
TestAdoption.sol
pragma solidity ^0.4.2;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol"; //デプロイしたアドレスを取得
import "../contracts/Adoption.sol"; //テスト対象のコントラクト

contract TestAdoption {
    Adoption adoption = Adoption(DeployedAddresses.Adoption());
}

Assert.solですが、Truffleに用意されてる変数の型などをチェックしてくれる関数が詰まったやつだそうです。
フルコードはこちら

8番のものを試しに買ってみて、想定する数値が返されるかのテストをコンストラクトの中に追記。
全文はこんな感じ。

TestAdoption.sol
pragma solidity ^0.4.2;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {
    Adoption adoption = Adoption(DeployedAddresses.Adoption());

    // petIdの投入テスト
    function testUserCanAdoptPet() public {
        uint returnedId = adoption.adopt(8); //adopt関数は入れた数が返ってくるはず

        uint expected = 8; // 8を入れたら8が返ってくるはず

        // 結果が想定と正しくなかったらエラーメッセージを出す
        Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
    }

    // 8番にきちんと飼い主のアドレスが記録されているかのテスト
    function testGetAdopterAddressByPetId() public {
        address expected = this;

        address adopter = adoption.adopters(8);

        Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
    }

    // 配列の8番目にきちんと入っているかのテスト
    function testGetAdopterAddressByPetIdInArray() public {
        address expected = this;

        address[16] memory adopters = adoption.getAdopters();

        Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
    }
}

テストを実行

$ truffle test

なんだかんだあって、こんな感じのが出てればテスト合格です。

  TestAdoption
    ✓ testUserCanAdoptPet (77ms)
    ✓ testGetAdopterAddressByPetId (64ms)
    ✓ testGetAdopterAddressByPetIdInArray (76ms)

  3 passing (999ms)

コントラクト部分はこれでOKなので続いてWeb UIの部分を作っていきます。
後半へ続く