Truffle のチュートリアル ETHEREUM PET SHOP をやってみた
はじめに
「Solidity とは何か? 開発環境を構築する!」で、Solidity の基礎を理解し、スマートコントラクトプロジェクトを作成する事が出来ました。 Dapps開発のフレームワークである「Truffle」の公式サイトで紹介されている「ETHEREUM PET SHOP」のチュートリアルをやってみて、実際にウェブ上に分散型アプリケーションを作成しましたので、備忘録としてまとめます。
https://trufflesuite.com/tutorial/
「Truffle」以外にも Hardhat を使用して Dapps開発をする方法があります。
参考にした本
- マスタリング・イーサリアム ―スマートコントラクトとDAppの構築
- SolidityとEthereumによる実践スマートコントラクト開発 ―Truffle Suiteを用いた開発の基礎からデプロイまで
- 詳解 ビットコイン ―ゼロから設計する過程で学ぶデジタル通貨システム
- ブロックチェーン dapp&ゲーム開発入門 Solidityによるイーサリアム分散アプリプログラミング
ETHEREUM PET SHOP
ペットの「adopt」というボタンを押すとMetaMaskが起動し、表示された金額と手数料を確認してトランザクションが作成されETHで支払うことができます。
Truffle Box を使った Truffleプロジェクトの作成
pet-shop-tutorialというディレクトリを作り、このディレクトリ内で作業していきます。
$ mkdir pet-shop-tutorial $ cd pet-shop-tutorial
今回はチュートリアルのためTruffle Boxというあらかじめ用意されたプロジェクトから作成します。
$ truffle unbox pet-shop
スマートコントラクトを書く
contracts/
ディレクトリの中にAdoption.sol
というファイルを作成します。
pragma solidity ^0.5.0; 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] memory) { return adopters; } }
truffle compile
を実行します。
マイグレーション
migrations/
ディレクトリの中に2_deploy_contracts.js
というマイグレーションファイルを作成します。
var Adoption = artifacts.require("Adoption"); module.exports = function(deploy) { deploy.deploy(Adoption); }
プライベートネットを作るツールとして、Ganache を利用します。
「Ganache」を起動した状態にして、ターミナルで truffle migrate
を実行します。
スマートコントラクトのテストコードの作成
「test」ディレクトリ内に「TestAdoption.sol」というテストコードを作成しましょう。
pragma solidity ^0.5.0; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../contracts/Adoption.sol"; contract TestAdoption { // The address of the adoption contract to be tested Adoption adoption = Adoption(DeployedAddresses.Adoption()); // The id of the pet that will be used for testing uint expectedPetId = 8; // The expected owner of adopted pet is this contract address expectedAdopter = address(this); // Testing the adopt() function function testUserCanAdoptPet() public { uint returnedId = adoption.adopt(expectedPetId); Assert.equal(returnedId, expectedPetId, "Adoption of the expected pet should match what is returned."); } // Testing retrieval of a single pet's owner function testGetAdopterAddressByPetId() public { address adopter = adoption.adopters(expectedPetId); Assert.equal(adopter, expectedAdopter, "Owner of the expected pet should be this contract"); } // Testing retrieval of all pet owners function testGetAdopterAddressByPetIdInArray() public { // Store adopters in memory rather than contract's storage address[16] memory adopters = adoption.getAdopters(); Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract"); } }
truffle test
を実行します。
スマートコントラクトと連動したUIの作成
フロントエンド部分のロジックは src ディレクトリで、スマートコントラクトの連携する部分を実装する為に、 /src/js/app.js
ファイルを編集します。
App = { web3Provider: null, contracts: {}, init: async 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 await App.initWeb3(); }, initWeb3: async function() { // Modern dapp browsers... if (window.ethereum) { App.web3Provider = window.ethereum; try { // Request account access await window.ethereum.request({ method: "eth_requestAccounts" });; } catch (error) { // User denied account access... console.error("User denied account access") } } // Legacy dapp browsers... else if (window.web3) { App.web3Provider = window.web3.currentProvider; } // If no injected web3 instance is detected, fall back to Ganache else { 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() { 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とブロックチェーンを接続する
- 右上の丸いボタンをクリックし、マイアカウントの「設定」を選択する。
- 「ネットワーク」を選択する。
- 「ネットワーク追加」をクリックする。
- ネットワーク名とRPC URLに
http://127.0.0.1:7545
を入れる。 - チェーンIDに
1337
を入れる。 - 通貨記号に
ETH
を入れ「保存」を押す。 - 右上のXをクリックし設定を終了する。
- Ganacheのアカウント1の残高が反映されたことを確認する。
Dappの完成
npm run dev
を実行します。
Dappがブラウザ上で表示できます。「adopt」ボタンをクリックすると、MetaMaskによりトランザクションが送られ、ETH払いでペットを購入することができます。
まとめ
この記事では、truffle unbox pet-shop
で作成される、Truffleのチュートリアルで ETHEREUM PET SHOP の流れにそって、実際にウェブ上に分散型アプリケーションを作成する事が出来るようになりました。
今後は以下の内容をまとめる予定です。
公開されているテストネットワーク Görli testnet にデプロイする。