以太坊宠物商店 - 记录第一个Dapp

背景

记录以前练习的第一个Dapp宠物商店,使用以太坊作为处理宠物收养的方式。该商店在特定时间可容纳16只宠物,并且他们已经拥有宠物数据库。所以我们做一个将以太坊地址与宠物相关联的dapp,官方提供了网站结构和样式。我们只需编写智能合约和执行前端逻辑。首先我的思路是:

设置开发环境
使用Truffle创建项目
编写智能合约
编译和迁移智能合约
测试智能合约
创建用户界面以与智能合约进行交互
在浏览器中与dapp交互

Dapp简介

Dapp 是 Decentralized Application 的简称,即去中心化应用。在某种程度上,比特币可以说是出现的第一个 Dapp,因为它是完全开源的,为贡献者提供奖励回报,不受一个中央机构的控制,并使用区块链作为支撑技术。 区块链作为一个基础设施,提供了分布式的去中心化可信数据库,人们可以基于此,可以开发各种应用,适用于不同的场景。 简单来说,Dapp和普通的 App 原理一样,除了他们是完全去中心化的,由类似以太坊网络本身自己的节点来运作的 Dapp,不依赖于任何中心化的服务器,Dapp 是去中心化的,可以完全自动地运行。 目前 Dapp 通常指代基于以太坊或者 EOS 上的智能合约开发的相关应用。
以太坊宠物商店 - 记录第一个Dapp_第1张图片

Dapp运行原理:Dapp 底层区块链开发平台就好比手机的 iOS 和 Android 系统,是各种 Dapp 的底层生态环境,Dapp 就是底层区块链平台生态上衍生的各种分布式应用,也是区块链世界中的基础服务提供方,Dapp于区块链,就好比 app之于 iOS 和 Android。

设置开发环境

在我们开始之前有一些环境需要安装。

  • 首先安装的是Node.js v8+ LTS and npm,它可以让JavaScript 运行在服务端的开发平台,在前端交互的地方会使用到,再根据自己的电脑版本选择合适的下载。
    以太坊宠物商店 - 记录第一个Dapp_第2张图片
  • 然后是git也是必须的,他是一个开源的分布式版本控制系统。
    以太坊宠物商店 - 记录第一个Dapp_第3张图片
  • 然后就是我们的truffle,可以直接在命令行里面安装
npm install -g truffle

要验证Truffle是否已正确安装,可以输入truffle version查看版本。
然后就是下载一个测试链工具Ganache,他的前身是TestRPC用于以太坊开发的个人区块链,我们主要使用它来部署合同,并且开发应用程序和运行测试。如果想在没有图形界面的环境中进行开发,可以使用Truffle Develop,这是Truffle的内置个人区块链,在命令行输入ganache-cli我们也可以看到可用的账户和秘钥,ganache默认创建10个测试账户,每个账户里面也会有一些余额
以太坊宠物商店 - 记录第一个Dapp_第4张图片

使用truffle创建项目

1、用Truffle在当前目录中初始化,在文件夹中创建一个目录,然后把项目移进去。

mkdir pet-shop-tutorial
cd pet-shop-tutorial

2、使用现成的的Truffle Boxpet-shop框架,其中包括基本的项目结构以及用户界面的代码。用truffle unbox命令解压缩这个框架到我们的文件夹下。

truffle unbox pet-shop

注意:truffle框架可以通过几种不同的方式初始化。另一个的初始化命令是truffle init,它创建一个空的Truffle项目,但不包含示例合同。

3、默认的Truffle目录结构包含以下内容:

  • contracts/:包含我们的智能合约的Solidity源文件。这里有一个迁移合约Migrations.sol
  • migrations/:Truffle使用迁移系统来处理智能合约部署。迁移是一种额外的特殊智能合约,可以跟踪变化。
  • test/:包含智能合约的JavaScript和Solidity测试
  • truffle-config.js:truffle配置文件

写智能合约

1、我们将通过编写充当后端逻辑和存储的智能合约来启动dapp。这里推荐一个在线的编译器remix可以用来测试智能合约。创建一个名为一个新的文件Adoption.sol在contracts/目录中。将以下内容添加到文件中:

pragma solidity ^0.5.0;
contract Adoption {

}

需要注意的事项:
合同顶部标明了所需的最低Solidity版本:pragma solidity ^ 0.5.0;。该pragma命令表示“ 仅编译器关心的附加信息 ”,而插入符号(^)表示“ 指示的版本或更高 ”。
与JavaScript或PHP一样,语句以分号结束。

2、Solidity是一种静态类型语言,意味着必须定义字符串,整数和数组等数据类型。Solidity有一种称为地址的独特类型。地址是以太坊地址,存储为20字节值。以太坊区块链上的每个帐户和智能合约都有一个地址,可以向此地址发送和接收以太网。之后添加一个地址变量

