以太坊智慧合約開發(truffle box pet-shop為例)

開發環境

    我在ubuntu環境下開發的,建議裝好vscode,nodejs 。選用別的編譯器也可以。

    裝好nodejs後裝一下truffle

    

npm install -g truffle
truffle version

    可以看到版本號,說明安裝成功。

開發語言

    本文選用solidity,現在以太坊智慧合約開發主流語言,不過比較新東西,和c java python這些成熟語言相比用起來沒那麼方便。想學的話推薦一個網站,加密殭屍,通過做一個遊戲來引導solidity入門。如果英語好的話可以看看官方文件:solidity官方文件。這個文件也有中文版,但是更新慢一點solidity文件中文版

  另外還用了nodejs,不過我不太熟悉js……有點尷尬。

編譯測試

    新建一個空資料夾,比如叫pet-shop。然後進入資料夾   

truffle unbox pet-shop

  解釋一下,trufflebox 給大家提供一些模板,這裡可以看到。當然也可以自己新建一個空的工程,我會在另一篇部落格做講解。這個unbox時間比較長,結束後大概這個樣子:

    看一下工程目錄:

contracts:智慧合約資料夾

migrations:用於部署的指令碼

node_modules:nodejs

src:前端資源

test:單元測試

bs-config.json:lite-server的配置檔案

package.json:nodejs依賴

truffle.js:不用解釋

程式碼編寫與釋出

contract下新建Adoption.sol:

pragma solidity ^0.4.16;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";
contract TestAdoption {
Adoption adoption = Adoption(DeployedAddresses.Adoption());   
function testUserCanAdoptPet() {      
uint returnedId = adoption.adopt(8);       
uint expected = 8;
Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
}   
function testGetAdopterAddressByPetIdInArray() {       
address expected = this;
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}
}

如果你用的是vscode,那麼有時候會出現一些奇怪的錯誤提示,比如什麼換行空格,solidity版本之類的,可以忽略不計,沒有python那麼嚴格。比如下圖這樣的:

migrations下新建2_deploy_contracts.js

var Adoption = artifacts.require("Adoption");
module.exports = function(deployer) {
deployer.deploy(Adoption);
};

終端輸入truffle develop 進入truffle 控制檯.

在truffle控制檯輸入compile進行編譯:

輸入migration進行釋出。

測試

單元測試還是必要的,當然你要有把握沒bug不測也沒事。

test下新建TestAdoption.sol.

pragma solidity ^0.4.16;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";
contract TestAdoption {
Adoption adoption = Adoption(DeployedAddresses.Adoption());   
function testUserCanAdoptPet() {      
uint returnedId = adoption.adopt(8);       
uint expected = 8;
Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
}   
function testGetAdopterAddressByPetIdInArray() {       
address expected = this;
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}
}

回到truffle控制檯,輸入test指令進行測試。

前端互動

工程已經幫我們寫好了html並定義好了一些js函式,完成剩下部分即可.

開啟js/app.js檔案。看到有如下函式:

init:讀取json並初始化頁面,這也是唯一一個已經寫好的函式

接下來的函式要自己新增daima

initweb3:初始化web3(web3.js是用來與以太坊智慧合約互動的)

 initWeb3: function() {
// Is there is an injected web3 instance?
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider;
} else {
// If no injected web3 instance is detected, fallback to the TestRPC
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
}
web3 = new Web3(App.web3Provider);
return App.initContract();
},

initContract:初始化智慧合約

initContract: function() {
$.getJSON('Adoption.json', function(data) {
// Get the necessary contract artifact file and instantiate it with truffle-contract
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();
},

bindEvents:繫結點選事件

  bindEvents: function() {
$(document).on('click', '.btn-adopt', App.handleAdopt);
},

markAdoption:根據領養情況更新UI

markAdopted: function(adopters, account) {
var adoptionInstance;
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
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);
});
},

handleAdoption:處理領養事件

  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;
// Execute adopt as a transaction by sending account
return adoptionInstance.adopt(petId, {from: account});
}).then(function(result) {
return App.markAdopted();
}).catch(function(err) {
console.log(err.message);
});
});
}

這些程式碼比較簡單沒啥可說的吧……

關於html頁面,引用的jquery是線上的,可能也需要梯子,因此我替換成了:

<script
src="http://code.jquery.com/jquery-2.2.4.js"
integrity="sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI="
crossorigin="anonymous"></script>

metamask與實際執行

為了在瀏覽器執行dapp需要一個叫metamask的外掛。地址:https://metamask.io/.我用的chrome瀏覽器。可能需要梯子,不過我已經放到github 上了,見文章末尾。

可以看到右上角有個小狐狸頭像,點開,假裝閱讀並接受協議。

不要create,先import existing den.第一個框框輸入助記詞:candy maple cake sugar pudding cream honey rich smooth crumble sweet treat.然後再輸入密碼和確認密碼.確認,看到介面如下:

顯然最好不要用公網測試,除非你是V神。所以自己搞一個,點選custom RPC

在main ethereum network 輸入http://localhost:9545. 儲存.

點選setting左側箭頭,回到主頁。

回到linux終端,不是truffle終端,進入工程目錄下輸入npm run dev啟動專案.

github