手把手教创建你的第一个以太智能合约:ETHEREUM PET SHOP(译)

in #cn7 years ago

image

手把手教创建你的第一个以太智能合约:ETHEREUM PET SHOP(译)

原文地址 : http://truffleframework.com/tutorials/pet-shop

译者:lucia3

译者steemit主页:++https://steemit.com/@lucia3++

译者以太地址:++0x2a703d8ae21d5f23d6ffab3a10c62f0a64825867++

如果觉得这个教程对你有用,请不要吝啬打赏哟~

这个系列的教程将会手把手带你搭建你的第一个dapp应用——一个宠物商店的领养追踪系统。

这个教程是为那些有基本的以太链和智能合约知识,懂得一些HTML和JavaScript,但是从来没开发过dapp的人准备的。

注意:如果想要补充以太坊基础,请在继续学习本教程前阅读 Truffle的教程Ethereum Overview

在这个教程里,我们将会学习以下内容:

  • 搭建开发环境(Truffle框架下开发以太坊智能合约)
  • 通过使用Truffle Box创建一个Truffle项目
  • 编写一个智能合约
  • 编译合约以及将合约迁移到区块链上
  • 测试智能合约
  • 创建一个与智能合约交互的UI
  • 在浏览器里使用你创建的dapp





背景

皮特对于使用以太坊技术处理他们店里的宠物领养非常有兴趣。这家店有16只宠物等待领养,这些宠物已经录入了数据库。作为一个概念的初步证明,Pete希望看到一个将一个以太坊地址与一只个宠物关联起来的dapp。

本项目的网站结构和样式已经提供。 我们的工作是为其编写智能合同和前端逻辑。





搭建开发环境

在我们开始之前, 请安装以下内容:

一旦我们安装了这些,我们只需要一个命令来安装Truffle:

npm install -g truffle

要验证Truffle是否正确安装,请在终端上输入truffle version

truffle version

如果您看到错误,请确保您的npm模块已添加到您的路径中。





通过使用Truffle Box创建一个Truffle项目

  1. Truffle会在当前的目录中初始化,所以首先在你选择的开发文件夹中创建一个目录,然后进入这个目录。
mkdir pet-shop-tutorial

cd pet-shop-tutorial
  1. 我们已经为这个pet-shop教程创建了一个特殊的++Truffle Box++,其中包括基本的项目结构以及用户界面的代码。 使用truffle unbox命令解压这个Truffle Box。
truffle unbox pet-shop
> ##### 注意:truffle可以通过几种不同的方式进行初始化。 另一个有用的初始化命令是`truffle init`,它创建一个空的Truffle项目,不包含任何示例合同。 有关更多信息,请参阅[++创建项目文档++](http://truffleframework.com/docs/getting_started/project)。


项目目录结构

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

  • contracts/: 包含了我们的项目的智能合约的源文件(++Solidity++语言开发的)。 在这里有一个重要的合约Migrations.sol,我们稍后再讨论。
  • migrations/: Truffle uses a migration system to handle smart contract deployments. A migration is an additional special smart contract that keeps track of changes.truffle使用迁移系统来处理智能合约部署。 迁移是追踪变化的一种额外的特殊智能合约。
  • test/: contracts包含了对我们智能合约中JavaScript和Solidity的测试
  • truffle.js: Truffle配置文件

pet-shop Truffle Box项目里还有些其它的文件和文件夹,但我们目前不用关注它们。





编写一个智能合约

我们将从编写充当后端逻辑和存储的智能合约开始,来开发我们的dapp。

  1. contracts /目录下创建一个名为Adoption.sol的新文件。
  2. 将以下内容添加到文件中:
pragma solidity ^0.4.4;

contract Adoption {

}

注意事项:

  • 在合同的顶部注明了所需的最低版本:pragma solidity ^0.4.4;pragma意味着“只有编译器关心的附加信息”,而脱字符号(^)表示“指定的版本或更高版本”。

  • 像JavaScript或PHP一样,语句以分号结尾

