# 區塊鏈 - Smart contract
###### tags: `區塊鏈` `作業`
:::success
目錄:
[TOC]
:::
:::danger
<注意>
- 下方程式碼區塊,皆設定成(!)超過頁面自動換行,去掉(!)可消除該設定。
- 此外,若將(!)改成(=)可見程式碼行數,但兩種功能不可混用。
:::
----
<div style="page-break-after: always;"> </div>
## MetaMask - 以太坊錢包
- step 1. 進入 Chrome商店 下載 MetaMask 擴充元件。

- step 2. 安裝完擴充元件後,將跳出以下視窗,點選「開始使用」。

<div style="page-break-after: always;"> </div>
- step 3. 接著,若是還沒有帳號,則選擇右側「創建錢包」;若是已有帳號,則選擇左側「匯入錢包」,並且跳到 ( step 8 )。

- step 4. 選擇「同意」。

<div style="page-break-after: always;"> </div>
- step 5. 「建立密碼」。

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

<div style="page-break-after: always;"> </div>
- step 7. 選擇「下一頁」,排序助憶詞作驗證。

<div style="page-break-after: always;"> </div>
- step 8. 當你下次要使用不同設備登入時,即需要使用「助憶詞」來匯入錢包。

<div style="page-break-after: always;"> </div>
- step 9. 「註冊」/「匯入」完成。

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

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

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

<div style="page-break-after: always;"> </div>
- step 13. 取得測試幣。剛進入「測試網路」是 0 ETH,現在點選畫面中間的三個按鈕中,左側的「買」。

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

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

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

<div style="page-break-after: always;"> </div>
- step 16. 根據下方的指示,我們能透過在 twitter 或 facebook 分享自己的以太幣帳號 ( 貼文需要設定成 "公開" ),接著將該貼文的網址複製回 rinkeby的網站,然後點選「Give me Ether」並且選擇 領取方式 ( 領18.75 Ether/在三天後才可以再領 )。
::: danger
在此建議使用 twitter 進行操作,facebook 的貼文 可能會收到 "No Ethereum address found to fund" 的錯誤訊息。
:::

<div style="page-break-after: always;"> </div>
- step 17. 回到 MetaMask錢包 的「交易紀錄」確認測試幣是否進帳。( Receive 18.75 )

----
<div style="page-break-after: always;"> </div>
## Remix - 智能合約「編譯」&「部署」的環境
### Remix 環境設定
- step 1. 打開「Remix」,用來編寫智能合約語言「Solidity」的 Web IDE。
::: success
- Google搜尋:「Remix - Ethereum ID」。
- 或者使用以下連結 「 https://remix.ethereum.org/ 」。
:::

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

- step 3. 因此接下來,我們進入左欄的第三個圖示,修改我們部屬智能合約的環境。

- step 4. 將「Enviroment」由「JavaScript VM」的虛擬環境 調整成「Injected Web3」來聯結我們的 MetaMask錢包帳戶。

<div style="page-break-after: always;"> </div>
- step 5. 點選「Injected Web3」後,MetaMask擴充元件 將跳出小視窗,請選擇要向 MetaMask錢包中的哪一個帳戶發出連線請求。

- step 6. 按「下一頁」後,點選「連線」。

<div style="page-break-after: always;"> </div>
- step 7. Connecting。

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

<div style="page-break-after: always;"> </div>
### Remix 智能合約「編譯」&「部屬」
- step 1. 點開 左欄的第一個圖示,可以看到各種儲存在 contracts 資料夾下的智能合約 ( 使用 Solidity 開發,副檔名為 .sol )。選擇 第一個範例檔案「1_Storage.sol」。

- step 2. 接著,進入 左欄的第二個圖示,可以編譯開發好的智能合約。 ( 直接使用預設的編譯參數即可 )

<div style="page-break-after: always;"> </div>
- step 3. 按下藍色的「Compile」後,若是編譯成功,第二個圖示會顯示綠色勾勾。

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

- step 5. 按下「確認」後,我們能看到左側的「Deployed Contracts」增加了我們剛部屬的合約。同時,檢查「MetaMask交易紀錄」能看到「Receive測試幣」之後增加了一筆「部屬合約」的交易。

- step 6. 點開「部屬合約」的交易,即可看到更詳細的活動紀錄。

