---
# System prepended metadata

title: 從以太坊白皮書理解 web 3 概念 - Day17
tags: [' Web 3', it 鐵人賽 30 天, ' ethereum']

---

---
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
```

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

## 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
```