定义变量

Solidity是一种静态类型的语言,意味着像字符串,整数和数组等数据类型必须被定义。 Solidity有一个称为address的独特类型。 Addresses址是以太坊地址,一个存储为20个字节的值。 以太坊区块链上的每个账户和智能合约都有一个地址,并可以通过此地址发送和接收以太币。

contract Adoption {之后,在下一行添加以下变量。

address[16] public adopters;

注意事项:

  • 我们已经定义了一个变量:adopters。 这是一个以太坊地址数组。 数组包含一种类型,可以有一个固定的或可变的长度。 在这个例子中,类型是address,长度是16。
  • 你还会注意到adopters是公共的。 公共变量具有自动getter方法,但是如果在公共变量是数组的情况下,键是必需的,给定了key的数组只会返回数组中的一个值。 稍后,我们将编写一个函数来返回整个数组,供我们的用户界面使用。

你的第一个函数:领养宠物

首先要让用户可以领养宠物。

  1. 在上面设置的变量声明之后,将下面的函数添加到智能合约中。
// Adopting a pet
function adopt(uint petId) public returns (uint) {
  require(petId >= 0 && petId <= 15);

  adopters[petId] = msg.sender;

  return petId;
}

注意事项:

  • 在Solidity中,必须指定函数参数和输出的类型。 在这种情况下,我们将获取一个petId(整数)并返回一个整数。

  • 首先要检查petId,以确保petId不会超出我们定义的数组范围。 Solidity中的数组起始索引为0,因此ID值将需要介于0和15之间。我们使用require()语句来确保ID在数组范围内。

  • 如果ID在范围内,我们往adopters数组中添加 address。 调用此函数的人员或智能合约的地址由msg.sender表示

  • 最后,我们返回传入的petId作为确认。

你的第二个函数:获取领养人

如上所述,数组getters方法只从给定的键返回一个单一的值。 我们的UI需要更新所有的宠物收养状态,但是调用16次getters方法并不明智。 所以我们下一步是编写一个函数来返回整个数组。

将以下getAdopters()函数添加到智能合约中,在我们上面添加的adopt()函数之后:

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

由于adopters已经声明,我们可以简单地返回它。 确保将返回类型(在这个例子中是adopters的类型)指定为address[16]





编译合约以及将合约迁移到区块链上

现在我们已经编写了我们的智能合约,接下来的步骤是编译合约以及将合约迁移到区块链上。

Truffle有一个内置的开发者控制台,我们称之为Truffle Develop,它生成一个开发区块链,我们可以用来测试部署合同。 它还能够直接从控制台运行Truffle命令。 我们将使用Truffle Develop在本教程中执行我们合同中的大部分操作。

编译

Solidity是一种编译语言,这意味着我们需要将我们的Solidity编译为用于以太坊虚拟机(EVM)执行的字节码。 把它看作是将我们人类可读的“固体”(Solidity)翻译成EVM所理解的东西。

  1. 登陆Truffle Develop。 确保你在包含dapp的目录中执行下列命令。
truffle develop

你会看到一个提示,显示你现在在Truffle Develop中。 下文除非另有说明,否则所有命令都将从此控制台运行。

truffle(develop)>

注意:如果你在Windows上并遇到运行此命令的问题,请参阅有关++解决Windows上的命名冲突++的文档。

  1. 编译dapp
compile

在执行完上面那条命令后,你应该看到类似于下面的输出:

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

注意:如果你没有使用Truffle Develop,这些命令可以在你的终端上使用truffle前缀。 如在编译中,在终端上运行truffle compile
但是,如果你不使用Truffle Develop,你将不得不使用另一个测试区块链,如++TestRPC++

将合约迁移到区块链上

现在我们已经成功编译了我们的合同,现在是时候将它们迁移到区块链了!

迁移是一个部署脚本,旨在改变应用程序合同的状态,将其从一个状态转移到另一个状态。 对于第一次迁移,您可能只是部署新的代码,但随着时间的推移,其他迁移可能会移动数据或用新的代码替换合同。

注意:在Truffle文档中阅读更多关于++迁移++的信息。

你将在migrations /目录中看到一个JavaScript文件:1_initial_migration.js。 这将处理部署Migrations.sol合同以观察后续的智能合同迁移,并确保将来不会对未更改的合同进行双重迁移。

现在我们准备创建我们自己的迁移脚本。

  1. migrations /目录下创建一个名为2_deploy_contracts.js的新文件。

  2. 将以下内容添加到2_deploy_contracts.js文件中:

var Adoption = artifacts.require("Adoption");

module.exports = function(deployer) {
  deployer.deploy(Adoption);
};
  1. 回到我们的控制台,将合同迁移到区块链。
migrate

在执行完上面那条命令后,你应该看到类似于下面的输出:

Using network 'develop'.

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...

您可以按顺序看到正在执行的迁移,然后是每个部署的合同的区块链地址。 (你的地址会有所不同。)

您现在已经写好了您的第一个智能合约,并将其部署到本地运行的测试区块链中。 现在是时候与我们的智能合约进行互动,以确保它符合我们的要求。

测试智能合约

在智能合约测试方面,Truffle非常灵活,因为测试可以用JavaScript或Solidity编写。 在本教程中,我们将在Solidity中编写我们的测试。

  1. test /目录下创建一个名为TestAdoption.sol的新文件。

  2. 将以下内容添加到TestAdoption.sol文件中:

pragma solidity ^0.4.11;

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

contract TestAdoption {
  Adoption adoption = Adoption(DeployedAddresses.Adoption());

}

我们用三个imports开始合同:

  • Assert.sol:使我们在测试中使用的各种断言。 在测试中,一个断言会检查如平等,不平等或判空之类的事情,以便从我们的测试中返回通过/失败。 ++truffle包含的断言的完整列表++
  • DeployedAddresses.sol:运行测试时,Truffle会将正在测试的合约的新实例部署到TestRPC。 这个智能合约获得了已经被部署的合约的地址。
  • Adoption.sol:我们想要测试的智能合约。

注意:
前两个imports是引用自全局Truffle文件,而不是truffle目录。 你不会看到你的test/目录里有truffle目录。

然后我们定义一个包含要测试的智能合约的合同范围的变量,调用DeployedAddresses智能合约来获得它的地址。

测试 adopt()函数

要测试adopt()函数,成功时返回给定的petId。 我们可以通过比较采用的返回值和我们传入的ID来确保返回的ID是正确的。

  1. Adoption的声明之后,在TestAdoption.sol智能合同中添加以下函数:
// Testing the adopt() function
function testUserCanAdoptPet() {
  uint returnedId = adoption.adopt(8);

  uint expected = 8;

  Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
}

注意事项:

  • 我们调用前面声明的智能合约adoption,传入的参数ID为8。
  • 然后我们声明我们预期的函数返回值expected为8。
  • 最后,我们将实际得到的返回值returnedId,期望值expected和失败消息(如果测试未通过,将打印到控制台)作为入参传递给Assert.equal()。

测试根据给定宠物id获得领养人的函数

由于数组只能给定一个键返回一个值,所以我们为整个数组创建了自己的getter。

  1. TestAdoption.sol中先前添加的函数下添加此函数。
// Testing retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() {
  // Expected owner is this contract
  address expected = this;

  // Store adopters in memory rather than contract's storage
  address[16] memory adopters = adoption.getAdopters();

  Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}

