基于以太坊的去中心化宠物商店构建教程

基于以太坊的去中心化宠物商店构建教程_第1张图片

策划|Tina编辑|盖磊 区块链前哨导语:  作为一款以区块链和以太坊为基础建立起来的游戏,“链养猫”游戏《谜恋猫》(CryptoKitties)去年 11 月发布至今,已经吸引 150 多万用户,总共完成的交易已经超过 4000 万美元。其中流行的一些小猫价值已经超过 20 万美元。而在本周,“链养猫”背后的 Axiom Zen 游戏工作室把它分拆为独立公司,并且已经获得 1200 万美元融资。

2 月 4 日晚,百度悄然上线了区块链游戏“莱茨狗”,pet-chain.baidu.com,造型和玩法上与“链养猫”如出一辙。官方介绍:“莱茨狗”的开发团队来自百度金融区块链实验室,目前的技术已应用于多条核心业务线,支撑了超 500 亿元资产的真实性问题。

针对宠物商店这一现象级游戏应用,我们给出了一个在以太坊上构建去中心化区块链应用的教程。教程中使用了 Solidity 语言和 Truffle 框架,详细说明了智能合约的编写、编译、模拟部署和测试过程,并介绍了如何使用 Truffle Box 构建应用的 UI。

大家都搞区块链,来不及解释了,赶紧上车就对了,按照这个教程自己写个宠物商店吧。

更多干货内容请关注微信公众号“区块链前哨”,(ID:blockchain-666)

基于以太坊的去中心化宠物商店构建教程_第2张图片

基于区块链构建的去中心化应用(Dapp,Distributed application)引人关注,因为这样的应用并非集中于某个特定的管理者。

下面,我们将生成一个基本的去中心化应用,以此实际了解此类应用的机制。

遵循 Truffle 指南中提供的“以太坊宠物商店”,我将在 Web 上实际构建一个宠物商店去中心化应用。基于 Truffle 开发框架,我使用了一种称为“Solidity”的语言编写智能合约。

项目完工的效果如下图。如果用户点击了心仪宠物所对应的“adopt”按钮,应用将启动 MetaMask 检查所展示的宠物的数量和费用、创建交易并使用 ETH 支付。

基于以太坊的去中心化宠物商店构建教程_第3张图片

基于以太坊的去中心化宠物商店构建教程_第4张图片

我们可以看到,该应用不同于一般的电子商务网站,在购买时不必输入个人信息或信用卡信息。此外,购买数据以交易形式记录在以太坊区块链中,因此不会被篡改。

实现去中心化应用的具体流程如下:

  • 设置开发环境;

  • 使用 Truffle Box 创建 Truffle 项目;

  • 描述智能合约;

  • 编译并模拟部署(Migrating)智能合约;

  • 测试智能合约;

  • 建立附着于智能合约之上的 UI;

  • 在浏览器中使用去中心化应用。

基于以太坊的去中心化宠物商店构建教程_第5张图片

首先准备使用 node 和 npm 的环境。对于 Ubuntu 操作系统,安装 Node.js 8.x 的操作命令为:

$ apt-get update
$ curl -sL https://deb.nodesource.com/setup_8.x | bash -
$ apt-get install -y nodejs

第一步,我们需要安装 Truffle。

$ npm install -g truffle

Truffle 是一个以太坊开发框架。对于智能合约开发,Truffle 是一种非常有用的框架,可以高效地实现源代码的编译和部署。

第二步,建立一个名为“pet-shop-tutorial”的文件夹作为工作目录。通常使用命令 truffle init 初始化工作目录,并创建一个空目录。但是在本文给出的教程中,是在一个预先准备好的项目“Truffle Box”手工中创建了这个目录:

$ mkdir pet-shop-tutorial
$ cd pet-shop-tutorial
$ truffle unbox pet-shop

第三步,在“pet-shop-tutorial”目录中建立如下图所列的文件和目录:

基于以太坊的去中心化宠物商店构建教程_第6张图片

