---
tags: it 鐵人賽 30 天, Web 3, ethereum
---
# 從以太坊白皮書理解 web 3 概念 - Day17
## Learn Solidity - Day 9 - Deploy Smart Contracts with Truffle
今天將會介紹如何透過 Truffle 發佈 Smart Contract
讀者可以一起使用 [Lession 10: Deploy Smart Contracts with Truffle](https://cryptozombies.io/en/lesson/10) 跟著本文一起實作
首先要介紹開發與編譯 Smart Contract 工具- Truffle
## Truffle
Truffle 是一套目前蠻受歡迎的開發 Smart Contract 工具
主要特性有以下:
* 編譯 Smart Contrat 方便
* 自動產生 ABI
* 整合 Smart Contract 的測試工具,支援 Mocha 與 Chai 等測試常用 assertion tool
* 支援多個鏈的網路 - 可以透過 Truffle 發佈到 Rinkeby,Ethereum,或是 Loom
安裝需求,需要先安裝 node.js 還有 npm
以下將會使用全域安裝的方式來安裝 Truffle 。
### 執行安裝
1 建立一個資料夾 CryptoZombies ,並且使用 cd 進入這個資料夾
2 透過 npm 或是 yarn 全域安裝 Truffle
```shell=
npm i truffle -g
```
```shell=
yarn global add truffle
```
## 初始化 Truffle 專案
透過 truffle init 就可以初始化一個 Truffle 專案
檔案結構如下
```javascript=
├── contracts
├── Migrations.sol
├── migrations
├── 1_initial_migration.js
└── test
truffle-config.js
truffle.j
```

## Truffle 預設的資料夾結構
* contracts: 這個資料夾就是用來存放所有 Smart Contract 的內容
* migrations: 這個資料夾會存放 javascript 檔案用來作為 deploy Smart Contrat 的程序
* test: 用來存放撰寫測試 Smart Contract 的邏輯
* truffle.js 與 truffle-config.js: 用來設定佈署到哪個鏈網路的設定檔案Truffle 會需要兩個設定檔是因為在 Windows 作業系統中, truffle.js 與 truffle.exe 檔會放在同個資料夾可能會產生檔名衝突。所以如果在Window 會建議使用 truffle-config.js 作為預設設定檔,而刪除 truffle.js
## truffle-hdwallet-provider
在這個課程,將會使用 infura 來發佈 Smart Contract
這樣就不用自己架設 Ethereum 節點或是錢包。
然而為了安全性,Infura 服務並不會幫忙管理簽章私鑰,代表我們必須要額外使用錢包來做這件事情。
為了方便做這件事情,將會使用 truffle-hdwallet-provider 這個套件來處理做簽章的事情。
### 初始化專案
1. 執行 truffle init
2. 安裝 truffle-hdwallet-provider
```shell=
npm install truffle-hdwallet-provider
```
或是
```shell=
yarn add truffle-hdwallet-provider
```
## Compile source code
EVM 無法直接解讀 Solidity source code
所以需要把 Solidity source code 編議程 bytecode EVM 才能讀取並執行
bytecode 的格式大致如下:
```shell=
"0x60806040526010600155600154600a0a6002556201518060035566038d7ea4c6800060085560006009556046600a55336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1..."
```
### Solidity Compiler
Solidity Compiler 負責編譯 solidity source code 為 bytecode 並且會做一些語法的上建議
比如說以下的一個 function
```solidity=
function add(uint16 a, uint16 b) internal returns (uint16) {
uint16 c = a + b;
assert(c >= a);
return c;
}
```
當 Solidity Compiler 編譯之後就會出現以下警告
```solidity=
safemath.sol:110:11: Warning: Function state mutability can be restricted to pure
function add(uint16 a, uint16 b) internal returns (uint16) {
^ (Relevant source part starts here and spans across multiple lines).
```
這個警告是因為這個 function 不需要對區塊鏈做讀取只是單純做計算因為會建議宣告為 pure function
透過宣告為 pure 可以節省呼叫時所需的 gas
### 編譯 Contract 的語法
```shell=
truffle compile
```
## Migration
在正式發佈 Smart Contract 到正式鏈之前
通常都會先在本地端對 Contract 做測試。
可以使用 Ganache 來當作本地節點
為了要發佈到正式鏈,會需要建立 migration
migration 是用來協助 Truffle 發佈 Smart Contract 的 javascript 檔案。
備註: truffle init 會建立一個特別的 Contract 叫作 Migrations.sol。
Migration.sol 是用來紀錄每次 Contract 的修改。
因此,整個修改 Contract 的歷史會被紀錄到鏈上,所以不會發佈兩次相同的 Contract 在上面。
### 建立一個新的 Migration
使用 truffle init 會建立了一個叫作 ./contracts/1_initial_migration.js
內容如下:
```javascript=
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};
```
檔案內容是從 Migration.sol 讀出修改的 Contract
然後透過 deployer 發佈
如果要新增新的發佈
則需要建立一個新的檔案 ./contracts/2_crypto_zombies.js
內容類似於上一個檔案,只是引入不同的 Contract
## 實作
1. 修改 ./contracts/2_crypto_zombies.js 如下
```javascript=
var CryptoZombies = artifacts.require("./CryptoZombies.sol");
module.exports = function(deployer) {
deployer.deploy(CryptoZombies);
};
```
## 設定檔
為了發佈到測試鏈
需要對設定檔做一些調整
### Ethereum Test Networks
乙太鏈具有許多公開的測試鏈可以用來測試 Smart Contract
在這裡將會使用 Rinkeby 來做測試
### truffle.js 設定內容
預設的 Truffle 設定內容如下
```javascript=
/*
* NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a
* function when declaring them. Failure to do so will cause commands to hang. ex:
*
* mainnet: {
* provider: function() {
* return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/<infura-key>')
* },
* network_id: '1',
* gas: 4500000,
* gasPrice: 10000000000,
* },
*/
```
因此需要加入 Rinkeby 的設定
### Truffle HD Wallet Provider
為了建立簽章,使用了 truffle-hdwallet-provider 這個套件
為了要能夠再測試環境使用,必須要加入以下修改到設定檔
```javascript=
var HDWalletProvider = require("truffle-hdwallet-provider");
```
### 設定 Rinkeby
修改 network 參數值如下
```javascript=
networks: {
// Configuration for mainnet
mainnet: {
provider: function () {
// Setting the provider with the Infura Mainnet address and Token
return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/v3/YOUR_TOKEN")
},
network_id: "1"
},
// Configuration for rinkeby network
rinkeby: {
// Special function to setup the provider
provider: function () {
// Setting the provider with the Infura Rinkeby address and Token
return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/YOUR_TOKEN")
},
// Network id is 4 for Rinkeby
network_id: 4
}
```
最後修改值如下
```javascript=
/ Initialize HDWalletProvider
const HDWalletProvider = require("truffle-hdwallet-provider");
// Set your own mnemonic here
const mnemonic = "YOUR_MNEMONIC";
// Module exports to make this configuration available to Truffle itself
module.exports = {
// Object with configuration for each network
networks: {
// Configuration for mainnet
mainnet: {
provider: function () {
// Setting the provider with the Infura Mainnet address and Token
return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/v3/YOUR_TOKEN")
},
network_id: "1"
},
// Configuration for rinkeby network
rinkeby: {
// Special function to setup the provider
provider: function () {
// Setting the provider with the Infura Rinkeby address and Token
return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/YOUR_TOKEN")
},
// Network id is 4 for Rinkeby
network_id: "4"
}
}
};
```
## Deploy Smart Contract
Truffle 透過 migration 來做發佈 Smart Contract
### Migrations
Migration 本身就是一個用來做發佈 Contract 的 Javascript 檔案
### Get some Ether
要發佈 Smart Contract 會需要消耗一些 Ether
要在測試鏈取得 Ether 可以透過一個叫作 faucet 的服務來取得
而 Rinkeby 的 faucet 就在 [Authenticated Faucet](https://faucet.rinkeby.io/)
只要把 account address 填入就可以得到 Rinkeby 鏈上的 Ether
### 實作
1. 透過以下指令發佈 Smart Contract 到 Rinkeby
```shell=
truffle migrate --network rinkeby
```
備註: truffle deploy 只是 truffle migrate 的別名
而如果要發佈到正式鏈只需要把 network 換成 mainnet
## 使用 Loom 鏈來做發佈
### Loom 鏈
在 Loom 鏈上做交易不需要任何 gas,並且比正式鏈交易速度快很多
在 Loom 鏈上比較適合做非金融的 Dapp
而 Deploy 到 Loom 的步驟與 Deploy 到 Rinkeby 一樣
只需要修改 network 設定
還有使用 loom-truffle-provider
### loom-truffle-provider
可以使用 Truffle 來發佈 Smart Contract 到 Loom
需要使用 loom-truffle-provider 來與 Loom 節點溝通
### 實作
1. 安裝 loom-truffle-provider
```shell=
npm i loom-truffle-provider
```
or
```shell=
yarn add loom-truffle-provider
```
## Deploy 到 Loom 測試鏈
要發佈 Smart Contract 在 Loom 測試鏈
首先需要建立 Loom private
可以透過 [Loom 設定](https://loomx.io/developers/en/basic-install-all.html#purpose) 來安裝 Loom cli
然後透過 loom cli 建立 private key 如下
```shell=
$./loom genkey -a public_key -k private_key
local address: 0x42F401139048AB106c9e25DCae0Cf4b1Df985c39
local address base64: QvQBE5BIqxBsniXcrgz0sd+YXDk=
$cat private_key
/i0Qi8e/E+kVEIJLRPV5HJgn0sQBVi88EQw/Mq4ePFD1JGV1Nm14dA446BsPe3ajte3t/tpj7HaHDL84+Ce4Dg==
```
### 更新 truffle.js 設定檔
新增 LoomTruffleProvider 如下
```javascript=
const LoomTruffleProvider = require('loom-truffle-provider');
```
加入 loom 的 network 設定如下
```javascript=
loom_testnet: {
provider: function() {
const privateKey = 'YOUR_PRIVATE_KEY'
const chainId = 'extdev-plasma-us1';
const writeUrl = 'http://extdev-plasma-us1.dappchains.com:80/rpc';
const readUrl = 'http://extdev-plasma-us1.dappchains.com:80/query';
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
},
network_id: '9545242630824'
}
```
更新後如下
```javascript=
// Initialize HDWalletProvider
const HDWalletProvider = require("truffle-hdwallet-provider");
// 1. Initialize LoomTruffleProvider
const LoomTruffleProvider = require('loom-truffle-provider');
// Set your own mnemonic here
const mnemonic = "YOUR_MNEMONIC";
// Module exports to make this configuration available to Truffle itself
module.exports = {
// Object with configuration for each network
networks: {
// Configuration for mainnet
mainnet: {
provider: function () {
// Setting the provider with the Infura Rinkeby address and Token
return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/v3/YOUR_TOKEN")
},
network_id: "1"
},
// Configuration for rinkeby network
rinkeby: {
// Special function to setup the provider
provider: function () {
// Setting the provider with the Infura Rinkeby address and Token
return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/YOUR_TOKEN")
},
// Network id is 4 for Rinkeby
network_id: 4
},
// 2. Put here the configuration for loom_dapp_chain
loom_testnet: {
provider: function() {
const privateKey = 'YOUR_PRIVATE_KEY'
const chainId = 'extdev-plasma-us1';
const writeUrl = 'http://extdev-plasma-us1.dappchains.com:80/rpc';
const readUrl = 'http://extdev-plasma-us1.dappchains.com:80/query';
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
},
network_id: '9545242630824'
}
}
};
```
然後就可以透過以下指令發佈 Contract 到 Loom testnet 了
```shell=
truffle migrate --network loom_testnet
```
## Deploy 到 Loom 正式鏈
流程如下
* 建立 private key
* 把正式鏈網路設定加入 truffle.js
* 透過 truffle 指令發佈
## 建立 private key
```shell=
./loom genkey -a mainnet_public_key -k mainnet_private_key
local address: 0x07419790A773Cc6a2840f1c092240922B61eC778
local address base64: B0GXkKdzzGooQPHAkiQJIrYex3g=
```
在 LoomTruffleProvider 引入
```javascript=
function getLoomProviderWithPrivateKey (privateKeyPath, chainId, writeUrl, readUrl) {
const privateKey = readFileSync(privateKeyPath, 'utf-8');
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
}
```
## 設定正式鏈網路
```javascript=
basechain: {
provider: function() {
const chainId = 'default';
const writeUrl = 'http://basechain.dappchains.com/rpc';
const readUrl = 'http://basechain.dappchains.com/query';
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
const privateKeyPath = path.join(__dirname, 'mainnet_private_key');
const loomTruffleProvider = getLoomProviderWithPrivateKey(privateKeyPath, chainId, writeUrl, readUrl);
return loomTruffleProvider;
},
network_id: '*'
}
```
最後 truffle.js 就會如下
```javascript=
// Initialize HDWalletProvider
const HDWalletProvider = require("truffle-hdwallet-provider");
const { readFileSync } = require('fs')
const path = require('path')
const { join } = require('path')
// Set your own mnemonic here
const mnemonic = "YOUR_MNEMONIC";
function getLoomProviderWithPrivateKey (privateKeyPath, chainId, writeUrl, readUrl) {
const privateKey = readFileSync(privateKeyPath, 'utf-8');
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
}
// Module exports to make this configuration available to Truffle itself
module.exports = {
// Object with configuration for each network
networks: {
// Configuration for mainnet
mainnet: {
provider: function () {
// Setting the provider with the Infura Rinkeby address and Token
return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/v3/YOUR_TOKEN")
},
network_id: "1"
},
// Configuration for rinkeby network
rinkeby: {
// Special function to setup the provider
provider: function () {
// Setting the provider with the Infura Rinkeby address and Token
return new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/YOUR_TOKEN")
},
// Network id is 4 for Rinkeby
network_id: 4
},
basechain: {
provider: function() {
const chainId = 'default';
const writeUrl = 'http://basechain.dappchains.com/rpc';
const readUrl = 'http://basechain.dappchains.com/query';
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey);
const privateKeyPath = path.join(__dirname, 'mainnet_private_key');
const loomTruffleProvider = getLoomProviderWithPrivateKey(privateKeyPath, chainId, writeUrl, readUrl);
return loomTruffleProvider;
},
network_id: '*'
}
}
};
```
然後就可以透過以下指令發佈到 Loom 正式鏈了
```shell=
truffle migrate --network basechain
```