- step 7. 接著,我們可以在「Deployed Contracts」測試剛剛部屬上去的「Storage合約」。
::: info
- 觸發「Store」後,將一個數值儲存到合約中。
- 然後「retrieve」可以取回存入合約的數值。
:::

<div style="page-break-after: always;"> </div>
- step 8. 測試「Store」時,由於需要將資料寫入智能合約中做儲存的動作,因此需要支付Store的手續費。但在「retrieve」時,並不涉及任何資料的改動,僅回傳並且顯示資料,因此不需要收取手續費。

----
<div style="page-break-after: always;"> </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;"> </div>
- deploy:
- 合約的constructor 需要在部屬時設定參數,因此直接按「Deploy」會因為沒填參數而無沒反應 ( deploy 失敗 )。

<div style="page-break-after: always;"> </div>
- 打開「Deploy」的下拉是選單,即可填入 合約的constructor 參數。

<div style="page-break-after: always;"> </div>
- Deploy 成功後,我們即可在 MetaMask 看到剛剛我們自己打造的「LT幣」。

- 並且可以透過合約的「transfer」成功發送LT幣出去,以及將此交易紀錄寫入 MetaMask錢包 中。

----
<div style="page-break-after: always;"> </div>
## npm - 協助安裝「Ganache」和「Truffle」
為了安裝「Ganache」和「truffle」,我們需要先安裝「Node.js」,再使用 npm 指令來完成上述工具的安裝。
- step 1. 安裝「Node.js」:「 https://nodejs.org/zh-tw/download/ 」

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

<div style="page-break-after: always;"> </div>
## Ganache - 建立一個虛擬的以太坊區塊鏈
Ganache 能幫我們建立一個虛擬的以太坊區塊鏈,並生成一些虛擬帳號,以便我們可以在開發過程中快速地測試智慧合約。
- step 1. 輸入「npm install -g ganache-cli」安裝「Ganache」。

<div style="page-break-after: always;"> </div>
- step 2. 若是安裝成功,輸入「ganache-cli」可以看到建立了10個虛擬帳戶。接著,維持這個cmd開啟,即可提供我們一個測試用的虛擬以太坊區塊鏈。
::: warning
**本cmd 提供虛擬環境後,將不再接受其他指令。若要維持虛擬環境啟動下,執行其他指令,請額外開啟另一個cmd 進行操作 ( 如後續「truffle」即需如此 )。**
:::

從圖中可以看到 Ganache 會預設建立10個帳戶,監聽地址是 127.0.0.1:8545,並且可以實時的看到「Gas Price」、「Gas Limit」等資訊。
<div style="page-break-after: always;"> </div>
## Truffle - Solidity 智能合約的「開發」及「測試」框架
Truffle 是一套針對 Solidity 語言的智能合約開發框架,可以讓開發者快速編譯、部署、測試智能合約,並且可以使用 JavaScript 來進行智能合約測試,能有效的協助 DApp的開發。
### Truffle 安裝 & 初始化
- step 1. 安裝 trufflesuite,輸入「npm install -g truffle」。

...

...

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

<div style="page-break-after: always;"> </div>
- step 3. 在想要建立專案的路徑下輸入「truffle init」指令來初始化 Truffle專案。

- 產生以下檔案:
- contracts:存放智能合約的資料夾。
- contracts / Migrations.sol:智能合約程式範例,合約名稱為Migrations。
- migrations:存放部署合約用的檔案。
- migrations/1_initial_migration.js:部署範例合約用的 JavaScript 檔,檔案名稱開頭必須是 阿拉伯數字 ,代表的意義為 執行的順序。( ※ require 的字串即為合約名稱。 )
- test:存放測試合約用的檔案。
- truffle-config.js:Truffle專案的設定檔。

<div style="page-break-after: always;"> </div>
### Truffle 測試部署
- step 1. 先用「truffle compile」編譯合約。將會在專案下增加一個 build資料夾,儲存編譯完的智能合約 ( .json 檔)。

- 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;"> </div>
- step 3. 設定完畢後,即可使用「truffle migrate (--reset)」部署合約。

<div style="page-break-after: always;"> </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」開始進行測試。

----
<div style="page-break-after: always;"> </div>
## 成品展示

### 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;
});
});
```
### 測試結果

----