请注意adoptersmemory属性。 memory属性告诉Solidity将数据临时存储在内存中,而不是将其保存到合同的存储。 由adopters是一个数组,我们从第一个领养函数的测试中知道我们领养了petId为 8的宠物,所以我们将测试合约地址与数组中的index为8的位置存储的地址进行比较。

执行测试函数

  1. 回到Truffle Develop中,执行下列命令:
test
  1. 如果所有的测试都通过了,你会看到类似这样的控制台输出:
Using network 'develop'.

   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

现在,我们已经创建了智能合约,将其部署到我们的本地测试区块链中,并确认我们可以通过控制台与它进行交互,现在是时候创建一个UI,让Pete有一些东西可以用于他的宠物店!

这个应用程序的前端代码在pet-shop项目目录里。 存在于src /目录中。

本项目的前端不使用构建系统(webpack,grunt等),尽可能简单地开始。 该应用程序的结构已经提供; 我们将专注于编写以太坊特有的函数。 这样,您就可以将这些知识应用到您自己的前端开发中。

初始化 web3

  1. 在文本编辑器中打开/src/js/app.js

  2. 检查文件。 请注意,有一个全局App对象来管理我们的应用程序,在init()中加载宠物数据,然后调用函数initWeb3()++web3 JavaScript++库与以太坊区块链交互。 它可以检索用户帐户,发送交易,与智能合约交互等等。

  3. initWeb3中删除多行注释并将其替换为以下内容:

// Is there is an injected web3 instance?
if (typeof web3 !== 'undefined') {
  App.web3Provider = web3.currentProvider;
} else {
  // If no injected web3 instance is detected, fallback to the TestRPC
  App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
}
web3 = new Web3(App.web3Provider);

注意事项:

  • 首先,我们检查web3实例是否已经存在。 (以太坊浏览器(如++Mist++或者带有++MetaMask++扩展的Chrome)将注入自己的web3实例。)如果注入的web3实例存在,我们将获取它的提供者并使用它创建我们的web3对象。

  • 如果没有web3实例存在,我们将基于我们的本地提供者创建我们的web3对象。 (对于开发环境来说,这种回退很好,但不安全,不适合生产。)

初始化智能合约

现在我们可以通过web3与以太坊互动,我们需要实例化我们的智能合约,以便web3知道在哪里找到它,以及它如何工作。 Truffle有一个库来帮助实现这些——truffle-contract。 它将有关合同的信息与迁移保持同步,因此您不需要手动更改合同的部署地址。

  1. 仍然在/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();
});

注意事项:

  • 首先我们获取我们的智能合约的artifact文件。 artifact是关于我们的合同的信息,例如其部署的地址和应用程序二进制接口(ABI)。 ABI是一个JavaScript对象,定义了如何与契约进行交互,包括变量,函数和参数。

  • 一旦我们在回调中获得了artifact,我们将它们传递给TruffleContract()。 这创建了一个我们可以与之交互的合同实例。

  • 实例化我们的合约之后,我们将合约提供者设置成App.web3Provider,这是之前设置web3提供者时存储的值。

  • 然后我们调用应用程序的markAdopted()函数,标记已经被领养的宠物。 我们把它封装在一个单独的函数中,因为我们需要在更改智能合约的数据时更新UI。

获取已经领养的宠物&更新UI

  1. 仍然在/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或其他错误的值。

  • 一旦找到了一个有相应地址的petId,我们禁用其领养按钮,并将按钮文本改为“成功”,这样用户就知道哪些宠物已经被领养了。

  • 任何错误都被记录到控制台。

处理adopt()函数

  1. 仍然在/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中。 这一次,我们将发送一个交易,而不是一个调用。 交易需要"from"地址,并会产生相关费用。 这个费用是用以太币支付的,被称为gasgas是在智能合约中执行计算和/或存储数据产生的费用。
    我们通过执行adopt()函数来发送交易,函数的入参是宠物ID和一个包含我们先前存储在账户中的账户地址的对象。
  • 发送交易的返回是一个交易对象。 如果没有错误,我们继续调用我们的markAdopted()函数来同步UI和我们新存储的数据。

在浏览器里使用你创建的dapp

现在你可以开始使用你的dapp啦!

安装和配置MetaMask

