---
# System prepended metadata

title: 區塊鏈 - Smart contract
tags: [作業, 區塊鏈]

---

# 區塊鏈 - Smart contract

###### tags: `區塊鏈` `作業`

:::success
目錄：
[TOC]
:::

:::danger
<注意>
- 下方程式碼區塊，皆設定成(!)超過頁面自動換行，去掉(!)可消除該設定。
- 此外，若將(!)改成(=)可見程式碼行數，但兩種功能不可混用。
:::

----

<div style="page-break-after: always;">&nbsp;</div>

## MetaMask - 以太坊錢包

- step 1. 進入 Chrome商店 下載 MetaMask 擴充元件。

    ![](https://i.imgur.com/Xrswwb7.png =x200)

- step 2. 安裝完擴充元件後，將跳出以下視窗，點選「開始使用」。
    
    ![](https://i.imgur.com/LDDQms7.png =x300)

<div style="page-break-after: always;">&nbsp;</div>
  
- step 3. 接著，若是還沒有帳號，則選擇右側「創建錢包」；若是已有帳號，則選擇左側「匯入錢包」，並且跳到 ( step 8 )。
    
    ![](https://i.imgur.com/iYCOmDN.png =x300)

- step 4. 選擇「同意」。

    ![](https://i.imgur.com/lyTnCsB.png =x400)

<div style="page-break-after: always;">&nbsp;</div>

- step 5. 「建立密碼」。

    ![](https://i.imgur.com/rjkSC1e.png =x400)

 - step 6. 接著，請將跳出的「助憶詞」紀錄下來，並妥善保管。**「助憶詞」為整個錢包的備份，只要有「助憶詞」即可獲得這個帳戶的權限，包括修改密碼的能力。**

    ![](https://i.imgur.com/eCgr0wR.png =x400)

<div style="page-break-after: always;">&nbsp;</div>

 - step 7. 選擇「下一頁」，排序助憶詞作驗證。

    ![](https://i.imgur.com/EBbJvvC.png =x500)

<div style="page-break-after: always;">&nbsp;</div>

 - step 8. 當你下次要使用不同設備登入時，即需要使用「助憶詞」來匯入錢包。

   ![](https://i.imgur.com/B8mVCnn.png =x500)

<div style="page-break-after: always;">&nbsp;</div>

 - step 9. 「註冊」/「匯入」完成。
 
    ![](https://i.imgur.com/fQsKFuM.png =x500)

- step 10. 顯示目前帳戶的錢包。

    ![](https://i.imgur.com/C39E1PM.png =x400)

- step 11. 點選「Account」，即可複製此帳戶的「帳號」，用來與他人交易使用。

    ![](https://i.imgur.com/KVpIruB.png =x400)

- step 12. 點選「以太坊 主網路」修改到「測試網路」來進行開發。「以太坊 主網路」即為正式的以太幣區塊鏈，在此進行的交易將花費到真實的以太幣。

     ![](https://i.imgur.com/1AvzQzn.png =x400)

<div style="page-break-after: always;">&nbsp;</div>

- step 13. 取得測試幣。剛進入「測試網路」是 0 ETH，現在點選畫面中間的三個按鈕中，左側的「買」。

    ![](https://i.imgur.com/SjIpEb5.png =x500)

<div style="page-break-after: always;">&nbsp;</div>
    
- step 14. 從「測試水管」點選「取得以太幣」。

    ![](https://i.imgur.com/SjIpEb5.png =x500)

- step 15. 跳出測試網路的畫面。

    ![](https://i.imgur.com/KxIVqNr.png =x400)

- step 16. 選擇左欄的「Crypto Faucet」水龍頭，即可進到能夠幫助我們領取測試幣的畫面。

    ![](https://i.imgur.com/R0PTEgf.png =x300)

<div style="page-break-after: always;">&nbsp;</div>

- step 16. 根據下方的指示，我們能透過在 twitter 或 facebook 分享自己的以太幣帳號 ( 貼文需要設定成 "公開" )，接著將該貼文的網址複製回 rinkeby的網站，然後點選「Give me Ether」並且選擇 領取方式 ( 領18.75 Ether/在三天後才可以再領 )。

    ::: danger
    在此建議使用 twitter 進行操作，facebook 的貼文 可能會收到 "No Ethereum address found to fund" 的錯誤訊息。
    :::
    
    ![](https://i.imgur.com/2M0JiJD.png =x500)

<div style="page-break-after: always;">&nbsp;</div>

- step 17. 回到 MetaMask錢包 的「交易紀錄」確認測試幣是否進帳。( Receive 18.75 )

    ![](https://i.imgur.com/t6UHxp3.png =x400)

----

<div style="page-break-after: always;">&nbsp;</div>

## Remix - 智能合約「編譯」&「部署」的環境

### Remix 環境設定

- step 1. 打開「Remix」，用來編寫智能合約語言「Solidity」的 Web IDE。

    ::: success
    - Google搜尋：「Remix - Ethereum ID」。
    - 或者使用以下連結 「 https://remix.ethereum.org/ 」。
    :::
    
    ![](https://i.imgur.com/esgxaMj.png)

- step 2. 首先，我們打開 MetaMask擴充元件，這邊能看到我們的 Account 旁邊還顯示著 「Not connected」表示 Remix 尚未連結到我們的 MetaMask錢包。

    ![](https://i.imgur.com/GNTzX49.png)

- step 3. 因此接下來，我們進入左欄的第三個圖示，修改我們部屬智能合約的環境。
    
    ![](https://i.imgur.com/NJK7srB.png =x400)

- step 4. 將「Enviroment」由「JavaScript VM」的虛擬環境 調整成「Injected Web3」來聯結我們的 MetaMask錢包帳戶。
    
    ![](https://i.imgur.com/2kLTcUv.png)

<div style="page-break-after: always;">&nbsp;</div>

- step 5. 點選「Injected Web3」後，MetaMask擴充元件 將跳出小視窗，請選擇要向 MetaMask錢包中的哪一個帳戶發出連線請求。

    ![](https://i.imgur.com/2SVMwUh.png)
    
- step 6. 按「下一頁」後，點選「連線」。

    ![](https://i.imgur.com/9LnaTpA.png)

<div style="page-break-after: always;">&nbsp;</div>

- step 7. Connecting。

    ![](https://i.imgur.com/5eEF8IQ.png)

- step 8. 連線成功後，我們可以看到 Remix 的「Account」欄位 自動填入了我們 MetaMask錢包的帳號。同時，打開 MetaMask擴充元件 也可以看到 Account 旁邊顯示了「Connected」的綠燈。

     ![](https://i.imgur.com/8FkUHqZ.png) 

<div style="page-break-after: always;">&nbsp;</div>

### Remix 智能合約「編譯」&「部屬」

- step 1. 點開 左欄的第一個圖示，可以看到各種儲存在 contracts 資料夾下的智能合約 ( 使用 Solidity 開發，副檔名為 .sol )。選擇 第一個範例檔案「1_Storage.sol」。

    ![](https://i.imgur.com/4ASG49m.png)

- step 2. 接著，進入 左欄的第二個圖示，可以編譯開發好的智能合約。 ( 直接使用預設的編譯參數即可 )
    
    ![](https://i.imgur.com/kFww6Ot.png =x400)

<div style="page-break-after: always;">&nbsp;</div>
  
- step 3. 按下藍色的「Compile」後，若是編譯成功，第二個圖示會顯示綠色勾勾。
    
    ![](https://i.imgur.com/gL36eLo.png =x500)

- step 4. 現在進入 左欄的第三個圖示 按下「Deploy」，開始部屬 剛剛編譯好的智能合約 ( 左側 Contract欄位 應該要能看到 剛編譯的智能合約名稱 )。此時，MetaMask 會跳出是否確認進行該合約的部屬，以及顯示為了部屬此合約到測試網路，所需要付的手續費「Gas Fee」。

    ![](https://i.imgur.com/vWERybI.png)
   
- step 5. 按下「確認」後，我們能看到左側的「Deployed Contracts」增加了我們剛部屬的合約。同時，檢查「MetaMask交易紀錄」能看到「Receive測試幣」之後增加了一筆「部屬合約」的交易。

    ![](https://i.imgur.com/JFAzcWd.png)
    
- step 6. 點開「部屬合約」的交易，即可看到更詳細的活動紀錄。

    ![](https://i.imgur.com/F15lZkT.png =x500)

- step 7. 接著，我們可以在「Deployed Contracts」測試剛剛部屬上去的「Storage合約」。

    ::: info
    - 觸發「Store」後，將一個數值儲存到合約中。
    - 然後「retrieve」可以取回存入合約的數值。
    :::
    ![](https://i.imgur.com/yA3qz1Z.png =x500)

<div style="page-break-after: always;">&nbsp;</div>

- step 8. 測試「Store」時，由於需要將資料寫入智能合約中做儲存的動作，因此需要支付Store的手續費。但在「retrieve」時，並不涉及任何資料的改動，僅回傳並且顯示資料，因此不需要收取手續費。
    
    ![](https://i.imgur.com/rAvM3KI.png =x500)

----

<div style="page-break-after: always;">&nbsp;</div>

## 程式撰寫

### 基礎「智能合約」架構

``` solidity!

// SPDX-License-Identifier: GPL-3.0

pragma solidity ^0.8.0;                          // 定義智能合約使用的 Solidity版本。

import "./1_Storage.sol";                        // 可從另外一個合約引用函數與功能。


/**
 * @title Demo
 * @dev store & retrieve value in a variable
 */
contract Demo {                                  // 合約的名稱。

    uint number;                                 // uint = 無號整數。

    /**
     * @dev Store value in variable
     * @param num value to store
     */
    function store(uint num) public {            // 合約的功能：store，a public function。
        number = num;
    }

    /**
     * @dev Return value 
     * @return value of 'number'
     */
    function retrieve() public returns (uint){   // 合約的功能：retrieve，a public function with return value。
        return number;
    }
    
}

``` 

### 打造自己的虛擬貨幣

- code：
	
    ``` solidity!

    // SPDX-License-Identifier: UNLICENSED

    pragma solidity ^0.8.0;

    /* learning contract */

    contract learning {

    /*
        // Global Variables: msg
        msg.sender
        msg.balance
        msg.value
        msg.data 
    */

    /*
        uint _totalSupply = 100;                // token 總發行量
        uint8 _decimal = 10;                    // token 小數點位數

        string _name = "learning token";        // token name
        string _symbol = "LT";                  // token symbol
    */

        bool isOwner = false;

        // address：帳戶位址 的 資料型態。
        address _owner = 0xeA88db747A50479Ac52047A98b43ac86DAc7d18F;    

        struct TransferLog {
            address _sender;
            address _receiver;
            uint _amount;
            uint _time;
        }
        TransferLog[] private _transferLog;     // [array] array for transfer logs

        mapping (address => uint) _balance;
	
        /* 建構合約 */

        string _name = "";
        string _symbol = "";
        uint _totalSupply = 0;
        uint8 _decimal;

        constructor (string memory name_, string memory symbol_, uint totalSupply_, uint8 decimal_) {     // 能在deploy時，決定以下參數。
            _name = name_;
            _symbol = symbol_;
            _totalSupply = totalSupply_;
            _decimal = decimal_;

            _balance[msg.sender] = totalSupply_;
        }

        function name() public view returns (string memory) {
            return _name;
        }

        function symbol() public view returns (string memory) {
            return _symbol;
        }

        function decimal() public view returns (uint8) {
            return _decimal;
        }

        function totalSupply() public view returns (uint) {
            return _totalSupply;
        }

        function balanceOf(address account) public view returns (uint) {
            return _balance[account];
        }

    /*
        // ======== 尚需功能

        function allowance(address owner, address spender) public view returns (uint) {
            owner;
            spender;
            return 0;
        }
        function approve(address spender, uint256 amount) public view returns (bool) {
            spender;
            amount;
            return true;
        }
        function transferFrom(address sender, address recipient, uint256 amount) public view returns (bool) {
            sender;
            recipient;
            amount;
            return true;
        }

        // ======== 尚需功能
    */

        // event：可以與外部溝通的資料型態，常用於 log 產生紀錄。
        event Transfer(address indexed _sender, address indexed _receiver, uint _amount);   // 定義 Transfer 事件

        // 轉帳
        /** 從A轉到B
         * A的餘額要足夠
         * 轉帳記錄要丟進去transferLog裡面
         * 發送Transfer事件
         */
        function transfer(address _receiver, uint _amount) public returns (bool) {
            address _sender = msg.sender;

            require (_balance[_sender] >= _amount, "Insufficient Amount of sender");     // cretical section
            _balance[_sender] = _balance[_sender] - _amount;
            _balance[_receiver] = _balance[_receiver] + _amount;


            emit Transfer(_sender, _receiver, _amount);         // Transfer事件 發送

            TransferLog memory log = TransferLog({
                _sender: _sender,
                _receiver: _receiver,
                _amount: _amount,
                _time: block.timestamp
            });
            _transferLog.push(log);

            return true;
        }

    }

    ```

<div style="page-break-after: always;">&nbsp;</div>

- deploy：
    
    - 合約的constructor 需要在部屬時設定參數，因此直接按「Deploy」會因為沒填參數而無沒反應 ( deploy 失敗 )。

        ![](https://i.imgur.com/8FFXVme.png =x500)

		<div style="page-break-after: always;">&nbsp;</div>

    - 打開「Deploy」的下拉是選單，即可填入 合約的constructor 參數。
    
        ![](https://i.imgur.com/eH4IVi9.png =x500)
		
		<div style="page-break-after: always;">&nbsp;</div>


    - Deploy 成功後，我們即可在 MetaMask 看到剛剛我們自己打造的「LT幣」。
        
        ![](https://i.imgur.com/l0Vki22.png =x500)

    - 並且可以透過合約的「transfer」成功發送LT幣出去，以及將此交易紀錄寫入 MetaMask錢包 中。
    
        ![](https://i.imgur.com/PxCe36y.png)

----

<div style="page-break-after: always;">&nbsp;</div>

## npm - 協助安裝「Ganache」和「Truffle」

為了安裝「Ganache」和「truffle」，我們需要先安裝「Node.js」，再使用 npm 指令來完成上述工具的安裝。

- step 1. 安裝「Node.js」：「 https://nodejs.org/zh-tw/download/ 」

    ![](https://i.imgur.com/1Cy4eSw.png =x400)

- step 2. 安裝完成後，打開cmd 透過以下兩個指令驗證是否正確安裝，能看到版本號即為安裝成功。

    ![](https://i.imgur.com/3i7cOgv.png =x200)
	
<div style="page-break-after: always;">&nbsp;</div>

## Ganache - 建立一個虛擬的以太坊區塊鏈

Ganache 能幫我們建立一個虛擬的以太坊區塊鏈，並生成一些虛擬帳號，以便我們可以在開發過程中快速地測試智慧合約。

- step 1. 輸入「npm install -g ganache-cli」安裝「Ganache」。

    ![](https://i.imgur.com/JPyItnQ.png =x300)
	
<div style="page-break-after: always;">&nbsp;</div>

- step 2. 若是安裝成功，輸入「ganache-cli」可以看到建立了10個虛擬帳戶。接著，維持這個cmd開啟，即可提供我們一個測試用的虛擬以太坊區塊鏈。

    ::: warning

    **本cmd 提供虛擬環境後，將不再接受其他指令。若要維持虛擬環境啟動下，執行其他指令，請額外開啟另一個cmd 進行操作 ( 如後續「truffle」即需如此 )。**

    :::
    
    ![](https://i.imgur.com/E84uzoj.png =x700)
    
    從圖中可以看到 Ganache 會預設建立10個帳戶，監聽地址是 127.0.0.1:8545，並且可以實時的看到「Gas Price」、「Gas Limit」等資訊。
	
<div style="page-break-after: always;">&nbsp;</div>

## Truffle - Solidity 智能合約的「開發」及「測試」框架

Truffle 是一套針對 Solidity 語言的智能合約開發框架，可以讓開發者快速編譯、部署、測試智能合約，並且可以使用 JavaScript 來進行智能合約測試，能有效的協助 DApp的開發。

### Truffle 安裝 & 初始化

- step 1. 安裝 trufflesuite，輸入「npm install -g truffle」。

    ![](https://i.imgur.com/DGy1zM7.png)
    ...
    ![](https://i.imgur.com/6oVBf7O.png)
    ...
    ![](https://i.imgur.com/kHTumSK.png)

- step 2. 安裝完成後，輸入「truffle --version」檢查是否成功安裝。

    ![](https://i.imgur.com/JDM1C2B.png =x500)
	
<div style="page-break-after: always;">&nbsp;</div>
 
- step 3. 在想要建立專案的路徑下輸入「truffle init」指令來初始化 Truffle專案。

    ![](https://i.imgur.com/tqU1pan.png =x300)
    
    - 產生以下檔案：
        - contracts：存放智能合約的資料夾。
            - contracts / Migrations.sol：智能合約程式範例，合約名稱為Migrations。
        - migrations：存放部署合約用的檔案。
            - migrations/1_initial_migration.js：部署範例合約用的 JavaScript 檔，檔案名稱開頭必須是 阿拉伯數字 ，代表的意義為 執行的順序。( ※ require 的字串即為合約名稱。 )
        - test：存放測試合約用的檔案。
        - truffle-config.js：Truffle專案的設定檔。
    
        ![](https://i.imgur.com/7MeHQYL.png =x300)
	
<div style="page-break-after: always;">&nbsp;</div>

### Truffle 測試部署

- step 1. 先用「truffle compile」編譯合約。將會在專案下增加一個 build資料夾，儲存編譯完的智能合約 ( .json 檔)。

    ![](https://i.imgur.com/k30wp0m.png)

- step 2. 完成編譯後進行部署的設定，依據「Ganache : Listening on 127.0.0.1:8545 」來修改「truffle-config.js」的內容。

    ``` js!
    /**
     * Use this file to configure your truffle project. It's seeded with some
     * common settings for different networks and features like migrations,
     * compilation and testing. Uncomment the ones you need or modify
     * them to suit your project as necessary.
     *
     * More information about configuration can be found at:
     *
     * trufflesuite.com/docs/advanced/configuration
     *
     * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
     * to sign your transactions before they're sent to a remote public node. Infura accounts
     * are available for free at: infura.io/register.
     *
     * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
     * public/private key pairs. If you're publishing your code to GitHub make sure you load this
     * phrase from a file you've .gitignored so it doesn't accidentally become public.
     *
     */

    // const HDWalletProvider = require('@truffle/hdwallet-provider');
    // const infuraKey = "fj4jll3k.....";
    //
    // const fs = require('fs');
    // const mnemonic = fs.readFileSync(".secret").toString().trim();

    module.exports = {
        /**
         * Networks define how you connect to your ethereum client and let you set the
         * defaults web3 uses to send transactions. If you don't specify one truffle
         * will spin up a development blockchain for you on port 9545 when you
         * run `develop` or `test`. You can ask a truffle command to use a specific
         * network from the command line, e.g
         *
         * $ truffle test --network <network-name>
         */

        networks: {
          // Useful for testing. The `development` name is special - truffle uses it by default
          // if it's defined here and no other network is specified at the command line.
          // You should run a client (like ganache-cli, geth or parity) in a separate terminal
          // tab if you use this network and you must also set the `host`, `port` and `network_id`
          // options below to some value.
          //
          development: {
            host: "127.0.0.1",     // Localhost (default: none)
            port: 8545,            // Standard Ethereum port (default: none)
            network_id: "*",       // Any network (default: none)
          },
          // Another network with more advanced options...
          // advanced: {
          // port: 8777,             // Custom port
          // network_id: 1342,       // Custom network
          // gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
          // gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
          // from: <address>,        // Account to send txs from (default: accounts[0])
          // websocket: true        // Enable EventEmitter interface for web3 (default: false)
          // },
          // Useful for deploying to a public network.
          // NB: It's important to wrap the provider as a function.
          // ropsten: {
          // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
          // network_id: 3,       // Ropsten's id
          // gas: 5500000,        // Ropsten has a lower block limit than mainnet
          // confirmations: 2,    // # of confs to wait between deployments. (default: 0)
          // timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
          // skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
          // },
          // Useful for private networks
          // private: {
          // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
          // network_id: 2111,   // This network is yours, in the cloud.
          // production: true    // Treats this network as if it was a public net. (default: false)
          // }
        },

        // Set default mocha options here, use special reporters etc.
        mocha: {
          // timeout: 100000
        },

        // Configure your compilers
        compilers: {
          solc: {
            version: "0.8.0",    // Fetch exact version from solc-bin (default: truffle's version)
            // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
            // settings: {          // See the solidity docs for advice about optimization and evmVersion
            //  optimizer: {
            //    enabled: false,
            //    runs: 200
            //  },
            //  evmVersion: "byzantium"
            // }
          }
        },

        // Truffle DB is currently disabled by default; to enable it, change enabled: false to enabled: true
        //
        // Note: if you migrated your contracts prior to enabling this field in your Truffle project and want
        // those previously migrated contracts available in the .db directory, you will need to run the following:
        // $ truffle migrate --reset --compile-all

        db: {
          enabled: false
        }
      };
      
    ``` 
		
<div style="page-break-after: always;">&nbsp;</div>

- step 3. 設定完畢後，即可使用「truffle migrate (--reset)」部署合約。
    
    ![](https://i.imgur.com/kVXUvld.png =x700)
	
<div style="page-break-after: always;">&nbsp;</div>

### Truffle 測試

- step 1. 在專案的test資料夾下，增加一個「truffle.js」的測試程式。

    ``` js!
    
    const Migrations = artifacts.require("Migrations");

    /*
     * uncomment accounts to access the test accounts made available by the
     * Ethereum client
     * See docs: https://www.trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript
     */
    contract("Migrations", function (accounts) {

      let MigrationsContract;

      before("Deploy Migrations contract", async () => {
        console.log("deploying Migrations contract...")
        MigrationsContract = await Migrations.deployed()

        assert.equal(await MigrationsContract.owner(), accounts[0], "wrong contract owner");
      })

      // truffle console：
        // await：JS 等待執行，use with contract.depolyed()。
          // const MigrationsContract = await Migrations.deployed();
          // MigrationsContract.name();
        // without await：
          // migrate
          // Migrations.deployed().then(function(instance){return instance.name();});

      // 確認 owner 是否正確
      it("確認 owner 有沒有問題", async () => {

        // 1. 先取得合約的實例(instance)
        const MigrationsContract = await Migrations.deployed()

        // 2. 確認 owner 是否正確
        const owner = await MigrationsContract.owner();

        assert.equal(owner, accounts[0], "owner 錯誤")

        // 3. 完成
        return true;
      });

    });
    
    ```

- step 2. 輸入「truffle test」開始進行測試。

    ![](https://i.imgur.com/1OQJ1kl.png =x400)

----
	
<div style="page-break-after: always;">&nbsp;</div>

## 成品展示

![](https://i.imgur.com/VwVlcaq.png)

### Code

- contracts

    1. IERC20.sol
    
        ``` solidity!
        
        // SPDX-License-Identifier: UNLICENSED 

        pragma solidity ^0.8.0;

        /**
         * @dev Interface of the ERC20 standard as defined in the EIP.
         */
        interface IERC20 {
            /**
             * @dev Returns the amount of tokens in existence.
             */
            function totalSupply() external view returns (uint256);

            /**
             * @dev Returns the amount of tokens owned by `account`.
             */
            function balanceOf(address account) external view returns (uint256);

            /**
             * @dev Moves `amount` tokens from the caller's account to `recipient`.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transfer(address recipient, uint256 amount) external returns (bool);

            /**
             * @dev Returns the remaining number of tokens that `spender` will be
             * allowed to spend on behalf of `owner` through {transferFrom}. This is
             * zero by default.
             *
             * This value changes when {approve} or {transferFrom} are called.
             */
            function allowance(address owner, address spender) external view returns (uint256);

            /**
             * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * IMPORTANT: Beware that changing an allowance with this method brings the risk
             * that someone may use both the old and the new allowance by unfortunate
             * transaction ordering. One possible solution to mitigate this race
             * condition is to first reduce the spender's allowance to 0 and set the
             * desired value afterwards:
             * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
             *
             * Emits an {Approval} event.
             */
            function approve(address spender, uint256 amount) external returns (bool);

            /**
             * @dev Moves `amount` tokens from `sender` to `recipient` using the
             * allowance mechanism. `amount` is then deducted from the caller's
             * allowance.
             *
             * Returns a boolean value indicating whether the operation succeeded.
             *
             * Emits a {Transfer} event.
             */
            function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

            /**
             * @dev Emitted when `value` tokens are moved from one account (`from`) to
             * another (`to`).
             *
             * Note that `value` may be zero.
             */
            event Transfer(address indexed from, address indexed to, uint256 value);

            /**
             * @dev Emitted when the allowance of a `spender` for an `owner` is set by
             * a call to {approve}. `value` is the new allowance.
             */
            event Approval(address indexed owner, address indexed spender, uint256 value);
        }
        
        ``` 
        
    2. YZT.sol
    
        ``` solidity!
        
        // SPDX-License-Identifier: UNLICENSED 

        /* Pragma definition, including version pragma and experimental pragma */
        pragma solidity ^0.8.0;
        // pragma experimental SMTChecker;

        /* Imports */
        import "./IERC20.sol";

        /* YZT main contract, inherit from IERC20 */
        contract YZT is IERC20 {

            /* [uint] YZT total supply */ 
            uint256 private _totalSupply;

            /* [uint] token decimal */
            uint8 private _decimal;

            /* [string] YZT token name */
            string private _name;

            /* [string] YZT token symbol */
            string private _symbol;

            /* [bool] breaker for logging transfer */
            bool recorded = false;

            /* [address] owner of this contract */
            address public owner;

            /* [struct] definition of transfer log */
            struct TransferLog {
                address _sender;
                address _receiver;
                uint _amount;
                uint _time;
            }

            /* [array] array for transfer logs */
            TransferLog[] private _transferLog;

            /* [mapping] balances definition */
            mapping (address => uint256) private _balances;

            /* [mapping] allowance definition */
            mapping (address => mapping (address => uint256)) private _allowances;

            constructor (string memory name_, string memory symbol_, uint8 decimal_, uint totalSupply_) {
                owner = msg.sender;

                _name = name_;
                _symbol = symbol_;
                _decimal = decimal_;
                _totalSupply = totalSupply_;

                _mint(owner, _totalSupply);
            }

            function decimal() public view virtual returns (uint8) {
                return _decimal;
            }

            function totalSupply() public view virtual override returns (uint) {
                return _totalSupply;
            }

            function name() public view virtual returns (string memory) {
                return _name;
            }

            function symbol() public view virtual returns (string memory) {
                return _symbol;
            }

            function balanceOf(address account) public view virtual override returns (uint256) {
                return _balances[account];
            }

            /**
             * @dev Moves tokens `amount` from `sender` to `recipient`.
             *
             * Requirements:
             * - `sender` cannot be the zero address.
             * - `recipient` cannot be the zero address.
             * - `sender` must have a balance of at least `amount`.
             * -  Emits a {Transfer} event.
             */
            function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
              address sender = msg.sender;
              require(sender != address(0), "ERC20: transfer from the zero address");
              require(recipient != address(0), "ERC20: transfer to the zero address");

              uint256 senderBalance = _balances[sender];
              require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
              _balances[sender] = senderBalance - amount;
              _balances[recipient] += amount;

              emit Transfer(sender, recipient, amount);

              return true;
            }

            function allowance(address origin, address spender) public view virtual override returns (uint256) {
              return _allowances[origin][spender];
            }

            /**
             * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
             *
             * Requirements:
             * - `owner` cannot be the zero address.
             * - `spender` cannot be the zero address.
             * - Emits an {Approval} event.
             */
            function approve(address spender, uint256 amount) public virtual override returns (bool) {
              address origin = msg.sender;
              require(origin != address(0), "ERC20: approve from the zero address");
              require(spender != address(0), "ERC20: approve to the zero address");

              _allowances[origin][spender] = amount;
              emit Approval(origin, spender, amount);

              return true;
            }

            /**
             * @dev Transfer token from sender to receiver by a spender
             *
             * Requirements:
             *
             * - `sender` and `recipient` cannot be the zero address.
             * - `sender` must have a balance of at least `amount`.
             * - the caller must have allowance for ``sender``'s tokens of at least
             * `amount`.
             * -  Emits a {Transfer} event.
             */
            function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
              require(sender != address(0), "ERC20: transfer from the zero address");
              require(recipient != address(0), "ERC20: transfer to the zero address");

              address spender = msg.sender;
              uint256 currentAllowance = _allowances[sender][spender];
              require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
              approve(spender, currentAllowance - amount);

              uint256 senderBalance = _balances[sender];
              require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
              _balances[sender] = senderBalance - amount;
              _balances[recipient] += amount;

              emit Transfer(sender, recipient, amount);

              return true;
            }

             /**
             * @dev Atomically increases the allowance granted to `spender` by the caller.
             *
             * Requirements:
             *
             * - `spender` cannot be the zero address.
             */
            function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
              address origin = msg.sender;
              uint256 currentAllowance = _allowances[origin][spender];

              approve(spender, currentAllowance + addedValue);

              return true;
            }

            /**
             * @dev Atomically decreases the allowance granted to `spender` by the caller.
             *
             * This is an alternative to {approve} that can be used as a mitigation for
             * problems described in {IERC20-approve}.
             *
             * Emits an {Approval} event indicating the updated allowance.
             *
             * Requirements:
             *
             * - `spender` cannot be the zero address.
             * - `spender` must have allowance for the caller of at least
             * `subtractedValue`.
             */
            function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
              address origin = msg.sender;
              uint256 currentAllowance = _allowances[origin][spender];
              require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
              approve(spender, currentAllowance - subtractedValue);

              return true;
            }

            /** @dev Creates `amount` tokens and assigns them to `account`, increasing
             * the total supply.
             *
             * Emits a {Transfer} event with `from` set to the zero address.
             *
             * Requirements:
             *
             * - `to` cannot be the zero address.
             */
            function _mint(address account, uint256 amount) internal virtual {
                require(account != address(0), "ERC20: mint to the zero address");

                _totalSupply += amount;
                _balances[account] += amount;
                emit Transfer(address(0), account, amount);
            }

            /**
             * @dev Destroys `amount` tokens from `account`, reducing the
             * total supply.
             *
             * Emits a {Transfer} event with `to` set to the zero address.
             *
             * Requirements:
             *
             * - `account` cannot be the zero address.
             * - `account` must have at least `amount` tokens.
             */
            function _burn(address account, uint256 amount) internal virtual {
                require(account != address(0), "ERC20: burn from the zero address");

                uint256 accountBalance = _balances[account];
                require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
                _balances[account] = accountBalance - amount;
                _totalSupply -= amount;

                emit Transfer(account, address(0), amount);
            }
        }

        ``` 

- migrations

    - 1618932708_yzt.js
    
        ``` js!
        
        const YZT = artifacts.require("YZT")

        module.exports = function(deployer) {
          // Use deployer to state migration tasks.

          deployer.deploy(YZT, 
            "YuenZe Token",
            "YZT",
            2, 
            10000,
            )
        };

        ``` 

- truffle-config.js

    ``` js!

    /**
     * Use this file to configure your truffle project. It's seeded with some
     * common settings for different networks and features like migrations,
     * compilation and testing. Uncomment the ones you need or modify
     * them to suit your project as necessary.
     *
     * More information about configuration can be found at:
     *
     * trufflesuite.com/docs/advanced/configuration
     *
     * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
     * to sign your transactions before they're sent to a remote public node. Infura accounts
     * are available for free at: infura.io/register.
     *
     * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
     * public/private key pairs. If you're publishing your code to GitHub make sure you load this
     * phrase from a file you've .gitignored so it doesn't accidentally become public.
     *
     */

    // const HDWalletProvider = require('@truffle/hdwallet-provider');
    // const infuraKey = "fj4jll3k.....";
    //
    // const fs = require('fs');
    // const mnemonic = fs.readFileSync(".secret").toString().trim();

    module.exports = { 
      /**
       * Networks define how you connect to your ethereum client and let you set the
       * defaults web3 uses to send transactions. If you don't specify one truffle
       * will spin up a development blockchain for you on port 9545 when you
       * run `develop` or `test`. You can ask a truffle command to use a specific
       * network from the command line, e.g
       *
       * $ truffle test --network <network-name>
       */

      networks: {
        // Useful for testing. The `development` name is special - truffle uses it by default
        // if it's defined here and no other network is specified at the command line.
        // You should run a client (like ganache-cli, geth or parity) in a separate terminal
        // tab if you use this network and you must also set the `host`, `port` and `network_id`
        // options below to some value.
        //
        development: {
          host: "127.0.0.1",     // Localhost (default: none)
          port: 8545,            // Standard Ethereum port (default: none)
          network_id: "*",       // Any network (default: none)
        },
        // Another network with more advanced options...
        // advanced: {
        // port: 8777,             // Custom port
        // network_id: 1342,       // Custom network
        // gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
        // gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
        // from: <address>,        // Account to send txs from (default: accounts[0])
        // websocket: true        // Enable EventEmitter interface for web3 (default: false)
        // },
        // Useful for deploying to a public network.
        // NB: It's important to wrap the provider as a function.
        // ropsten: {
        // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
        // network_id: 3,       // Ropsten's id
        // gas: 5500000,        // Ropsten has a lower block limit than mainnet
        // confirmations: 2,    // # of confs to wait between deployments. (default: 0)
        // timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
        // skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
        // },
        // Useful for private networks
        // private: {
        // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
        // network_id: 2111,   // This network is yours, in the cloud.
        // production: true    // Treats this network as if it was a public net. (default: false)
        // }
      },

      // Set default mocha options here, use special reporters etc.
      mocha: {
        // timeout: 100000
      },

      // Configure your compilers
      compilers: {
        solc: {
          version: "0.8.0",    // Fetch exact version from solc-bin (default: truffle's version)
          // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
          // settings: {          // See the solidity docs for advice about optimization and evmVersion
          //  optimizer: {
          //    enabled: false,
          //    runs: 200
          //  },
          //  evmVersion: "byzantium"
          // }
        }
      },

      // Truffle DB is currently disabled by default; to enable it, change enabled: false to enabled: true
      //
      // Note: if you migrated your contracts prior to enabling this field in your Truffle project and want
      // those previously migrated contracts available in the .db directory, you will need to run the following:
      // $ truffle migrate --reset --compile-all

      db: {
        enabled: false
      }
    };

    ``` 

- test

    - yzt.js
    
        ``` js!

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

        /*
         * uncomment accounts to access the test accounts made available by the
         * Ethereum client
         * See docs: https://www.trufflesuite.com/docs/truffle/testing/writing-tests-in-javascript
         */
        contract("YZT", function (accounts) {

          let yztContract;

          before("Deploy YZT contract", async () => {
            console.log("deploying YZT contract...")
            yztContract = await YZT.deployed()

            assert.equal(await yztContract.owner(), accounts[0], "wrong contract owner");
            assert.equal(await yztContract.name(), "YuenZe Token", "wrong contract name");
            assert.equal(await yztContract.symbol(), "YZT", "wrong contract symbol");
          })

          it("should have correct contract name", async function () {
            return await yztContract.name(), "YuenZe Token", "wrong contract name";
          });


          // truffle console：
            // await：JS 等待執行，use with contract.depolyed()。
              // const yztContract = await YZT.deployed();
              // yztContract.name();
            // without await：
              // migrate
              // YZT.deployed().then(function(instance){return instance.name();});

          // 確認 transfer 是否正確
          it("確認 transfer函式 有沒有問題", async () => {
            // 測試邏輯

            // 測試 a to b
            // 確認 a 有無扣款
            // 確認 b 有無被轉入

            // a -> accounts[0]
            // b -> accounts[1]

            // 1. 先取得合約的實例(instance)
            const yztContract = await YZT.deployed()

            // 2. 執行 transfer函式，指定從 a 轉1塊錢到 b
            await yztContract.transfer(
              accounts[1],          // 指定 b 的 收款地址
              100,                  // 因為小數點為2，因此 100 == 一塊錢
              {
                from : accounts[0]  // 從 a 送出這筆交易
              }
            )

            // 3. 確認 a 和 b 帳戶餘額是否正確
            const a_Balance = await yztContract.balanceOf(accounts[0]);
            const b_Balance = await yztContract.balanceOf(accounts[1]);

            assert.equal(a_Balance, 9900, "a 的 餘額錯誤")
            assert.equal(b_Balance, 100, "b 的 餘額錯誤")

            // 4. 完成
            return true;
          });


        });

        ``` 

### 測試結果

![](https://i.imgur.com/RaPIYhk.png)

----