來源 | 深入淺出區塊鏈技術
作者 | Tiny 熊
出品 | 區塊鏈大本營(blockchain_camp)
老鐵們,今天營長手把手帶你們開發一款去中心化應用(Dapp)——寵物商店,來來來,先看下效果圖:
怎麼樣?是不是很酷?
透過本次動手實操,你將學到:
搭建智慧合約開發環境
建立 Truffle 專案
編寫智慧合約
編譯和部署智慧合約到區塊鏈
如何透過 Web3 和智慧合約互動
MetaMask 的使用
你為什麼要開發這款 DApp?又如何動手開發呢?跟隨營長一起動手吧!
專案背景
Pete 有一個寵物店,有16只寵物,他想開發一個去中心化應用,讓大家來領養寵物。在 truffle box 中,已經提供了 pet-shop 的網站部分的程式碼,我們只需要編寫合約及互動部分。
環境搭建
安裝 Node
安裝 Truffle :npm install -g truffle
安裝 Ganache
建立專案
1、建立專案目錄並進入
> mkdir pet-shop-tutorial
> cd pet-shop-tutorial
2、使用 truffle unbox 建立專案
truffle unbox pet-shop
Downloading...
Unpacking...
Settingup...
Unboxsuccessful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Testcontracts: truffle test
npm run dev :
這一步需要等待一會
也可以使用 truffle init 來建立一個全新的專案。
專案目錄結構
contracts/ 智慧合約的資料夾,所有的智慧合約檔案都放置在這裡,裡面包含一個重要的合約Migrations.sol
migrations/ 用來處理部署(遷移)智慧合約 ,遷移是一個額外特別的合約用來儲存合約的變化。
test/ 智慧合約測試用例資料夾
truffle.js/ 配置檔案
其他程式碼可以暫時不用管
編寫智慧合約
智慧合約承擔著分散式應用的後臺邏輯和儲存。智慧合約使用 solidity 編寫,可參考:
https://learnblockchain.cn/categories/ethereum/Solidity/
在 contracts 目錄下,新增合約檔案 Adoption.sol
pragma solidity ^0.5.0;
contract Adoption {
address[16] public adopters; // 儲存領養者的地址
// 領養寵物
functionadopt(uint petId)publicreturns(uint){
require(petId >= 0 && petId <= 15); // 確保id在陣列長度內
adopters[petId] = msg.sender; // 儲存呼叫這地址
return petId;
}
// 返回領養者
functiongetAdopters()publicviewreturns(address[16] memory){
return adopters;
}
}
編譯部署智慧合約
Truffle 整合了一個開發者控制檯,可用來生成一個開發鏈用來測試和部署智慧合約。
1、編譯
Solidity 是編譯型語言,需要把可讀的 Solidity 程式碼編譯為 EVM 位元組碼才能執行。
DApp 的根目錄 pet-shop-tutorial 下,
> truffle compile
輸出
Compiling./contracts/Adoption.sol...
Writingartifacts to ./build/contracts
2、部署
編譯之後,就可以部署到區塊鏈上。
在 migrations 資料夾下已經有一個 1_initial_migration.js 部署指令碼,
用來部署 Migrations.sol 合約。
Migrations.sol 用來確保不會部署相同的合約。
現在我們來建立一個自己的部署指令碼 2_deploy_contracts.js
var Adoption = artifacts.require("Adoption");
module.exports = function(deployer) {
deployer.deploy(Adoption);
};
在執行部署之前,需要確保有一個區塊鏈執行, 可以使用 Ganache 來開啟一個私鏈來進行開發測試,預設會在7545埠上執行一個開發鏈。
Ganache 啟動之後是這樣:
接下來執行部署命令:
> truffle migrate
執行後,有以下類似的輸出:
Usingnetwork 'develop'.
Runningmigration: 1_initial_migration.js
DeployingMigrations...
0x3076b7dac65afc44ec51508bf6f2b6894f833f0f9560ecad2d6d41ed98a4679f
Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Savingsuccessful migration to network...
0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Savingartifacts...
Runningmigration: 2_deploy_contracts.js
DeployingAdoption...
0x2c6ab4471c225b5473f2079ee42ca1356007e51d5bb57eb80bfeb406acc35cd4
Adoption: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Savingsuccessful migration to network...
0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Savingartifacts...
在開啟的Ganache裡可以看到區塊鏈狀態的變化,現在產生了4個區塊。
這時說明已經智慧合約已經部署好了。
測試
現在我們來測試一下智慧合約,測試用例可以用 JavaScript 或 Solidity 來編寫,這裡使用 Solidity。
在 test 目錄下新建一個 TestAdoption.sol,編寫測試合約
pragma solidity ^0.5.0;
import"truffle/Assert.sol"; // 引入的斷言
import"truffle/DeployedAddresses.sol"; // 用來獲取被測試合約的地址
import"../contracts/Adoption.sol"; // 被測試合約
contract TestAdoption {
Adoption adoption = Adoption(DeployedAddresses.Adoption());
// 領養測試用例
functiontestUserCanAdoptPet() public{
uint returnedId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
}
// 寵物所有者測試用例
functiontestGetAdopterAddressByPetId() public{
// 期望領養者的地址就是本合約地址,因為交易是由測試合約發起交易,
address expected = this;
address adopter = adoption.adopters(8);
Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
}
// 測試所有領養者
functiontestGetAdopterAddressByPetIdInArray() public{
// 領養者的地址就是本合約地址
address expected = this;
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}
}
提示:Assert.sol 及 DeployedAddresses.sol是Truffle框架提供,在test目錄下並不提供truffle目錄。
TestAdoption 合約中新增 adopt 的測試用例。
執行測試用例
在終端中,執行
truffle test
如果測試透過,則終端輸出:
Usingnetwork 'develop'.
Compiling./contracts/Adoption.sol...
Compiling./test/TestAdoption.sol...
Compilingtruffle/Assert.sol...
Compilingtruffle/DeployedAddresses.sol...
TestAdoption
testUserCanAdoptPet (62ms)
testGetAdopterAddressByPetId (53ms)
testGetAdopterAddressByPetIdInArray (73ms)
3passing (554ms)
建立使用者介面和智慧合約互動
我們已經編寫和部署及測試好了我們的合約,接下我們為合約編寫 UI,讓合約真正可以用起來。
在 Truffle Box pet-shop 裡,已經包含了應用的前端程式碼,程式碼在 src/ 資料夾下。
在編輯器中開啟 src/js/app.js
可以看到用來管理整個應用的 App 物件,init 函式載入寵物資訊,就初始化 web3(web3 是一個實現了與以太坊節點通訊的庫,我們利用 web3 來和合約進行互動)。
1、初始化web3
接下來,我們來編輯 app.js 修改 initWeb3():
刪除註釋,修改為:
initWeb3: function() {
// Modern dapp browsers...
if (window.ethereum) {
App.web3Provider = window.ethereum;
try {
// Request account access
awaitwindow.ethereum.enable();
} catch (error) {
// User denied account access...
console.error("User denied account access")
}
}
// Legacy dapp browsers...
elseif (window.web3) {
App.web3Provider = window.web3.currentProvider;
}
// If no injected web3 instance is detected, fall back to Ganache
else {
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3Provider);
return App.initContract();
}
新的 Dapp 瀏覽器或 MetaMask 的新版本,注入了一個 ethereum 物件到 window 物件裡, 應該優先使用 ethereum 來構造 web3, 同時使用 ethereum.enable() 來請求使用者授權訪問連結賬號。
程式碼中優先使用 Mist 或 MetaMask 提供的 web3 例項,如果沒有則從本地環境建立一個。
2、例項化合約
使用 truffle-contract 會幫我們儲存合約部署的資訊,就不需要我們手動修改合約地址,修改 initContract() 程式碼如下:
initContract: function() {
// 載入Adoption.json,儲存了Adoption的ABI(介面說明)資訊及部署後的網路(地址)資訊,它在編譯合約的時候生成ABI,在部署的時候追加網路資訊
$.getJSON('Adoption.json', function(data) {
// 用Adoption.json資料建立一個可互動的TruffleContract合約例項。
var AdoptionArtifact = data;
App.contracts.Adoption = TruffleContract(AdoptionArtifact);
// Set the provider for our contract
App.contracts.Adoption.setProvider(App.web3Provider);
// Use our contract to retrieve and mark the adopted pets
return App.markAdopted();
});
return App.bindEvents();
}
3、處理領養
修改markAdopted()程式碼:
markAdopted: function(adopters, account) {
var adoptionInstance;
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
// 呼叫合約的getAdopters(), 用call讀取資訊不用消耗gas
return adoptionInstance.getAdopters.call();
}).then(function(adopters) {
for (i = 0; i < adopters.length; i++) {
if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
$('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
}
}
}).catch(function(err) {
console.log(err.message);
});
}
修改handleAdopt()程式碼:
handleAdopt: function(event) {
event.preventDefault();
var petId = parseInt($(event.target).data('id'));
var adoptionInstance;
// 獲取使用者賬號
web3.eth.getAccounts(function(error, accounts) {
if (error) {
console.log(error);
}
var account = accounts[0];
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
// 傳送交易領養寵物
return adoptionInstance.adopt(petId, {from: account});
}).then(function(result) {
return App.markAdopted();
}).catch(function(err) {
console.log(err.message);
});
});
}
在瀏覽器中執行
1、安裝 MetaMask
MetaMask 是一款外掛形式的以太坊輕客戶端,開發過程中使用 MetaMask 和我們的 dapp 進行互動是個很好的選擇。
2、配置錢包
在接受隱私說明後,會出現頁面如下:
這裡我們透過還原一個 Ganache 為我們建立好的錢包,作為我們的開發測試錢包。點選頁面的 Import Existing DEN,輸入 Ganache 顯示的助記詞。
candy maple cake sugar pudding cream honey rich smooth crumble sweet treat
然後自己想要的密碼,點選OK,如圖:
3、連線開發區塊鏈網路
預設連線的是以太坊主網,選擇 Custom RPC,新增一個網路:http://127.0.0.1:7545,點返回後,顯示如下:
這是左上角顯示為 Private Network,賬號是 Ganache 中預設的第一個賬號。
至此 MetaMask 安裝、配置已經完成。
4、安裝和配置lite-server
接下來需要本地的 web 伺服器提供服務的訪問,Truffle Box pet-shop 裡提供了一個 lite-server 可以直接使用,我們看看它是如何工作的。
bs-config.json 指示了 lite-server 的工作目錄。
{
"server": {
"baseDir": ["./src", "./build/contracts"]
}
}
./src 是網站檔案目錄
./build/contracts 是合約輸出目錄
以此同時,在package.json檔案的scripts中新增了dev命令:
"scripts": {
"dev": "lite-server",
"test": "echo \"Error: no test specified\" && exit 1"
},
當執行npm run dev的時候,就會啟動lite-server
5、啟動服務
> npm run dev
會自動開啟瀏覽器顯示我們的 dapp,如本文的第一張圖。
現在領養一直寵物看看,當我們點選 Adopt 時,MetaMask 會提示我們交易的確認,如圖:
點選 Submit 確認後,就可以看到成功領養了這次寵物。
在 MetaMask 中,也可以看到交易的清單:
好了,恭喜你,即將成為一名去中心化式應用開發者的你已經成為邁出了堅實的一步。
參考文件:
Truffle手冊
https://truffleframework.com/tutorials/pet-shop
中國人的區塊鏈論文闖入國際學術頂會
4月21日相約北京,不見不散!!
推薦閱讀:
猛戳"閱讀原文"有驚喜喲
老鐵在看了嗎?👇