# 區塊鏈 - 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) ----