Eth101:基于以太坊的智能合约应用示例:PET SHOP TUTORIAL (译)
原文地址 : http://truffleframework.com/tutorials/pet-shop
这个系列的教程将会手把手带你搭建你的第一个dapp应用——一个宠物商店的领养追踪系统。
这个教程是为那些有基本的以太链和智能合约知识,懂得一些HTML和JavaScript,但是从来没开发过dapp的人准备的。
注意:如果想要补充以太坊基础,请在继续学习本教程前阅读 Truffle的教程Ethereum Overview
在这个教程里,我们将会学习以下内容:
- 搭建开发环境(Truffle框架下开发以太坊智能合约)
- 通过使用Truffle Box创建一个Truffle项目
- 编写一个智能合约
- 编译合约以及将合约迁移到区块链上
- 测试智能合约
- 创建一个与智能合约交互的UI
- 在浏览器里使用你创建的dapp
背景
皮特对于使用以太坊技术处理他们店里的宠物领养非常有兴趣。这家店有16只宠物等待领养,这些宠物已经录入了数据库。作为一个概念的初步证明,Pete希望看到一个将一个以太坊地址与一只个宠物关联起来的dapp。
本项目的网站结构和样式已经提供。 我们的工作是为其编写智能合同和前端逻辑。
搭建开发环境
在我们开始之前, 请安装以下内容:
++Node.js v6+ LTS and npm (comes with Node)++
++Git++
一旦我们安装了这些,我们只需要一个命令来安装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项目,不包含任何示例合同。 有关更多信息,请参阅++创建项目文档++。
项目目录结构
默认的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测试网络的教程。
(完)
Exclusive 30 days free upvotes to your every new post. No need to send any kinds of steem or sbd its full free service. we have paid service too so please check them too. Active the free upvote service and learn more about it here : http://www.steemitfollowup.cf
Congratulations @spacetiller, you have decided to take the next big step with your first post! The Steem Network Team wishes you a great time among this awesome community.
The proven road to boost your personal success in this amazing Steem Network
Do you already know that awesome content will get great profits by following these simple steps that have been worked out by experts?
Congratulations @spacetiller! You have completed some achievement on Steemit and have been rewarded with new badge(s) :
You published your First Post
Award for the number of upvotes
Click on any badge to view your own Board of Honor on SteemitBoard.
For more information about SteemitBoard, click here
If you no longer want to receive notifications, reply to this comment with the word
STOP