基于以太坊的去中心化宠物商店构建教程-阿里云开发者社区

开发者社区> 开发与运维> 正文

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

简介:

3b89a10991212c9135fddbbfd2c45308868badb5

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

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

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

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

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

5b854d6c687ee1f6f815c499db975ad93485d3f3

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

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

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

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

0b7beab5470cf4a9887165482ed75c73d2752ce5

41eca0260deab7953aae1e3bea8e859b55dbc24e

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

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

  • 设置开发环境;

  • 使用 Truffle Box 创建 Truffle 项目;

  • 描述智能合约;

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

  • 测试智能合约;

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

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

27705bc4cdcc08a2d4ec18bf81cbea298d54b7b5

首先准备使用 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”目录中建立如下图所列的文件和目录:

967642724d97935efd79c10254efc05151364af7

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

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

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

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

  • truffle.js:Truffle 配置文件。

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

9d1e863c86f7cd2e34323bbe1c235bad6a96164d

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

9129ed46013a4898094a5ef51c84ac5f74b5fc1b

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

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 绑定在一起”。

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

e4fea3762834044a9c972a82bf4f6ab484dd02ef

编译将以编程语言编写的源代码转译为机器可直接执行的机器语言。换句话说,本例中就是将 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

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

672698a0004c33b46d72d153829422bf20fda7c6

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

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

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

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

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

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

194450bf7127b9d0ca3756089bbfe0950e1fecd6

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

80b501b7ec9b2b9f4dee929c97648f9758cdff70

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

  • 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

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

f5d76114fa9d54b98d0c807ee557abcfb6e6b390

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

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

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

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

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

c4108982c7b7de1734b81bd88cadf2e6133510e9

f417e19d495d35b2ecac086329248c605c403d4a

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

 ① 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 上以新数据显示。

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

def533688209881bc04eea4809882f118c7b0a16

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

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

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

b8aa624627f178eb7ab480b5819e87459df97f6b

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

8b8a0c7ca49d86c9befc3d94a031d534bda0e723

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

c492df57ddb4858d80502617a96ae10b6beb6af1

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

$ npm run dev

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

dedce2e76f7502c61c686003605b659969cd7fcf

170ee517f5e192b4d19d1b446e83ac56874bfbb3

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

bb37f0d2994eedd59cc400eea20e0bca070363d4

1ea58728691648ec5c53f641c8cacd68c21c8140

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



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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章