本篇是翻譯自Truffle官方所釋出的Dapp教學文檔Pet-Shop
http://truffleframework.com/tutorials/pet-shop
執行環境
開發前準備
首先要安裝testrpc、truffle :
1 2 3 4 5 npm install -g ethereumjs-testrpc npm install -g truffle
安裝truffle上已經預先包好的練習專案(Truffle box: ETHEREUM PET SHOP):
1 2 3 4 5 6 7 8 mkdir pet-shop-tutorial cd pet-shop-tutorial truffle unbox pet-shop
目錄架構
/contracts: 存放合約的地方,檔名為.sol,而Migrate.sol是負責紀錄其他合約如何deploy到區塊鏈,不能刪除!
/migrations: 負責將合約掛到區塊鏈上,並且追蹤合約的更動狀況。
/test: 包含Javascript and solidity檔案,負責測試合約內容。
truffle.js
: truffle的設定檔
合約內容
在/contract 建立 Adoption.sol
宣告一個contract:
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 pragma solidity ^0.4 .4 ; 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 returns (address[16 ] ) { return adopters; } }
編譯(Compiling)和部署(Migrating)合約
寫好合約後,需要將.sol檔進行編譯成.bytecode,才能在EVM (ethereum virtual machine上執行).
然後在terminal上執行 testrpc
啟動
啟動後會出現:
數組帳戶的address以及私鑰
HD wallet的資訊(稍後會提到metatask,會使用到Mnemonic section的資訊)
Mnemonic為數個變數的資訊,如下
Mnemonic: spider level team helmet shaft clarify abuse recipe stem ankle angry fee
執行 truffle compile
會看到 .sol檔被編譯
Migration
migrate.sol描述如何將合約的內容部署到鏈上,並且處理合約上state的更動。
在 /migration
檔案內:
1 2 3 4 5 6 var Migrations = artifacts.require("./Migrations.sol" );module .exports = function (deployer ) { deployer.deploy(Migrations); };
透過該檔案可以追蹤後續contract的變化,已部署過的合約且沒有被修改就不用再被部署(不然又會消耗gas).
2_deploy_contracts.js
注意,這邊的命名開頭要編號,因為truffle進行migrate時會依據該編號而進行。
1 2 3 4 5 var Adoption = artifacts.require("./Adoption.sol" );module .exports = function (deployer ) { deployer.deploy(Adoption); };
在terminal執行 truffle migrate
會看到如下結果
1 2 3 4 5 6 7 8 9 10 11 12 Using network 'development' . Running migration: 1_initial_migration.js Deploying Migrations.. . Migrations: 0x75175eb116b36ff5fef15ebd15cbab01b50b50d1 Saving successful migration to network.. . Saving artifacts.. . Running migration: 2_deploy_contracts.js Deploying Adoption.. . Adoption: 0xb9f485451a945e65e48d9dd7fc5d759af0a89e21 Saving successful migration to network.. . Saving artifacts.. .
若看到上述表面表示我們寫的第一隻 Adoption.sol的合約已經成功被部署到鏈上!
為Smart Contract寫測試
我們可以用javascript或solidity寫測試,
不過本範例用solidity來寫。
在 /test 目錄下創建 TestAdoption.sol
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 42 43 44 45 46 47 48 pragma solidity ^0.4 .11 ; 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 testGetAdopterAddressByPetId ( ) { address expected = this ; address adopter = adoption.adopters(8 ); Assert.equal(adopter, expected, "Owner 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
若看到以下畫面表示test通過
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Using network 'development' . Compiling ./contracts/Adoption.sol.. . Compiling ./test/TestAdoption.sol.. . Compiling truffle/Assert.sol.. . Compiling truffle/DeployedAddresses.sol.. . TestAdoption ✓ testUserCanAdoptPet (91ms) ✓ testGetAdopterAddressByPetId (70ms) ✓ testGetAdopterAddressByPetIdInArray (89ms) 3 passing (670ms)
使用UI和Smart Contract互動
當解開truffle box的pet-shop,可以看到在 /src
目錄底下
會有已經預設好的UI檔供練習用。
使用Web3.js初始化前端環境
Web3.js為用來和ethereum溝通的javascript library.
(而練習專案前端是使用jQuery)
在 /src/js/app.js
檔案的內容改為以下
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 App = { web3Provider: null , contracts: {}, init: function ( ) { $.getJSON('../pets.json' , function (data ) { var petsRow = $('#petsRow' ); var petTemplate = $('#petTemplate' ); for (i = 0 ; i < data.length; i ++) { petTemplate.find('.panel-title' ).text(data[i].name); petTemplate.find('img' ).attr('src' , data[i].picture); petTemplate.find('.pet-breed' ).text(data[i].breed); petTemplate.find('.pet-age' ).text(data[i].age); petTemplate.find('.pet-location' ).text(data[i].location); petTemplate.find('.btn-adopt' ).attr('data-id' , data[i].id); petsRow.append(petTemplate.html()); } }); return App.initWeb3(); }, initWeb3: function ( ) { if (typeof web3 !== 'undefined' ) { App.web3Provider = web3.currentProvider; web3 = new Web3(web3.currentProvider); } else { App.web3Provider = new web3.providers.HttpProvider('http://localhost:8545' ); web3 = new Web3(App.web3Provider); } return App.initContract(); }, initContract: function ( ) { $.getJSON('Adoption.json' , function (data ) { var AdoptionArtifact = data; App.contracts.Adoption = TruffleContract(AdoptionArtifact); App.contracts.Adoption.setProvider(App.web3Provider); return App.markAdopted(); }); return App.bindEvents(); }, bindEvents: function ( ) { $(document ).on('click' , '.btn-adopt' , App.handleAdopt); }, handleAdopt: function ( ) { 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); }); }); }, 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('Pending...' ).attr('disabled' , true ); } } }).catch(function (err ) { console .log(err.message); }); } }; $(function ( ) { $(window ).load(function ( ) { App.init(); }); });
在Chrome上和Dapp互動
安裝 metamask
的擴充套件
長這樣:
由於我們要測試自己的Dapp,點選該圖左上角切換到自己開啟testrpc:8545的私有鏈
因為是初次登入,點選I forgot my password
將一開始開啟testrpc 產生的 Mnemonic(數組變數名稱)貼到wallet seed.
Mnemonic:
1 spider level team helmet shaft clarify abuse recipe stem ankle angry fee
(Warning:你的testrpc產生的Mnemonic變數組會跟上面的不一樣)
設置自己的新密碼,確認後點選OK
進入後就會看到自己的第一筆account資訊了
若有顯示帳戶表示成功與testrpc連接
(原本testrpc是預設100 ether,顯示出來不是100 ether的原因是因為
部署合約到自己的private blockchain也需要費用)
安裝和設置lite-server
啟動Dapp
lite-server
已經包含在pet-shop的box內
在 bs-config.json
檔案中
1 2 3 4 5 { "server" : { "baseDir" : ["./src" , "./build/contracts" ] } }
該設置告訴server要執行的基底目錄在哪。
./src
:前端的內容
./build/contracts
:放置合約的內容
在script檔中
1 2 3 4 "scripts" : { "dev" : "lite-server" , "test" : "echo \" Error : no test specified\" && exit 1" },
設置 "dev" : lite-server"
方便我們可以直接在terminal下執行 npm run dev
來運行Dapp
運行成功後的樣子
若要進行認養:點選 Adopt
按鈕
點選Sumit
後即完成認養的動作,並且把該認養的資訊透過合約掛到testrpc的鏈上,並在該寵物上狀態改成 Pending...
(已認養)
延伸