实际使用的目录和文件如下:

  • contracts 目录:包含描述智能合约的 Solidity 源文件;

  • migrations 目录:模拟部署(migration)系统,用于部署智能合约过程中。

  • test 目录:目录中为测试文件,使用 JavaScript 和 Solidity 编写;

  • truffle.js:Truffle 配置文件。

其中,Solidity 是一种描述以太坊智能合约的编程语言。

基于以太坊的去中心化宠物商店构建教程_第7张图片

下面,我将使用 Solidity 编写智能合约。在所创建的 contracts 目录中,建立一个名为“Adoption.sol”的文件,文件内容如下:

基于以太坊的去中心化宠物商店构建教程_第8张图片

下面我依次介绍代码的各个部分:

pragma solidity ^0.4.4;

该语句指定了 Solidity 编译器的版本信息。此外,Solidity 与 JavaScript 类似,需在代码行结尾处添加分号“;”。

contract Adoption { ・・・ }

这里定义了一个名为 Adoption 的合约,并在其中实现合约。

address[16] public adopters;

这句话定义了一个名为 adopters 的状态变量。鉴于 Solidity 是一种静态语言,因此变量必须要定义类型。除了 string、uint 等通用数据类型之外,Solidity 还具有一种特有的数据类型 address。address 中包含账户的地址。

这里,定义了一个名为 adopters 的 address 数组,该变量具有 16 个地址。

此外,在 adopters 变量前指定了 public,即任何人都可以访问合约。

在定义了以上变量之后,开始定义合约的方法。

function adopt(uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15);
adopters[petId] = msg.sender;
return petId;
}

根据 adopters 数组的长度,将整数类型变量 petId 的值设为 0 到 15(数组的索引值从 0 开始)。

代码中使用 require() 函数设置 petId 的值为 0 到 15。

msg.sender 表示执行函数者(或智能合约)的地址。

这样,语句 adopters [petId] = msg.sender; 将执行函数者的地址添加到 adopters 数组。

返回值在 petId 中。

上面定义的 adopt() 函数返回一个地址,因为 petId 是 adopters 数组的键值。

但是,鉴于每次重加载都需要做 16 次 API 调用,我使用下面定义的 getAdopters() 函数返回整个 adopters 数组:

function getAdopters() public returns (address[16]) {
return adopters;
}

鉴于变量 adopters 已经定义,函数可以仅指定数据类型,并将返回值返回。

至此,我完成了对智能合约的描述。

总结一下,我创建了如下的 Adoption 合约:“共有 16 种宠物。如果用户想领养一只宠物,就将用户地址和该宠物 ID 绑定在一起”。

下面,我们将继续编译智能合约,并模拟部署。

基于以太坊的去中心化宠物商店构建教程_第9张图片

编译将以编程语言编写的源代码转译为机器可直接执行的机器语言。换句话说,本例中就是将 Solidity 语言编写的代码转换为 EVM(以太坊虚拟机,Ethereum Virtual Machine)可执行的字节码。

在包含去中心化应用的目录中,使用终端等方式加载 Truffle Develop:

$ truffle develop

然后,在启动的 Truffle 开发控制台上,输入 compile 命令:

$ truffle(develop)> compile

如果输出如下,表明编译成功。

Compiling ./contracts/Adoption.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts

尽管其中可能存在一些公开可见的警告,但是继续编译是没有问题的。下面请一直保持 Truffle 开发控制台的运行。

c13261be9ce5f382ffcd6733221d79b3d4be6d28

模拟部署(Migration)类似于“移动”,就是将已有系统或类似系统迁移到一个新的平台上。

在本例中,模拟部署文件完成将所创建的 Adoption 合约部署到以太坊区块链网络上。

如果查看 migrations 目录的内容,其中已经存在一个名为“1_initial_migration.js”的 JavaScript 部署文件。该文件将 Migrations.sol 部署到 contracts 目录中,并管理它,这使得一系列的智能合约可以正确地迁移。

下面在 migrations 目录中创建一个名为“2_deploy_contracts.js”的部署文件。在部署文件中写入如下内容:

