手把手教创建你的第一个以太智能合约: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项目
- Truffle会在当前的目录中初始化,所以首先在你选择的开发文件夹中创建一个目录,然后进入这个目录。
mkdir pet-shop-tutorial
cd pet-shop-tutorial
- 我们已经为这个
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。
- 在
contracts /
目录下创建一个名为Adoption.sol
的新文件。 - 将以下内容添加到文件中:
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的数组只会返回数组中的一个值。 稍后,我们将编写一个函数来返回整个数组,供我们的用户界面使用。
你的第一个函数:领养宠物
首先要让用户可以领养宠物。
- 在上面设置的变量声明之后,将下面的函数添加到智能合约中。
// 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所理解的东西。
- 登陆Truffle Develop。 确保你在包含dapp的目录中执行下列命令。
truffle develop
你会看到一个提示,显示你现在在Truffle Develop中。 下文除非另有说明,否则所有命令都将从此控制台运行。
truffle(develop)>
注意:如果你在Windows上并遇到运行此命令的问题,请参阅有关++解决Windows上的命名冲突++的文档。
- 编译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
合同以观察后续的智能合同迁移,并确保将来不会对未更改的合同进行双重迁移。
现在我们准备创建我们自己的迁移脚本。
在
migrations /
目录下创建一个名为2_deploy_contracts.js
的新文件。将以下内容添加到
2_deploy_contracts.js
文件中:
var Adoption = artifacts.require("Adoption");
module.exports = function(deployer) {
deployer.deploy(Adoption);
};
- 回到我们的控制台,将合同迁移到区块链。
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中编写我们的测试。
在
test /
目录下创建一个名为TestAdoption.sol
的新文件。将以下内容添加到
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是正确的。
- 在
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。
- 在
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.");
}
请注意adopters
的memory
属性。 memory
属性告诉Solidity将数据临时存储在内存中,而不是将其保存到合同的存储。 由adopters
是一个数组,我们从第一个领养函数的测试中知道我们领养了petId
为 8的宠物,所以我们将测试合约地址与数组中的index
为8的位置存储的地址进行比较。
执行测试函数
- 回到Truffle Develop中,执行下列命令:
test
- 如果所有的测试都通过了,你会看到类似这样的控制台输出:
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
在文本编辑器中打开
/src/js/app.js
。检查文件。 请注意,有一个全局
App
对象来管理我们的应用程序,在init()
中加载宠物数据,然后调用函数initWeb3()
。 ++web3 JavaScript++库与以太坊区块链交互。 它可以检索用户帐户,发送交易,与智能合约交互等等。从
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
。 它将有关合同的信息与迁移保持同步,因此您不需要手动更改合同的部署地址。
- 仍然在
/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
- 仍然在
/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()
函数
- 仍然在
/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"地址,并会产生相关费用。 这个费用是用以太币支付的,被称为gas
。gas
是在智能合约中执行计算和/或存储数据产生的费用。
我们通过执行adopt()
函数来发送交易,函数的入参是宠物ID和一个包含我们先前存储在账户中的账户地址的对象。 - 发送交易的返回是一个交易对象。 如果没有错误,我们继续调用我们的
markAdopted()
函数来同步UI和我们新存储的数据。
在浏览器里使用你创建的dapp
现在你可以开始使用你的dapp啦!
安装和配置MetaMask
在浏览器中与我们的dapp交互的最简单的方法是通过MetaMask,Chrome的扩展插件。
在您的浏览器中安装MetaMask。
安装完成后,您会看到地址栏旁边的MetaMask狐狸图标。 点击图标,你会看到这个屏幕出现
点击接受接受隐私声明。
那么你会看到使用条款。 阅读它们,滚动到底部,然后单击接受。
现在你会看到最初的MetaMask屏幕。 点击Import Existing DEN。
6.在标有“Wallet Seed”的框中,输入登陆Truffle Develop时显示的助记词:
candy maple cake sugar pudding cream honey rich smooth crumble sweet treat
警告:请勿在以太网主网络(mainnet)上使用此助记符。 如果您将ETH发送到由此助记符生成的任何帐户,您将会失去所有您发送到该地址上的ETH!
在下面输入密码,然后单击 OK。
- 现在我们需要将MetaMask连接到由Truffle Develop创建的区块链。 点击显示“Main Network”的菜单并选择Custom RPC。
- 在标题为“New RPC URL”的框中输入
http:// localhost:9545
,然后单击Save。
- 点击"Settings"旁边的向左箭头关闭页面并返回到“帐户”页面。Truffle Develop
创建的每个账户都有100个eth。 你会注意到它在第一个账户上稍微少一些,因为当合同本身被部署时使用了一些gas。
现在配置完成。
安装和配置lite-server
我们现在可以启动一个本地Web服务器并使用dapp。 我们正在使用lite-server
库来为我们的静态文件提供服务。 这是pet-shop
Truffle Box里已经有的服务器,但让我们来看看它是如何工作的。
- 在文本编辑器(在项目的根目录下)中打开
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
- 启动本地Web服务器:
npm run dev
开发服务器将启动并自动打开一个新的浏览器选项卡,其中包含您的dapp。
Pete's Pet Shop
要使用dapp,请点击您选择的宠物上的 Adopt 按钮。
系统将自动提示您通过MetaMask批准交易。 点击 Submit 以批准交易。
Adoption transaction review
就像我们期望的一样,你会看到被宠物宠物改变的旁边的按钮,说“成功”,并被禁用,因为宠物已经被领养。
Adoption success
注意:如果按钮不会自动改变为"Success",刷新浏览器中的应用程序应该会触发它。
MetaMask transaction
恭喜! 你已经迈出了一大步,成为一个成熟的dapp开发者。 为了在本地进行开发,您可以使用所有工具开始制作更高级的绘图。 如果您希望让您的dapp能够让其他人使用,请继续关注我们将来部署到Ropsten测试网络的教程。
(完)
推荐两个区块链、以太坊开发DApp的实战教程:
适合区块链新手的以太坊DApp开发:
http://xc.hubwiz.com/course/5a952991adb3847553d205d1?affid=20180412steemit
用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商平台:
http://xc.hubwiz.com/course/5abbb7acc02e6b6a59171dd6/?affid=20180412steemit