3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Truffle のチュートリアル ETHEREUM PET SHOP をやってみた

Last updated at Posted at 2022-07-19

はじめに

Solidity とは何か? 開発環境を構築する!」で、Solidity の基礎を理解し、スマートコントラクトプロジェクトを作成する事が出来ました。
Dapps開発のフレームワークである「Truffle」の公式サイトで紹介されている「ETHEREUM PET SHOP」のチュートリアルをやってみて、実際にウェブ上に分散型アプリケーションを作成しましたので、備忘録としてまとめます。

petshop.png

「Truffle」以外にも Hardhat を使用して Dapps開発をする方法があります。

参考にした本

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というファイルを作成します。

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というマイグレーションファイルを作成します。

2_deploy_contracts.js
var Adoption = artifacts.require("Adoption");

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

プライベートネットを作るツールとして、Ganache を利用します。
「Ganache」を起動した状態にして、ターミナルで truffle migrate を実行します。

スマートコントラクトのテストコードの作成

「test」ディレクトリ内に「TestAdoption.sol」というテストコードを作成しましょう。

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.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とブロックチェーンを接続する

  1. 右上の丸いボタンをクリックし、マイアカウントの「設定」を選択する。
  2. 「ネットワーク」を選択する。
  3. 「ネットワーク追加」をクリックする。
  4. ネットワーク名とRPC URLに http://127.0.0.1:7545 を入れる。
  5. チェーンIDに 1337 を入れる。
  6. 通貨記号に ETH を入れ「保存」を押す。
  7. 右上のXをクリックし設定を終了する。
  8. Ganacheのアカウント1の残高が反映されたことを確認する。

Dappの完成

npm run dev を実行します。
Dappがブラウザ上で表示できます。「adopt」ボタンをクリックすると、MetaMaskによりトランザクションが送られ、ETH払いでペットを購入することができます。

まとめ

この記事では、truffle unbox pet-shop で作成される、Truffleのチュートリアルで ETHEREUM PET SHOP の流れにそって、実際にウェブ上に分散型アプリケーションを作成する事が出来るようになりました。

今後は以下の内容をまとめる予定です。

公開されているテストネットワーク Görli testnet にデプロイする。

3
3
0

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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?