address[16] public adopters;

需要注意的事项:
我们定义了一个变量:adopters。这是以太坊地址的数组。数组包含一种类型,可以具有固定或可变长度。在这种情况下,类型是address和长度是16。而且adopters是public类型,公共变量具有getter方法。

3、第一个功能:获取宠物
在我们上面设置的变量声明之后,将用户提出采用请求的相关函数添加到智能合约中。

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

需要注意的事项:
在Solidity中,必须指定函数参数和输出的类型。在这种情况下,我们将接受一个petId(整数)并返回一个整数。我们正在检查以确保petId在我们的adopters阵列范围内。Solidity中的数组从0开始索引,因此ID值必须介于0到15之间。我们使用该require()语句确保ID在范围内。如果ID在范围内,我们然后添加调用我们的adopters数组的地址。调用此函数的人或智能合约的地址表示为msg.sender。最后,我们将petId提供的内容作为确认返回。
4、第二个功能:搜索adopter()
数组getter仅从给定键返回单个值。我们的UI需要更新所有宠物采用状态,但进行16次API调用比较麻烦。所以写一个函数来返回整个数组。
要注意的事项:

// Retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
  return adopters;
}

编译和迁移智能合约

Solidity是一种编译语言,这意味着我们需要将我们的Solidity编译为字节码,以便执行以太坊虚拟机(EVM)。可以把它想象成将人类可读的Solidity翻译成EVM理解的东西。
1、在终端中,确保位于包含dapp的目录的根目录中并键入:

truffle compile

会有如下显示:
在这里插入图片描述
2、成功编译了合同后,是时候将它们迁移到区块链了!
迁移是一种部署脚本,用于更改应用程序合同的状态,将其从一个状态移动到另一个状态。对于第一次迁移,您可能只是部署新代码,但随着时间的推移,其他迁移可能会移动数据或用新代码替换合同。在migrations/目录中看到一个JavaScript文件:1_initial_migration.js。这将处理部署Migrations.sol合同以观察后续智能合约迁移,并确保我们不会在以后重复迁移未更改的合同。
现在我们准备创建自己的迁移脚本了。
然后创建一个名为一个新的文件2_deploy_contracts.js在migrations/目录中。
将以下内容添加到2_deploy_contracts.js文件中:

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

在我们将合同迁移到区块链之前,我们需要运行在端口7545上本地运行的区块链。
以太坊宠物商店 - 记录第一个Dapp_第5张图片
4、回到我们的终端,将合同迁移到区块链。

truffle migrate

会有如下显示
以太坊宠物商店 - 记录第一个Dapp_第6张图片
在Ganache中,请注意区块链的状态已发生变化。区块链现在显示以前的当前块0现在是4。接下来我们将智能合约进行互动,以确保它能够满足我们的需求。
在这里插入图片描述

测试智能合约

1、Truffle在智能合约测试方面非常灵活,因为测试可以用JavaScript或Solidity编写,我们先创建一个名为一个新的文件TestAdoption.sol在test/目录中。然后将以下内容添加到TestAdoption.sol文件中:

pragma solidity ^0.5.0;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

contract TestAdoption {
 // The address of the adoption contract to be tested
 Adoption adoption = Adoption(DeployedAddresses.Adoption());

 // The id of the pet that will be used for testing
 uint expectedPetId = 8;

 //The expected owner of adopted pet is this contract
 address expectedAdopter = address(this);

}

我们用3种import执行合同:
Assert.sol:给我们在测试中使用各种断言。在测试中,断言检查诸如平等,不平等或空类型之类的事情,以便从我们的测试中返回通过或者失败。
DeployedAddresses.sol:运行测试时,Truffle会将正在测试的合同的新实例部署到区块链。这个智能合约获得已部署合同的地址。
Adoption.sol:我们想要测试的智能合约。
注意:前两个导入的是指全局Truffle文件,而不是truffle目录文件。在truffle目录中看不到test/目录。
然后我们定义三个合约范围的变量:
首先,一个包含要测试的DeployedAddresses智能合约,调用智能合约来获取其地址。
第二,将用于测试采用的宠物的id。
第三,TestAdoption合同将发送交易,我们将预期的采用者地址设置为此,这是一个获得当前合约地址的合约范围变量。

2、测试adopt()函数
要测试该adopt()功能,请记住,一旦成功,它将返回给定的petId。我们可以通过比较adopt()传入的ID 的返回值来确保返回ID并确认它是正确的。
TestAdoption.sol声明后,在智能合约中添加以下功能Adoption:

