設定步驟
設定開發者相關環境變數
建立一個 Truffle 專案
寫一個基本的合約
編譯與部屬你的合約
測試你的合約
建立一個介面來跟你的合約溝通
放到瀏覽器開工囉
後端 1. 設定開發者相關環境變數 安裝基本環境 Nodejs
Git
Ganache
個人的以太區塊鏈的測試環境
1 2 3 $ npm install -g truffle $ truffle --version $ mkdir pet-shop-tutorial && cd pet-shop-tutorial
2. 建立一個 Truffle 專案 Truffle 有提供一些套件協助建立 DAPP
Truffle Boxes
我常用 React 開發
Truffle React Box
所以這裡使用 React 作為範例
建立一個 pet-shop 的 basic project
1 2 3 $ npx truffle unbox react$ npm install -g truffle$ truffle unbox react
1 Truffle initial 有很多方式, `truffle init` 會幫你建立一個空的 Truffle 專案
Truffle init
檔案結構 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 . ├── LICENSE ├── box-img-lg.png ├── box-img-sm.png ├── bs-config.json ├── build │ └── contracts │ ├── Adoption.json │ └── Migrations.json ├── contracts │ ├── Adoption.sol │ └── Migrations.sol ├── migrations │ └── 1 _initial_migration.js ├── package-lock.json ├── package.json ├── src │ ├── css │ │ ├── bootstrap.min .css │ │ └── bootstrap.min .css .map │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── images │ │ ├── boxer.jpeg │ │ ├── french-bulldog.jpeg │ │ ├── golden-retriever.jpeg │ │ └── scottish-terrier.jpeg │ ├── index.html │ ├── js │ │ ├── app.js │ │ ├── bootstrap.min .js │ │ ├── truffle-contract.js │ │ └── web3.min .js │ └── pets.json ├── test └── truffle-config.js
* contracts : Solidity 的檔案,在這個範例中有一個 Migrations.sol 的範例檔案
migrations : Truffle 利用 migrations system 來處理開發環境,A migration is an additional special smart contract that keeps track of changes.
test : 測試檔案
truffle.js : Truffle 的設定檔案
3. 寫一個基本的合約 在 constracts 中建立一個檔案 Adoption.sol
Adoption.sol
1 2 3 4 5 pragma solidity ^0.5.0; contract Adoption { }
pragma 定義使用 solidity 編譯器的版本
變數 Solidity 有一種特別的變數 adress,這是代表 Ethereum 的位址
儲存了 20 個 byte 得值, 每一個帳號和合約在 Ethereum block chain 都擁有一個 adress
這個變數是 唯一的
在 Adoption.sol 中宣告一個 adress
1 2 3 4 5 pragma solidity ^0.5.0; contract Adoption { address[16] public adopters; }
First Function: Adopting a pet 1 2 3 4 5 6 7 8 9 10 11 12 13 pragma solidity ^0.5.0; contract Adoption { address[16] public adopters; // Adopting a pet function adopt(uint petId) public returns (uint) { require(petId >= 0 && petId <= 15); adopters[petId] = msg.sender; return petId; } }
在 Solidity 中必須要定義函式的 輸入值和回傳值的型態,在這個範例中會接收一個 petId (整數)也會回傳一個整數
在函式要保證 petId 的範圍值必須在 adopters 陣列 範圍內, Solidity 中的陣列 index 是從 0 開始,所以這個 ID 會在 0~15 之間, 我們利用 require() 來定義這個範圍
如果這個 ID 在允許的範圍內 (0 ~ 15) 之間,則新增這個人的位址到採用者的陣列 adopters 而這個人的位址則是利用 msg.sender 來取得
最後回傳 petId 提供確認
Second Function: Retrieving the adopters array getter 只能回傳一個值,但是但是 16 個 API 不是很實際,所以我們需要一個 API 來回傳所有的寵物列表
在 adopt() 後增加一個 getAdopters() 這個 function
1 2 3 function getAdopters() public view returns (address[16] memory) { return adopters; }
adopters已經宣告過了,可以直接回傳,但是要確認加上 memory 這個關鍵字 確認給出的是變量的位置
而 view 這個關鍵字則代表這個 function 部會修改這個合約的任何狀態值,更詳細的資訊可以查看這
memory 表示 adopters 的值存在記憶體裡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 pragma solidity ^0.5.0; contract Adoption { address[16] public adopters; function adopt(uint petId) public returns (uint) { require(petId >= 0 && petId <= 15); adopters[petId] = msg.sender; return petId; } function getAdopters() public view returns (address[16] memory) { return adopters; } }
在這邊我們將回傳之前宣告的 adopters 定義類型為 address[16]
編譯並部署智能合約 Truffle 有一個開發者控制台, 他會生成一個開發區塊鏈,可以利用他測試部署合同
他可以直接在控制台運行,本範例大部分將會使用它完成
編譯 Solidity 是一個編譯語言,所以需要一個編譯器先編譯之後才能執行,
這時候應該會看到這些資訊
1 2 3 Compiling ./contracts/ Adoption.sol... Compiling ./contracts/Mig rations.sol... Writing artifacts to ./build/ contracts
部署 資料夾內會看到 migrations/1_initial_migration.js 這個檔案,,他是一個更改 contract 狀態的部屬腳本,會避免未來重複部署同樣的 Migrations.sol 合約
現在我們需要新增一個屬於我們自己的部署 script
在 migrations 資料夾中新增一個檔案 2_deploy_contracts.js
2_deploy_contracts.js 內容
1 2 3 4 5 const Adoption = artifacts.require ("Adoption" );module .exports = function (deployer ){ deployer.deploy (Adoption ); }
在部署之前要先執行 Ganache 他會有一個本地的區塊鏈在 port 7575
若您尚未下載 download Ganache
然後回到終端機
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 $ truffle migrate Compiling ./contracts/Adoption.sol... Writing artifacts to ./build/contracts ⚠️ Important ⚠️ If you're using an HDWalletProvider, it must be Web3 1.0 enabled or your migration will hang. Starting migrations... ====================== > Network name: 'development' > Network id: 5777 > Block gas limit: 6721975 1_initial_migration.js ====================== Deploying 'Migrations' ---------------------- > transaction hash: 0x9965bb63687936396ef9db5830b9e0a9ff36f10108b775abf944fc86f061454c > Blocks: 0 Seconds: 0 > contract address: 0x3216882738b0ca58BD4a2a3125Fa4bC651100C7e > account: 0x10D045570AD2a69921Dc4e6b55148e071fC7484D > balance: 99.99430184 > gas used: 284908 > gas price: 20 gwei > value sent: 0 ETH > total cost: 0.00569816 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.00569816 ETH Summary ======= > Total deployments: 1 > Final cost: 0.00569816 ETH
在 Ganache 中 blockchain 的狀態改變了從原本的 0 改變為 4 ,之後會再討論到交易成本
寫好第一個合約並且部署上了區塊鏈,接下來要開始測試一下你的合約
transaction hash 代表這個合約的序號
你可以透過這個序號來搜尋這個合約
回到 Terminal migrate 合約到鍊上
結果會是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 Starting migrations... ====================== > Network name: 'development' > Network id: 5777 > Block gas limit: 6721975 2_deploy_contracts.js ===================== Deploying 'Adoption' -------------------- > transaction hash: 0xac113a702da3ab7a8fde7ec8941143ff854cb8ae3f2457e1cc9251a0fa62a2b8 > Blocks: 0 Seconds: 0 > contract address: 0xAc30aaD46a83f8E8De3f452B0d4C175a1173a54b > account: 0x10D045570AD2a69921Dc4e6b55148e071fC7484D > balance: 99.98838348 > gas used: 253884 > gas price: 20 gwei > value sent: 0 ETH > total cost: 0.00507768 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.00507768 ETH Summary ======= > Total deployments: 1 > Final cost: 0.00507768 ETH
可以打開 Ganache 之前的數值是 0 現在會變成 4,也可以看到第一個帳號原本是 100但是現在不到 100 (我的顯示是 99.99),因為這次的 migration 花費了乙太幣,等等會討論到更多關於這個花費的問題
測試智能合約 Truffle 如何測試你的合約呢?
建立一個 TestAdoption.sol 在 test 的資料夾下
內容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 pragma solidity ^0.5.0; import "truffle/Assert.sol"; import "truffle/DeployedAddresses.sol"; import "../contracts/Adoption.sol"; contract TestAdoption { Adoption adoption = Adoption(DeployedAddresses.Adoption()); uint expectedPetId = 8; address expectedAdopter = address(this); }
Assert.sol: 提供 assertions 使用,判斷是否相等,大於小於等等的判斷
DeployedAddresses.sol: 側是的時後 Truffle 會在鍊上部署一個新的實例,會取得那一個的位址來使用模擬
Adoption.sol: 要測試的智能合約內容
然後再定義其他的變數
DeployedAddresses 模擬部署一個智能合約取得他的位址
expectedPetId 提供測試的寵物 ID
因為預計 TestAdoption 合約會發送交易,預期的 sneder 位址設為此,取得現在合約的 address
測試 adopt() 函式 測試 adopt() 使用這個函式成功後回傳 petId
可以判斷這個 petId 的直是否正確
在 TestAdoption.sol 中的 Adoption 中增加下面的程式碼
1 2 3 4 5 function testUserCanAdoptPet() public { uint returnedId = adoption.adopt(expectedPetId); Assert.equal(returnedId, expectedPetId, "Adoption of the expected pet should match what is returned."); }
expectedPetId 是我們要認養的 寵物 ID 跟回傳的 returnedId是否相等
測試單個寵物主人的主人 public 變數會有一個 getter 的 function 來取得,測試的過程中數據會持續存在
所以可以沿用 expectedPetId 在其他測試中
增加一個 function 在 TestAdoption.sol 中
1 2 3 4 5 function testGetAdopterAddressByPetId() public { address adopter = adoption.adopters(expectedPetId); Assert.equal(adopter, expectedAdopter, "Owner of the expected pet should be this contract"); }
取得 adopter 的位址 存在合約中,利用 Assert 判斷是否一致
測試所有寵物主人 1 2 3 4 5 function testGetAdopterAddressByPetIdInArray() public { address[16] memory adopters = adoption.getAdopters(); Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract"); }
注意 adopters 屬性,因為有 memory 關鍵字代表存在記憶體中,不是存在合約的 storage 中,當 adopters 在一個陣列中,比較了陣列中的 expectedAdopter做比較
Running test 1 2 3 4 5 6 7 8 9 10 11 12 $ truffle testUsing network 'development'. Compiling ./contracts/Adoption.sol... Compiling ./test/TestAdoption.sol... Compiling truffle/Assert.sol... Compiling truffle/DeployedAddresses.sol... TestAdoption ✓ testUserCanAdoptPet (75 ms) ✓ testGetAdopterAddressByPetId (64 ms) ✓ testGetAdopterAdderssByPetIdInArray (138 ms)
參考資料 Tutorial