一、智能合约的创建与测试
1、创建项目
truffle init
生成的目录如下:
2、编写智能合约Adoption.sol
pragma solidity ^0.4.17;
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]) {
return adopters;
}
}
注:这里没写构造函数,会自动生成。
3、编译合约
在项目根目录下执行:truffle compile,然后会生成一个build文件夹,里面是编译成的Adoption.json文件
4、在migration目录下创建部署文件
var Adoption = artifacts.require("Adoption"); //这里填入的是合约名,而不是文件名
module.exports = function(deployer) {
deployer.deploy(Adoption);
};
5、打开ganache(区块链的运行环境)
6、配置truffle.js文件
module.exports = {
networks: {
development: {
host: "127.0.0.1", //ganache网络的地址
port: 7545, //ganache的监听端口
network_id: "*" // Match any network id
}
}
};
注意:该配置是为了让truffle连接ganache为我们准备的区块链网络。
注意:查看合约编译生成的文件Adoption.json,里面的"networks":配置为空。
7、部署合约
truffle migrate
执行后查看Adoption.json,这时网络信息已经加上去了:
"networks": {
"5777": {
"events": {},
"links": {},
"address": "0x345ca3e014aaf5dca488057592ee47305d9b3e10",
"transactionHash": "0x0ec。。。"
}
},
8、这时查看ganache信息的block已经不再是0了。
9、编写测试合约(在test目录下创建TestAdoption.sol)
pragma solidity ^0.4.17;
import 'truffle/Assert.sol';
import 'truffle/DeployedAddresses.sol'; //用于获取部署合约的地址
import '../contracts/Adoption.sol';
contract TestAdoption {
Adoption adoption = Adoption(DeployedAddresses.Adoption());
function testUserCanAdoptPet() public {
uint returnedId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returnedId, expected, "Aoption of pet Id 8 should be recorded.");
}
function testGetAdopterAddressByPetid() public {
address expected = this;
address adopter = adoption.adopters(8);
Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
}
function testGetAdopterAddressByPetIdInArray() public {
address expected = this;
address[16] memory adopters = adoption.getAdopters(); //返回领养者的地址
Assert.equal(adopters[8], expected, "Owner of Pet Id 8 should be recoded.");
}
}
10、测试
在终端运行:truffle test。运行如下图所示,表示三个测试方法都通过了:
这时去查看ganache,发现多了很多交易,也打包了一些区块,说明测试过程也有很多交易产生。
二、初始化web环境
11、将项目转为npm工程
终端执行npm init
这一步执行完后会生成package.json文件,可用于显示工程的信息,也可以用于管理一些依赖。
12、提供一个web服务器
npm install lite-server
注:我通过这种方式安装失败,然后通过npm install lite-server --save-dev安装成功
13、新建一个src文件夹,用于存储web应用的源文件
14、配置服务器信息
根目录下创建bs-config.json文件,如下:
{
"port": 8000, //这个端口要不指定,则会有个默认端口
"server": {
"baseDir": ["./src", "./build/contracts"]
}
}
15、package.json文件中加入红框行
表示:在运行这个脚本命令时运行起server
16、在src目录下创建文件index.html,内容如下:
Hello Pet-shop
17、在终端运行
npm run dev
运行完之后会自动打开一个浏览器,输出 index.html中的内容Hello Pet-shop,如下所示:
18、页面编写
在上面创建的src目录中,完全拷贝从truffle unbox出来的pet-shop项目的src目录(css、fonts、images、js、pets.json等)。
19、编辑index.html,内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Pete's Pet Shop</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h1 class="text-center">Pete's Pet Shop</h1>
<div id="petsRow" class="row"></div>
</div>
<div id="petTemplate" style="display: none">
<div class="col-sm-6 col-md-4 col-lg-3">
<div class="panel panel-default panel-pet">
<div class="panel-heading">
<h3 class="panel-title">Scrappy</h3>
</div>
<div class="panel-body">
<img alt="140x140" data-src="holder.js/140x140"
class="img-rounded img-center" style="width: 100%;" src="" data-holder-rendered="true">
<br/><br/>
<strong>Breed</strong>: <span class="pet-breed">Golden Retriever</span><br/>
<strong>Age</strong>: <span class="pet-age">3</span><br/>
<strong>Location</strong>: <span class="pet-location">Warren, MI</span><br/><br/>
<button class="btn btn-default btn-adopt" type="button" data-id="0">Adopt</button>
</div>
</div>
</div>
</div>
<!-- 官方的代码中引入谷歌的库,但是谷歌被墙了 -->
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<!-- 下面的两个库是要和智能合约进行交互的,truffle-contract是web3的包装,有高级一点的js的用法 -->
<script src="js/web3.min.js"></script>
<script src="js/truffle-contract.js"></script>
<script src="js/app.js"></script>
</body>
</html>
20、编辑pets.json文件,内容如下:
[
{
"id": 0,
"name": "Frieda",
"picture": "images/scottish-terrier.jpeg",
"age": 3,
"breed": "Scottish Terrier",
"location": "Lisco, Alabama"
},
{
"id": 1,
"name": "Gina",
"picture": "images/scottish-terrier.jpeg",
"age": 3,
"breed": "Scottish Terrier",
"location": "Tooleville, West Virginia"
},
{
"id": 2,
"name": "Collins",
"picture": "images/french-bulldog.jpeg",
"age": 2,
"breed": "French Bulldog",
"location": "Freeburn, Idaho"
},
{
"id": 3,
"name": "Melissa",
"picture": "images/boxer.jpeg",
"age": 2,
"breed": "Boxer",
"location": "Camas, Pennsylvania"
},
{
"id": 4,
"name": "Jeanine",
"picture": "images/french-bulldog.jpeg",
"age": 2,
"breed": "French Bulldog",
"location": "Gerber, South Dakota"
},
{
"id": 5,
"name": "Elvia",
"picture": "images/french-bulldog.jpeg",
"age": 3,
"breed": "French Bulldog",
"location": "Innsbrook, Illinois"
},
{
"id": 6,
"name": "Latisha",
"picture": "images/golden-retriever.jpeg",
"age": 3,
"breed": "Golden Retriever",
"location": "Soudan, Louisiana"
},
{
"id": 7,
"name": "Coleman",
"picture": "images/golden-retriever.jpeg",
"age": 3,
"breed": "Golden Retriever",
"location": "Jacksonwald, Palau"
},
{
"id": 8,
"name": "Nichole",
"picture": "images/french-bulldog.jpeg",
"age": 2,
"breed": "French Bulldog",
"location": "Honolulu, Hawaii"
},
{
"id": 9,
"name": "Fran",
"picture": "images/boxer.jpeg",
"age": 3,
"breed": "Boxer",
"location": "Matheny, Utah"
},
{
"id": 10,
"name": "Leonor",
"picture": "images/boxer.jpeg",
"age": 2,
"breed": "Boxer",
"location": "Tyhee, Indiana"
},
{
"id": 11,
"name": "Dean",
"picture": "images/scottish-terrier.jpeg",
"age": 3,
"breed": "Scottish Terrier",
"location": "Windsor, Montana"
},
{
"id": 12,
"name": "Stevenson",
"picture": "images/french-bulldog.jpeg",
"age": 3,
"breed": "French Bulldog",
"location": "Kingstowne, Nevada"
},
{
"id": 13,
"name": "Kristina",
"picture": "images/golden-retriever.jpeg",
"age": 4,
"breed": "Golden Retriever",
"location": "Sultana, Massachusetts"
},
{
"id": 14,
"name": "Ethel",
"picture": "images/golden-retriever.jpeg",
"age": 2,
"breed": "Golden Retriever",
"location": "Broadlands, Oregon"
},
{
"id": 15,
"name": "Terry",
"picture": "images/golden-retriever.jpeg",
"age": 2,
"breed": "Golden Retriever",
"location": "Dawn, Wisconsin"
}
]
21、这时刷新浏览器,就可以看到UI界面了。
22、完成UI和合约的交互
在上一步骤完成时,有个很漂亮的UI,但是还没有交互效果,因为并没有调用到智能合约。这
部分逻辑在./src/app.js中完成,如下:
App = {
web3Provider: null,
contracts: {},
init: function() {
// Load pets.
$.getJSON('../pets.json', function(data) {
var petsRow = $('#petsRow');
var petTemplate = $('#petTemplate');
//把一个个pet对象赋值到模板对应的标签上去
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() { //初始化web3
//web3初始化过程参考:https://github.com/ethereum/web3.js
//在初始化web3时,要先判断当前环境中是否已经有web3,没有再构建web3。因为在使用metamask时,会自带web3.
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider;
} else {
App.web3Provider = new Web3.prviders.HttpProvider("http://127.0.0.1:7545"); //ganache的地址
}
web3 = new Web3(App.web3Provider);
return App.initContract();
},
initContract: function() { //初始化合约
$.getJSON('Adoption.json', function(data) { //为何可直接加载Adoption.json文件呢,这是因为./build/contracts目录亿配置到服务器的环境中
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); //btn-adopt是index.html,当这个按钮被点击时调用App的handleAdopt方法
},
markAdopted: function(adopters, account) { //标记当前的宠物是否被领养
var apotionInstance;
//App.contracts.Adoption.deployed()可以拿到合约的实例,传递给then(function(instance)
//中的instance。.then(function(instance)是promise的写法,
App.contracts.Adoption.deployed().then(function(instance) {
apotionInstance = instance;
return apotionInstance.getAdopters.call();
}).then(function(adopters) { //上一步return apotionInstance.getAdopters.call()的返回值传递给了function(adopters)
for(i =0; i< adopters.length; i++) {
console.log(adopters[i]);
//地址元素默认为0x0000000000000000000000000000000000000000
if(adopters[i] !== '0x0000000000000000000000000000000000000000') {
//button的文字写成Success,同时不能被点击
$('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
}
}
}).catch(function(err) { //捕获异常
console.log(err.message);
})
},
handleAdopt: function(event) {
event.preventDefault();
var apotionInstance;
var petId = parseInt($(event.target).data('id'));
web3.eth.getAccounts(function(error, accounts){ //拿到当前环境下的账号
var account = accounts[0]; //取第一个账号
App.contracts.Adoption.deployed().then(function(instance){
apotionInstance = instance;
return apotionInstance.adopt(petId, {from: account});
}).then(function(result) {
return App.markAdopted(); //标记状态
} ).catch(function(err) {
console.log(err.message);
});
});
}
};
$(function() {
$(window).load(function() { //当window被加载时,会调用App.init
App.init();
});
});
22、运行(调试1)
点了adopt没有反应,打开F12调试。(360浏览器)
22、运行(调试2)。(谷歌浏览器)
原因是要在谷歌浏览器的metamask中设置本地以太坊网络。
23、metamask设置ganache网络
我们的ganache中的地址默认为127.0.0.1:7545,如果metamask中有这个地址的网络则直接选择就好,如果没有则需要选择Custom RPC进行配置。
这样还是不能领养宠物,因为网络中的地址和ganache中的10个地址不一样,要将ganache中的10个地址,导入到metamask网络中去
24、将ganache中的一个地址私钥导入到metamask网络
1)复制ganache中的第一个地址的私钥
3)输入刚才拷贝的私钥,导入即可看到我们选择的那个地址