// Testing the adopt() function
function testUserCanAdoptPet() public {
  uint returnedId = adoption.adopt(expectedPetId);
  Assert.equal(returnedId, expectedPetId, "Adoption of the expected pet should match what is returned.");
}

需要注意的事项:
我们将之前声明的智能合约称为ID expectedPetId。
最后,我们传递实际值,期望值和失败消息(如果测试未通过则将其打印到控制台)Assert.equal()。
3、测试单个宠物主人的检索
记住上面公共变量有自动getter方法,我们可以检索上面的采用测试存储的地址。存储的数据将在我们的测试期间持续存在,因此我们对expectedPetId上面的pet的采用可以通过其他测试来检索。
在以前添加的功能下面添加此功能TestAdoption.sol。


// Testing retrieval of a single pet's owner
function testGetAdopterAddressByPetId() public {
  address adopter = adoption.adopters(expectedPetId);

  Assert.equal(adopter, expectedAdopter, "Owner of the expected pet should be this contract");
}

4、测试所有宠物主人的检索
由于数组只能在给定单个键的情况下返回单个值,因此为整个数组创建了getter。在TestAdoption.sol中添加下面代码。

// Testing retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() public {
  // Store adopters in memory rather than contract's storage
  address[16] memory adopters = adoption.getAdopters();
  Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract");
}

运行测试

然后回到终端,运行测试truffle:

truffle test

如果所有测试都通过,您将看到与此类似的控制台输出:
以太坊宠物商店 - 记录第一个Dapp_第7张图片

创建用户界面以与智能合约进行交互

现在我们已经创建了智能合约,将其部署到我们的本地测试区块链并确认我们可以通过控制台与它进行交互,是时候创建一个UI,以便Pete可以为他的宠物商店使用它!包含在pet-shopTruffle Box中的是该应用程序前端的代码。该代码存在于src/目录中。前端不使用构建系统(webpack,grunt等)尽可能容易上手。应用程序的结构已经存在; 我们将填补以太坊独有的功能。这样,您就可以掌握这些知识并将其应用到您自己的前端开发中。
1、实例化web3
/src/js/app.js在文本编辑器中打开检查文件。从内部删除多行注释initWeb3并将其替换为以下内容:

// Modern dapp browsers...
if (window.ethereum) {
  App.web3Provider = window.ethereum;
  try {
    // Request account access
    await window.ethereum.enable();
  } catch (error) {
    // User denied account access...
    console.error("User denied account access")
  }
}
// Legacy dapp browsers...
else if (window.web3) {
  App.web3Provider = window.web3.currentProvider;
}
// If no injected web3 instance is detected, fall back to Ganache
else {
  App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3Provider);

需要注意的事项:
首先,我们检查是否使用最新dapp浏览器或更新版本的MetaMask,其中将ethereum提供程序注入到window对象中。如果是这样,我们使用它来创建我们的web3对象,但我们还需要明确请求访问帐户ethereum.enable()。
如果该ethereum对象不存在,我们将检查注入的web3实例。如果它存在,则表示我们正在使用较旧的dapp浏览器(如Mist或较旧版本的MetaMask)。如果是这样,我们得到它的提供者并使用它来创建我们的web3对象。
如果没有注入web3实例,我们将根据本地提供程序创建web3对象。(这种后备适用于开发环境,但不安全且不适合生产。)
2、实例化合同
既然我们可以通过web3与以太坊进行交互,我们需要实例化我们的智能合约,以便web3知道在哪里找到它以及它是如何工作的。Truffle有一个图书馆来帮助这个叫做truffle-contract。它使合同信息与迁移保持同步,因此您无需手动更改合同的部署地址。在/src/js/app.js从内部删除多行注释initContract并将其替换为以下内容:

$.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();
});

我们首先检索智能合约的工件文件。工件是有关我们合同的信息,例如其部署的地址和应用程序二进制接口(ABI)。ABI是一个JavaScript对象,用于定义如何与契约交互,包括其变量,函数及其参数。一旦我们在回调中获得了工件,我们就会将它们传递给TruffleContract()。这将创建我们可以与之交互的合同实例。在我们的合同实例化的情况下,我们使用App.web3Provider我们之前在设置web3时存储的值来设置其web3提供程序。然后我们调用该应用程序的markAdopted()功能,以防任何宠物已经从之前的访问中采用。我们已将其封装在一个单独的函数中,因为我们需要在更改智能合约数据时更新UI。获取被采用的宠物并更新UI仍在/src/js/app.js,删除多行注释markAdopted并将其替换为以下内容:

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()该实例。然后adoptionInstance在智能合约调用之外声明变量,以便我们可以在最初检索它之后访问该实例。使用call()允许我们从区块链中读取数据而无需发送完整的事务,这意味着我们不必花费任何以太币。在调用之后getAdopters(),我们遍历所有这些函数,检查是否为每只宠物存储了一个地址。由于数组包含地址类型,因此以太坊使用16个空地址初始化数组。这就是我们检查空地址字符串而不是null或其他falsey值的原因。一旦petId找到具有相应地址的地址,我们将禁用其采用按钮并将按钮文本更改为“成功”,以便用户获得一些反馈。任何错误都会记录到控制台。
在/src/js/app.js,删除多行注释handleAdopt并将其替换为以下内容:

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

