iT邦幫忙

2022 iThome 鐵人賽

DAY 30
1
Web 3

從以太坊白皮書理解 web 3 概念系列 第 31

從以太坊白皮書理解 web 3 概念 - Day30

  • 分享至 

  • xImage
  •  

從以太坊白皮書理解 web 3 概念 - Day30

使用 ganache 與 Truffle 來實作 pet-shop dapp

前面透過 Geth 建立 POA 節點

步驟看起來似呼是有點複雜

筆者覺得 ganache 是一個比較簡單好使用的 local 測試節點

今天將透過 Truffle 建構 pet-shop Dapp 並且發佈在 ganache 上

並且使用 MetaMask 錢包作為簽章工具

安裝 ganache

yarn global add ganache-cli

透過 ganache-cli 可以開啟一個 rpc 為 http://localhost:8545 且 chainId 為 1337 的節點

並且預設建立 10個 account 並且每個 account 會給予 100 ETH

如下

ganache-cli

設定 MetaMask 連結

設定網路

把其剛剛的帳號的密鑰匯入 MetaMask



建立 dapp 資料夾

mkdir pet-shop-dapp
cd pet-shop-dapp
truffle unbox pet-shop

新增網路設定

更新 truffle-config.js 如下

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // for more about customizing your Truffle configuration!
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*" // Match any network id
    },
    develop: {
      port: 8545
    }
  },
  compilers: {
    solc: {
      version: "0.8.0"
    }
  },
  mocha: {
    // timeout: 100000
  },
};

撰寫 Adoption.sol 內容

建立 contracts/Adoption.sol 如下

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.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;
  }
}

adopters 是一個用來紀錄領養人的結構

主要有兩個 functions

adopt 負責把對應的領養人紀錄到 array 對應的位置

getAdopters 取得整個領養人資料

測試撰寫

Truffle 測試 contract 可以利用 solidity 語法或是 javascript 語法

solitiy 語法

因為主要只有兩個 function

adopt 與 getAdopters

所以測試會從這兩大項去做

會先在 test 建立一個測試 sol 檔案 TestAdoption.sol

需要先宣告一個 Contract body 如下

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract TestAdoption {
  Adoption adoption = Adoption(DeployedAddresses.Adoption());
  uint expectedPetId = 8;
  address expectedAdopter = address(this);
}

Assert.sol 是 truffle 所提供的 Assertion 功能
DeployedAddresses.sol 是 truffle 用來取這個測試 Contract 發佈出去的 address
這邊這個 this 會取得當下的 contract address

因為每次領養人會被設定為 msg.sender 所以如果要做測試需要取的當下呼叫者的 address ,所以這邊用 this

  • 測試 adopt 功能

adopt 功能會傳入要領養的寵物 id 並且再設定完領養人後回傳 petId

所以這邊要做的驗證是證明傳入的 petId 與執行完收到的一樣的

邏輯如下

// 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.");
}
  • 測試 getAdopters 功能

這邊需要驗證是否能夠取得正確的 Adopters 資料

所以透過前面的 expectedPetId 去測試取的回來的 getAdopters 是否對應到正確的 address

邏輯如下

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");
  }
  • 測試 adopters 取值
    另外因為 adopters 是 public 因此也可以測試一下取值是否正確

邏輯如下

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

js 語法

truffle 提供可以使用 mocha 與 chai 的 assert 語法

首先建立 testAdoption.test.js 如下

const Adoption = artifacts.require("Adoption");

contract("Adoption", (accounts) => {
  let adoption;
  let expectedPetId = 8;
  before(async () => {
    adoption = await Adoption.deployed();
  })
})

這邊的 artifacts.require 可以用來讀取建立好的 Adoption Contract

這裡的 Adoption.deployed() 可以用來取得 deploy 之後的 Contract 實體

然後就可以透過 adoption 來呼叫 Adoption Contract

如同前面所述測試內容撰寫如下

const Adoption = artifacts.require("Adoption");

contract("Adoption", (accounts) => {
  let adoption;
  let expectedPetId = 8;
  before(async () => {
    adoption = await Adoption.deployed();
  })

  describe("adopting a pet and retrieving account addresses", async () => {
    before("adopt a pet using accounts[0]", async () => {
      await adoption.adopt(expectedPetId, { from: accounts[0] });
      expectedAdopter = accounts[0];
    });
  
    it("can fetch the address of an owner by pet id", async () => {
      const adopter = await adoption.adopters(expectedPetId);
      assert.equal(adopter, expectedAdopter, "The owner of the adopted pet should be the first account.");
    });
    it("can fetch the collection of all pet owners' addresses", async () => {
      const adopters = await adoption.getAdopters();
      assert.equal(adopters[expectedPetId], expectedAdopter, "The owner of the adopted pet should be in the collection.");
    });
  });
})

執行測試

truffle test

UI 互動邏輯撰寫

主要分為四大部份

  1. initWeb3: 用來初始化 web3
function initWeb3() {
	if (window.ethereum) {
      App.web3Provider = window.ethereum;
      try {
        // Request account access
        await window.ethereum.enable();
      } 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();
}
  1. initContract: 用來初始化 Contract
function initContract() {
	 $.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();
}
  1. markAdopted: 用來標注領養邏輯
function markAdopted() {
	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);
    });
}
  1. handleAdopt: 用來處理領養邏輯
function handleAdopt(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);
      });
    });
}

執行 Dapp

yarn run dev



以上就是完整的 pet-shop dapp

參考文獻

https://trufflesuite.com/guides/pet-shop/


上一篇
從以太坊白皮書理解 web 3 概念 - Day29
下一篇
從以太坊白皮書理解 web 3 概念 - 結語
系列文
從以太坊白皮書理解 web 3 概念32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言