在浏览器中与我们的dapp交互的最简单的方法是通过MetaMask,Chrome的扩展插件。

  1. 在您的浏览器中安装MetaMask。

  2. 安装完成后,您会看到地址栏旁边的MetaMask狐狸图标。 点击图标,你会看到这个屏幕出现




    image



  3. 点击接受接受隐私声明。

  4. 那么你会看到使用条款。 阅读它们,滚动到底部,然后单击接受。




    image



  5. 现在你会看到最初的MetaMask屏幕。 点击Import Existing DEN




    image




    6.在标有“Wallet Seed”的框中,输入登陆Truffle Develop时显示的助记词:

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

警告:请勿在以太网主网络(mainnet)上使用此助记符。 如果您将ETH发送到由此助记符生成的任何帐户,您将会失去所有您发送到该地址上的ETH

在下面输入密码,然后单击 OK




image



  1. 现在我们需要将MetaMask连接到由Truffle Develop创建的区块链。 点击显示“Main Network”的菜单并选择Custom RPC




    image



  2. 在标题为“New RPC URL”的框中输入http:// localhost:9545,然后单击Save




    image



  3. 点击"Settings"旁边的向左箭头关闭页面并返回到“帐户”页面。Truffle Develop


    创建的每个账户都有100个eth。 你会注意到它在第一个账户上稍微少一些,因为当合同本身被部署时使用了一些gas。




    image




    现在配置完成。



安装和配置lite-server

我们现在可以启动一个本地Web服务器并使用dapp。 我们正在使用lite-server库来为我们的静态文件提供服务。 这是pet-shop Truffle Box里已经有的服务器,但让我们来看看它是如何工作的。

  1. 在文本编辑器(在项目的根目录下)中打开bs-config.json并检查其内容:
{
  "server": {
    "baseDir": ["./src", "./build/contracts"]
  }
}

这告诉lite-server哪些文件包含在我们的基础目录中。 我们为我们的网站文件添加./src目录,为合同工件添加./build/contracts目录。




我们还在项目的根目录下的package.json文件中的scripts对象中添加了一个dev命令。 scripts对象允许我们将控制台命令别名为单个npm命令。 在这种情况下,我们只是做一个单一的命令,但可能有更复杂的配置。 比如像:

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

这告诉npm在我们从控制台执行npm run dev的时候运行我们的lite-server的本地安装。

使用dapp

  1. 启动本地Web服务器:
npm run dev

开发服务器将启动并自动打开一个新的浏览器选项卡,其中包含您的dapp。




image




Pete's Pet Shop



  1. 要使用dapp,请点击您选择的宠物上的 Adopt 按钮。

  2. 系统将自动提示您通过MetaMask批准交易。 点击 Submit 以批准交易。




    image




    Adoption transaction review



  3. 就像我们期望的一样,你会看到被宠物宠物改变的旁边的按钮,说“成功”,并被禁用,因为宠物已经被领养。




    image




    Adoption success



注意:如果按钮不会自动改变为"Success",刷新浏览器中的应用程序应该会触发它。




image





MetaMask transaction




恭喜! 你已经迈出了一大步,成为一个成熟的dapp开发者。 为了在本地进行开发,您可以使用所有工具开始制作更高级的绘图。 如果您希望让您的dapp能够让其他人使用,请继续关注我们将来部署到Ropsten测试网络的教程。
(完)

Sort:  

推荐两个区块链、以太坊开发DApp的实战教程:

  1. 适合区块链新手的以太坊DApp开发:
    http://xc.hubwiz.com/course/5a952991adb3847553d205d1?affid=20180412steemit

  2. 用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商平台:
    http://xc.hubwiz.com/course/5abbb7acc02e6b6a59171dd6/?affid=20180412steemit

Coin Marketplace

STEEM 0.25
TRX 0.20
JST 0.037
BTC 93400.01
ETH 3410.75
USDT 1.00
SBD 3.80