需要注意的事项:
我们使用web3来获取用户的帐户。在错误检查后的回调中,然后选择第一个帐户我们像上面那样获得部署的合同并存储实例adoptionInstance。但这一次,我们将发送一个交易而不是一个简单调用。交易需要从地址获取相关的成本。支付的这笔费用称为gas。gas成本是在智能合约中执行计算和存储数据的费用。我们通过执行adopt()包含宠物ID和包含我们之前存储的帐户地址的对象的函数来发送交易account。发送事务的结果是事务对象。如果没有错误,我们继续调用markAdopted()函数来将UI与我们新存储的数据同步。

在浏览器中与dapp交互

在浏览器中与dapp交互的最简单方法是通过Chrome和Firefox的浏览器扩展安装MetaMask。安装后,浏览器中的选项卡应打开,显示以下内容:
以太坊宠物商店 - 记录第一个Dapp_第8张图片
单击之后应该会看到初始的MetaMask屏幕。点击“ 导入钱包”
以太坊宠物商店 - 记录第一个Dapp_第9张图片
接下来,应该看到一个请求匿名分析的屏幕。选择拒绝或同意。
以太坊宠物商店 - 记录第一个Dapp_第10张图片
在其下方输入密码,然后单击“ 确定”。
以太坊宠物商店 - 记录第一个Dapp_第11张图片
如果一切顺利,MetaMask应显示以下屏幕。单击全部完成。
以太坊宠物商店 - 记录第一个Dapp_第12张图片
现在我们需要将MetaMask连接到Ganache创建的区块链。单击显示“主网络”的菜单,然后选择自定义RPC。
以太坊宠物商店 - 记录第一个Dapp_第13张图片
在标题为“新网络”的框中输入http://127.0.0.1:7545并单击“ 保存”。
以太坊宠物商店 - 记录第一个Dapp_第14张图片
顶部的网络名称将切换为say http://127.0.0.1:7545。单击右上角的X以关闭“设置”并返回“帐户”页面。
由Ganache创建的每个帐户都有100个以太网。然而第一个帐户的价格略低,因为在合同本身部署和运行测试时会使用一些gas。
以太坊宠物商店 - 记录第一个Dapp_第15张图片
我们使用本地测试的ganache加metamask进行测试,首先打开ganache查看本地网络号主要查看的rpc地址。
在这里插入图片描述
然后打开metamask,点击Import Account。
在这里插入图片描述
以太坊宠物商店 - 记录第一个Dapp_第16张图片
然后选择密钥加入,密钥在ganache中查看,选择第一个查看密钥填入。

以太坊宠物商店 - 记录第一个Dapp_第17张图片

安装和配置lite-server

我们现在可以启动本地Web服务器并使用dapp。使用lite-server库来提供静态文件。将bs-config.json在文本编辑器中打开(在项目的根目录中)并检查内容:

{
  "server": {
    "baseDir": ["./src", "./build/contracts"]
  }
}

这包括了lite-server在基目录中包含哪些文件。
我们还在项目根目录中的文件中添加了一个dev命令。该对象允许我们将控制台命令别名为单个npm命令。在这种情况下,我们只是执行一个命令,但也有可能有更复杂的配置。代码如下:

scriptspackage.jsonscripts

"scripts": {
  "dev": "lite-server",
  "test": "echo \"Error: no test specified\" && exit 1"
},

启动本地Web服务器:

npm run dev

以太坊宠物商店 - 记录第一个Dapp_第18张图片
开发服务器将启动并自动打开包含dapp的新浏览器选项卡。
以太坊宠物商店 - 记录第一个Dapp_第19张图片
出现MetaMask弹出窗口后,允许Pete’s Pet Shop连接到我们的MetaMask钱包。
以太坊宠物商店 - 记录第一个Dapp_第20张图片
使用dapp,单击您选择的宠物上的Adopt按钮。然后MetaMask会自动提示您批准该交易。单击“ 提交”以批准该事务。
以太坊宠物商店 - 记录第一个Dapp_第21张图片
在MetaMask中,您将看到所有列出的交易:
以太坊宠物商店 - 记录第一个Dapp_第22张图片
我们还会在交易部分下看到Ganache中列出的相同交易,到这就算基本完成了。

你可能感兴趣的:(区块链)