【TruffleでDapp開発】公式チュートリアル「ETHEREUM PET SHOP」で基礎を学ぶ
【はじめに】
イーサリアムを購入してからブロックチェーンに興味を持ち、休みの日はDapp開発の勉強をしています。技術情報を調べていると【Truffle】というイーサリアム開発のフレームワークに出会いました。Truffleの公式サイトに「ETHEREUM PET SHOP」というチュートリアルがあり、Dapp開発の基礎を学べるので早速触ってみました。チュートリアルと言ってもインストールでエラーが発生したり、環境構築で四苦八苦しましたので、苦労したところをブログに残しておこうと思います。
【参考にしたブログ】
Dapp開発初心者かつ英語も得意ではないので、こじりょー(@kojiryoinvestor)さんのブログを参考に進めました。大変分かり易かったです。ありがとうございます。
【Truffleのインストール】
nodeとnpmが使える環境で以下のコマンドを実行します。
nodeはv6以上をインストールしておかないと、後々困るので注意してください。私は見落としていて時間を無駄にしてしまいました。。。
npm install -g truffle
【バージョン確認】
以下のコマンドを実行し、Truffleのバージョンが表示すれば正常にインストールされています。
truffle version
正しくインストールされている場合、TruffleとSolidityのバージョンが表示されます。
Truffle v4.1.13 (core: 4.1.13) Solidity v0.4.24 (solc-js)
私の場合、ここでSyntaxErrorが発生しました。
SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
解決方法はこちらにまとめています。
【Truffleのプロジェクト作成】
Truffleのプロジェクトを格納するディレクトリを作成し、そのディレクトリに移動します。
mkdir pet_shop cd pet_shop
下記のコマンドで「ETHEREUM PET SHOP」のプロジェクト一式がダウンロードできます。
truffle unbox pet-shop
成功すると下記の様に「Unbox successful. Sweet!」が表示されます。
Downloading... Unpacking... Setting up... Unbox successful. Sweet! Commands: Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test Run dev server: npm run dev
私の場合、「Downloading...」で止まり、先に進んでいる様子がありませんでした。
解決方法はこちらにまとめています。
【スマートコントラクトの作成】
contractsのディレクトリ配下に「Adoption.sol」を新規作成します。ファイルの中身は次の様に記載します。
pragma solidity ^0.4.17; contract Adoption { address[16] public adopters; // Adopting a pet function adopt(uint petId) public returns (uint) { require(petId >= 0 && petId <= 15); adopters[petId] = msg.sender; return petId; } // Retrieving the adopters function getAdopters() public view returns (address[16]) { return adopters; } }
【コンパイル】
truffle compile
build/contractsのディレクトリ配下に「Adoption.json」、「Migrations.json」が作成されていればコンパイルが正常に完了しています。
Windowsの場合、コマンドプロンプトでコンパイルすると、以下の様にtruffle.jsのエラーになります。公式にも記載さている通り、私はWindows PowerShellでコンパイルしました。
truffle.jsのエラー
エラー:'module'は宣言されていません。
コード:800A1391
ソース:MicrosoftJScript 実行時エラー
【マイグレーション】
作成したスマートコントラクトをデプロイするためにマイグレーションファイルが必要です。migrationsのディレクトリ配下に「2_deploy_contracts.js」を新規作成します。ファイルの中身は次の様に記載します。
var Adoption = artifacts.require("Adoption"); module.exports = function(deploy) { deploy.deploy(Adoption); }
【Ganacheを起動】
開発用のブロックチェーン(プライベートネットワーク)が必要なので、ローカル環境にイーサリアムのブロックチェーンを構築してくれるツール「Ganache」を使用します。Ganacheをダウンロードして起動します。
Ganacheはこちらからダウンロードできます。
起動するとテスト用のアドレスが割り振られます。
【デプロイ】
Ganacheを起動した状態で以下のコマンドを実行します。
truffle migrate
次の様な出力が表示されます。
Using network 'development'. Running migration: 1_initial_migration.js Deploying Migrations... ... 0x1d88cf62c4d2c04cd115f578496740c76198299db54e706f751f21b63425b37c Migrations: 0x15637172d8cf0057da00a7b55e8bf6843c44163a Saving successful migration to network... ... 0x4a6862964a8c66850373e62d3cd8b364667d437f81f6b00c0b7f24a93c24679e Saving artifacts... Running migration: 2_deploy_contracts.js Deploying Adoption... ... 0x207859a3b740dc07200a2f0068adf25390044725ed4846d0b94efbd69f366104 Adoption: 0xf979539e570d3d49bd2dc97741ab1df96339f132 Saving successful migration to network... ... 0xf3325f15f22d04ed92ca9b06bf802c9df13f5a182532980c508e8dd6d15e8be7 Saving artifacts...
Ganacheを見るとスマートコントラクトをデプロイする際のコストとして、ETHが消費されていることが分かります。
これでプライベートネットワークにスマートコントラクトがデプロイされました。
【スマートコントラクトのテスト】
TruffleはJavaScriptやSolidityでテストコードが作成できます。チュートリアルでは、Solidityで書いています。testのディレクトリ配下に「TestAdoption.sol」を新規作成します。ファイルの中身は次の様に記載します。
pragma solidity ^0.4.17; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../contracts/Adoption.sol"; contract TestAdoption { Adoption adoption = Adoption(DeployedAddresses.Adoption()); // Testing the adopt() function function testUserCanAdoptPet() public { uint returnedId = adoption.adopt(8); uint expected = 8; Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded."); } // Testing retrieval of a single pet's owner function testGetAdopterAddressByPetId() public { // Expected owner is this contract address expected = this; address adopter = adoption.adopters(8); Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded."); } // Testing retrieval of all pet owners function testGetAdopterAddressByPetIdInArray() public { // Expected owner is this contract address expected = this; // Store adopters in memory rather than contract's storage address[16] memory adopters = adoption.getAdopters(); Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded."); } }
以下のコマンドでテストを実行します。
truffle test
次の様なテスト結果が出力されます。
Using network 'development'. Compiling .\contracts\Adoption.sol... Compiling .\test\TestAdoption.sol... Compiling truffle/Assert.sol... Compiling truffle/DeployedAddresses.sol... TestAdoption √ testUserCanAdoptPet (55ms) √ testGetAdopterAddressByPetId (51ms) √ testGetAdopterAddressByPetIdInArray (73ms) 3 passing (802ms)
すべてのテストが想定通りの結果になりましたので、テストは完了です。
【ユーザインタフェースの作成】
バックエンド側の実装が完了しましたので、続いてフロント側を実装していきます。src/jsのディレクトリ配下にある「app.js」を開きます。必要な関数は全て準備されていますが中身がコメントになっています。コメント部分をチュートリアルに記載されているコードに置き換えていきます。置き換えるのは、initWeb3、initContract、markAdopted、handleAdoptの4箇所です。
以下、完成した「app.js」のコードになります。
App = { web3Provider: null, contracts: {}, init: function() { // Load pets. $.getJSON('../pets.json', function(data) { var petsRow = $('#petsRow'); var petTemplate = $('#petTemplate'); for (i = 0; i < data.length; i ++) { petTemplate.find('.panel-title').text(data[i].name); petTemplate.find('img').attr('src', data[i].picture); petTemplate.find('.pet-breed').text(data[i].breed); petTemplate.find('.pet-age').text(data[i].age); petTemplate.find('.pet-location').text(data[i].location); petTemplate.find('.btn-adopt').attr('data-id', data[i].id); petsRow.append(petTemplate.html()); } }); return App.initWeb3(); }, initWeb3: function() { // Is there an injected web3 instance? if (typeof web3 !== 'undefined') { App.web3Provider = web3.currentProvider; } else { // If no injected web3 instance is detected, fall back to Ganache App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545'); } web3 = new Web3(App.web3Provider); return App.initContract(); }, initContract: function() { $.getJSON('Adoption.json', function(data) { // Get the necessary contract artifact file and instantiate it with truffle-contract var AdoptionArtifact = data; App.contracts.Adoption = TruffleContract(AdoptionArtifact); // Set the provider for our contract App.contracts.Adoption.setProvider(App.web3Provider); // Use our contract to retrieve and mark the adopted pets return App.markAdopted(); }); return App.bindEvents(); }, bindEvents: function() { $(document).on('click', '.btn-adopt', App.handleAdopt); }, markAdopted: function(adopters, account) { var adoptionInstance; App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance; return adoptionInstance.getAdopters.call(); }).then(function(adopters) { for (i = 0; i < adopters.length; i++) { if (adopters[i] !== '0x0000000000000000000000000000000000000000') { $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true); } } }).catch(function(err) { console.log(err.message); }); }, handleAdopt: function(event) { event.preventDefault(); var petId = parseInt($(event.target).data('id')); var adoptionInstance; web3.eth.getAccounts(function(error, accounts) { if (error) { console.log(error); } var account = accounts[0]; App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance; // Execute adopt as a transaction by sending account return adoptionInstance.adopt(petId, {from: account}); }).then(function(result) { return App.markAdopted(); }).catch(function(err) { console.log(err.message); }); }); } }; $(function() { $(window).load(function() { App.init(); }); });
【MetaMaskのインストールと設定】
ETHEREUM PET SHOPの実装は全て完了しましたが、取引する為にはMetaMaskが必要になります。
チュートリアルではGanacheに表示されるMnemonicを使用してMetaMaskのアカウントを作成していますが、メインネットでこのアカウントにETHを送信すると全て失います。
私はMetaMaskのアカウントを別で作成していましたので、こじりょーさんのブログ(MetaMaskをプライベートネットに接続しようの箇所)を参考にして、Ganacheに表示されているアドレスをMetaMaskに取り込みました。この辺りは言葉の意味を理解できていないとセルフゴックス(操作ミスで仮想通貨を失う)の可能性があります。誰も責任を取ってくれませんので自己責任でお願いします。私の拙い文章では誤解される可能性がありますので詳細を省きます。
【ETHEREUM PET SHOPを表示】
以下のコマンドを実行すると、ETHEREUM PET SHOPがブラウザに表示されます。チュートリアルなので、lite-server(ローカルのWEBサーバ)の起動、Webサイトはsrcディレクトリ配下のファイルを使用、スマートコントラクトはbuild/contractsディレクトリ配下のファイルを使用するように設定済みでした。
npm run dev
遂にETHEREUM PET SHOPを表示することができました!
早速、ペットを購入してみます。Adoptボタンをクリック!
MetaMaskでETHを支払うと・・・
Successに変わりました。正常に購入できました!
【まとめ】
環境構築に時間を費やしてしまいましたが、その後は止まることなく進めることができました。このチュートリアルを行うことで、TruffleやGanacheを使用してプライベートネットワークにデプロイする方法とプロジェクトの構成(フロント、バックエンド、デプロイに必要な設定など)が理解できました。新しい技術を勉強するのは楽しいですね。もっと時間が欲しい。
【TruffleでDapp開発】「truffle unbox pet-shop」を実行すると「Downloading...」で止まる
【はじめに】
イーサリアムを購入してからブロックチェーンに興味を持ち、休みの日はDapp開発の勉強をしています。技術情報を調べていると【Truffle】というイーサリアム開発のフレームワークに出会いました。早速触ってみましたが、インストールでエラーが発生したり環境構築で四苦八苦しています。
【ETHEREUM PET SHOP】
先回、何とかTruffleのインストールが完了したので、公式サイトにあるチュートリアル「ETHEREUM PET SHOP」をダウンロードしてみました。
【ダウンロード】
Truffleのインストールが完了していると、下記のコマンドで「ETHEREUM PET SHOP」のプロジェクト一式がダウンロードできます。
truffle unbox pet-shop
成功すると下記の様に「Unbox successful. Sweet!」が表示されます。
Downloading... Unpacking... Setting up... Unbox successful. Sweet! Commands: Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test Run dev server: npm run dev
私の場合は「Downloading...」で止まり、先に進んでいる様子がありません。。。
【原因】
セキュリティソフトが原因でした。Webのトラフィックを監視する機能を無効にして再度、コマンドを実行すると「Unbox successful. Sweet!」が表示されました。気付いたら簡単な事ですが、なかなか気付けず2~3時間悩んでしまいました。まだまだ先が長い。
【TruffleでDapp開発】Truffleのコマンド「truffle version」でSyntaxError
【はじめに】
イーサリアムを購入してからブロックチェーンに興味を持ち、休みの日はDapp開発の勉強をしています。技術情報を調べていると【Truffle】というイーサリアム開発のフレームワークに出会いました。早速触ってみましたが、インストール時点でエラーが発生しましたので原因と対応方法をまとめておきます。
【Truffleのインストール】
nodeとnpmが使える環境で以下のコマンドを実行します。
npm install -g truffle
【バージョン確認】
以下のコマンドを実行し、Truffleのバージョンが表示すれば正常にインストールされています。
truffle version
正しくインストールされている場合、TruffleとSolidityのバージョンが表示されます。
Truffle v4.1.13 (core: 4.1.13) Solidity v0.4.24 (solc-js)
私の場合、ここでSyntaxErrorが発生しました。
SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
【原因と対応方法】
原因と対応方法については、この記事が参考になります。
trueman-developer.blogspot.com
以下のいずれかの方法で対応可能であることが分かりましたので、nodeのバージョンをあげることにしました。Truffleのインストールなのでnodeのバージョンアップしか選択肢がありません。
- "use strict"(厳格モード)を使用する
- 実行時に起動オプション(--use_strict)をつける
- nodeのバージョンをあげる(6.x 以上)
【nodeのバージョンアップ】
上げる前のバージョン:v5.10.1
上げた後のバージョン:v10.8.0
nodeのバージョンは以下のコマンドで確認できます。
node -v
今回はnodeのバージョン管理ツール「nodist」を使いました。以下の記事が参考になります。
手順2
再起動後、以下のコマンドでnodistのバージョンを確認します。
nodist -v
nodistのバージョンが表示すればインストール完了です。
0.8.8
手順3
nodeのバージョンを指定してインストールを行います。以下のコマンドでインストールできるバージョンが確認できます。
nodist dist
手順4
バージョンを指定してインストールを行います。今回はv10.8.0にしました。
nodist + 10.8.0
手順5
使用するnodeのバージョンに切り替えを行います。nodistはnodeのバージョン管理ツールなので簡単に切り替えができます。
nodist 10.8.0
手順6
正しく切り替わっているか確認します。以下のコマンドでnodeのバージョンを確認します。
node -v
変わっていない。。。
v5.10.1
nodist経由以外でnodeをインストールしている場合、アンインストールする必要がありました。ということで、v5.10.1のnodeをアンインストールします。windowsの設定>アプリと機能からnodeをアンインストールしました。
再度、nodeのバージョンを確認します。
node -v
バージョンがv10.8.0になりました!
v10.8.0
【Truffleのバージョン確認】
Truffleのバージョンを確認します。
truffle version
おぉ、SyntaxErrorではなくTruffleとSolidityのバージョンが表示されました。nodeのバージョンが古すぎたのが原因でしたね。これでTruffleのインストールは完了です。
Truffle v4.1.13 (core: 4.1.13) Solidity v0.4.24 (solc-js)
【Solidity】OpenZeppelinのOwnableコントラクトとは
【はじめに】
この記事はSolidityを勉強する為にCryptoZombiesで学んだ事を自分用のメモとして書いています。
【CryptoZombiesとは】
Solidityでスマートコントラクトの構築を学習できるサイト
日本語で丁寧に説明してくれるので、プログラミングやったことない人でも学習できる。
【OpenZeppelinのOwnableコントラクトとは】
Lesson3でOpenZeppelinのOwnableコントラクトが出てきました。
Solidityの勉強で重要と思いましたので記事にしておきます。
【Ownableコントラクト】
/** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param _newOwner The address to transfer ownership to. */ function transferOwnership(address _newOwner) public onlyOwner { _transferOwnership(_newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param _newOwner The address to transfer ownership to. */ function _transferOwnership(address _newOwner) internal { require(_newOwner != address(0)); emit OwnershipTransferred(owner, _newOwner); owner = _newOwner; } }
modifier onlyOwner
onlyOwnerを関数につけることで使用できる。
コントラクトを呼び出したアドレスと作成者のアドレスが同じでない場合、エラーになる。
つまり、作成者以外が呼び出しても実行できなくなる。
transferOwnership
ownerのアドレスを変更することで実行権限を移行することができる。
【まとめ】
OpenZeppelinはSolidityの開発で頻繁に使われているので、自分で作成するよりもセキュリティ面で安心して使用できます。
Ownable以外にも便利なコントラクトがありそうなので、もっと勉強しなければ。
【CryptoZombiesを触ってみた】Lesson2で学んだこと
【CryptoZombiesとは】
Solidityでスマートコントラクトの構築を学習できるサイト
日本語で丁寧に説明してくれるので、プログラミングやったことない人でも学習できる。
【はじめに】
Solidityは未経験ですが、JavaScriptは触ったことがあります。
そんな人が自分用の学習メモとして書いています。
【学んだこと】
- データ型のmappingとaddress
- mappingはKVS(Key-Value Store)
- addressはEthereumのアドレス
'mappingの書き方 Key => Value mapping (address => uint) public accountBalance; mapping (uint => string) userIdToName;
- msg.sender
その関数を呼び出したユーザー(またはスマートコントラクト)のaddressを取得する。
mapping (address => uint) accountBalance; function setBalance(uint _myBalance) public { accountBalance[msg.sender] = _myBalance; } function whatIsMyBalance() public view returns (uint) { return accountBalance[msg.sender]; }
- Require
条件を満たさない場合、エラーを投げる。
'挨拶(_greeting)が"Hello"の場合、"Hello!"を返す。 '"Hello"以外の場合、エラーになる。 function sayHello(string _greeting) public returns (string) { require(keccak256(_greeting) == keccak256("Hello")); return "Hello!"; }
- 継承
'BabyDogからtype()も実行可能 contract Dog { function type() public returns (string) { return "puppy"; } } contract BabyDog is Dog { function age() public returns (string) { return "1 year old"; } }
- Import
別ファイルに定義されているコントラクトをImportで呼び出し、
継承すれば使用可能になる。
import "./someothercontract.sol"; contract newContract is SomeOtherContract { }
- 変数のStorageとMemory
- Storageはブロックチェーン上に永久に格納される。
- Memoryは関数呼び出し時に消去される。
- InternalとExternal
関数宣言はprivateやpublicの宣言方法と同じ。
Internalはprivateと同じだが、このコントラクトから継承したコントラクトにもアクセスできる。
Externalはpublicと同じだが、コントラクトの外からだけ呼び出すことができる。
コントラクト内部の別の関数から呼び出すことはできない。
function eat() internal { } function drink() external { }
- interface
ブロックチェーン上の他人のコントラクトとやりとりする時に使用する。
'Ethereumのアドレスと電話番号を紐づけるコントラクト contract PhoneNumber { mapping(address => uint) numbers; function setNum(uint _num) public { numbers[msg.sender] = _num; } function getNum(address _myAddress) public view returns (uint) { return numbers[_myAddress]; } }
'別のコントラクトからPhoneNumberのコントラクトを使用したい場合、Interfaceを定義する。 contract PhoneNumberInterface { function getNum(address _myAddress) public view returns (uint); } contract MyContract { address PhoneNumberInterfaceAddress = 0x......; PhoneNumberInterface phoneNumberContract = PhoneNumberInterface(PhoneNumberInterfaceAddress); function someFunction() public { uint num = phoneNumberContract.getNum(msg.sender); } }
- 複数の返り値
返り値は複数指定することができる。
特定の返り値のみ取得する場合は不要な返り値をブランクにする。
function multipleReturns() internal returns(uint a, uint b, uint c) { return (1, 2, 3); } function processMultipleReturns() external { uint a; uint b; uint c; (a, b, c) = multipleReturns(); } function getLastReturnValue() external { uint c; (,,c) = multipleReturns(); }
- If
JavaScriptと同じ
【できたもの】
ゾンビにクリプトキティを食べさせると進化する。
【CryptoZombiesを触ってみた】Lesson1で学んだこと
【CryptoZombiesとは】
Solidityでスマートコントラクトの構築を学習できるサイト
日本語で丁寧に説明してくれるので、プログラミングやったことない人でも学習できる。
【はじめに】
Solidityは未経験ですが、JavaScriptは触ったことがあります。
そんな人が自分用の学習メモとして書いています。
【学んだこと】
- contractの書き方、お作法
- 変数宣言
- 四則演算
Solidityは指数演算子もサポートしている。
uint x = 5 ** 2; // 5^2 = 25 と同様
※cryptozombies より引用
- 構造体と配列の操作は他のプログラミングと同じ
- 関数
- グローバル変数と区別をつけるために、引数の変数名にアンダースコアをつける。
- スコープ(public、private)を指定する位置が後ろ
- privateにする場合は引数同様、関数名にもアンダースコアをつける。
- 修飾子
- view:データの読み取り専用で編集できない。
- pure:アプリ内のデータにアクセスできない。戻り値が関数の引数のみに依存する。
function _generateRandomDna(string _str) private view returns (uint) { }
※cryptozombies より引用
- 型のキャスト
- Event(イベント)
ブロックチェーンで何かが生じたとき、
コントラクトがアプリのフロントエンドに伝えることができる。
event NewZombie(uint zombieId, string name, uint dna); function _createZombie(string _name, uint _dna) private { uint id = zombies.push(Zombie(_name, _dna)) - 1; NewZombie(id, _name, _dna); // イベント発生、フロントエンドに通知 }
※cryptozombies より引用
【できたもの】
ゾンビの名前を入力すると、オリジナルのゾンビを作成してくれる。