const Adoption = artifacts.require("Adoption");
module.exports = (deployer) => {
deployer.deploy(Adoption);
};

在前面打开的 Truffle 开发控制台上,运行 migrate 命令:

$ truffle(develop)> migrate

如果生成如下输出,表明模拟部署成功完成:

基于以太坊的去中心化宠物商店构建教程_第10张图片

测试智能合约是非常重要的一步。这是因为智能合约中的设计错误和缺陷将与用户的代币(资产)直接相关,可导致对用户利益的严重损害。

智能合约测试主要分为手工测试和自动测试。下面分别介绍这两种测试。

手工测试使用 Ganache 等本地开发环境工具,检查应用的运行情况。这易于理解,因为这些工具实际指向 GUI 中的交易。

本文将跳过对手工测试的介绍。下面介绍自动测试。

在 Truffle 中,可使用 JavaScript 或 Solidity 描述智能合约的自动测试。在本例中,我采用 Solidity 编写。

在所创建的 test 目录中,创建一个名为“TestAdoption.sol”的文件,其中的内容如下:

基于以太坊的去中心化宠物商店构建教程_第11张图片

文件内容略长。我将分解该文件做介绍。

基于以太坊的去中心化宠物商店构建教程_第12张图片

首先,我导入了如下三个合约:

  • Assert.sol:测试期间的各种检查工作。

  • DeployedAddresses.sol:获取在测试期间部署的合约的地址。

  • Adoption.sol:测试智能合约。

创建一个名为“TestAdoption”的合约,并定义变量 adoption。adoption 包含 DeployedAddresses。

在下面给出的 TestAdoption 合约中,我定义了用于测试的函数:

function testUserCanAdoptPet() {
uint returnedId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");  
}

该代码测试 Adoption 合约中定义的 adopt() 函数。如果 adopt() 函数功能正常,它将返回与参数具有同一数值的 petId(即返回值)。

此处将值为 8 的 petId 置入 adopt() 函数,并使用 Assert.equal() 函数确保与 petId 返回值匹配。

function testGetAdopterAddressByPetId() {
address expected = this;
address adopter = adoption.adopters(8);
Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
}

需要测试的是 petId 是否关联了正确的所有者地址。代码测试了宠物 ID 是 8 的所有者的地址是否正确。

顺便提及,变量 this 表示的是当前合约的地址。

function testGetAdopterAddressByPetIdInArray() {
address expected = this;
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}

最后,检查具有所有地址的数组 adopters 是否被正确返回。

属性 memory 并未保存在合约的“存储”中,这意味着它是一个临时记录的值。

现在可以编写测试。我将使用 Truffle Develop 返回测试文件。

$ truffle(develop)> test

如果输出如下,表明测试成功。

基于以太坊的去中心化宠物商店构建教程_第13张图片

目前为止,我们已经完成了智能合约的创建,模拟部署在本地环境的测试区块链中,并测试其是否正常工作。

下面,我们将创建用户界面,在浏览器中实际查看宠物商店。

基本结构已经由 Truffle Box 构建,我们只需在以太坊中添加特性函数。

应用的前端部分位于 src 目录中,我们需要编辑其中的 /src/js/app.js 文件。

下面给出 App 对象的声明,我随后在①到④处添加代码。

基于以太坊的去中心化宠物商店构建教程_第14张图片

基于以太坊的去中心化宠物商店构建教程_第15张图片

下面分别介绍在① ~ ④处添加的源代码。

 ① web3 实例化if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider;
} else {  
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:9545');
}
web3 = new Web3(App.web3Provider);

首先,确保 web3 的实例是“活动”的。如果它是“活动”的,那么使用所创建应用的 web3 对象替换它。如果它并非“活动”的,那么在本地开发环境中创建 web3 对象。

 ② 合约实例化

鉴于我们现在可通过 web3 与“以太坊网络”建立通讯,这时需要实例化所创建的“智能合约”。为实现合约的实例化,我们需要将合约的具体位置以及工作方式告知 web3。

$.getJSON('Adoption.json', function(data) {
var AdoptionArtifact = data;
App.contracts.Adoption = TruffleContract(AdoptionArtifact);
App.contracts.Adoption.setProvider(App.web3Provider);
return App.markAdopted();
});

Truffle 提供了一个有用的软件库,称为“truffle-contract”。该软件库作用于 web3 上,简化了与“智能合约”的联系。例如,truffle-contract 实现模拟部署期间合约信息的同步,无需手工更改部署地址。

Artifact 文件提供了部署地址和 ABI(应用二进制接口,Application Binary Interface)信息。

ABI 表示了合约接口上的信息,即变量、函数、参数等。

在 TruffleContract() 函数中插入 Artifact,并实例化合约。然后设置由 web3 实例化所创建的 App.web3Provider 到合约中。

此外,如果先前已经选定了宠物,那么这时需要调用 markAdopted()。每次智能合约数据发生改变时,都有必要对 UI 进行更新。更新 UI 定义为在③处给出的各种“函数”。

 ③ UI 更新

下面的代码确保宠物状态保持更改,并且 UI 得到了更新。

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);
 });

代码首先在所部署的 Adoption 合约实例上调用 getAdopters() 函数。call() 函数并不更改区块链的状态,它只是读取数据,因此这里无需支付 GAS。

此后,代码检查是否每个 petId 都绑定了一个地址。如果地址存在,就将按钮状态改为“Success”,这样用户不能再次按下按钮。

 ④ 操作 adopt() 函数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);
});
});

在本例中,确认 web3 使用账号无误后,就实际进行交易处理。交易执行通过 adopt() 函数完成,输入参数为一个包含 petId 和账号地址的对象。

之后,使用在③中定义的 markAdopted() 函数,将交易结果在 UI 上以新数据显示。

一旦万事俱备,现在就可以在浏览器中查看上面创建的去中心化应用。

基于以太坊的去中心化宠物商店构建教程_第16张图片

这里需要预先做一些安装工作,因为我们将使用 Chorome 的一个扩展 MetaMask。账号将通过下面给出的“钱包私钥”(Wallet Seed),使用 Truffle Develop 的账号。在执行 Truffle Develop 时,会显示该私钥(它是通用私钥)。

candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

如果使用 MetaMask,可以通过菜单项“Lock”访问如下的屏幕。

基于以太坊的去中心化宠物商店构建教程_第17张图片

为了将 MetaMask 连接到 Truffle Develop 创建的区块链,要将左上位置的“Main Network”改为“Custom RPC”,“Truffle Develop”更改为“http://localhost:9545”,并将显示从“Main Network”更改为“Private Network”。

基于以太坊的去中心化宠物商店构建教程_第18张图片

账号由上面给出的私钥生成,其中应该会具有少许的 100ETH,它们来自于合约部署中消费的 GAS 量。

基于以太坊的去中心化宠物商店构建教程_第19张图片

一旦对 MetaMask 做了如上设置,就可以在终端等处输入下面的命令,启动本地 Web 服务器(鉴于已经 bs-config.json 和 package.json 已经创建,还可以使用 lite-server 软件库)。

$ npm run dev

这样,在浏览器中就能显示如下的去中心化应用。

基于以太坊的去中心化宠物商店构建教程_第20张图片

基于以太坊的去中心化宠物商店构建教程_第21张图片

一旦点击心仪宠物的“adopt”按钮,交易就通过 MetaMask 发出,使用者可以用 ETH 支付宠物购买。

基于以太坊的去中心化宠物商店构建教程_第22张图片

基于以太坊的去中心化宠物商店构建教程_第23张图片

鉴于本文只是给出一个教程,因此内容主要聚焦于使用 Truffle Box 在以太坊中实现的去中心化应用的一些特性。即便读者并不具备详细的以太坊区块链知识,只要能按教程实际动手操作,就可理解去中心化应用的工作机制。



原文发布时间为:2018-03-26
本文作者:分分钟变大神
本文来源:微信公众号-区块链前哨,如需转载请联系原作者。

你可能感兴趣的:(基于以太坊的去中心化宠物商店构建教程)