默认的Truffle目录结构包含以下内容:
contract/:包含我们智能合约的Solidity源文件。这里有一个名为Migrations.sol的重要合约,我们稍后会谈到。migrations/:Truffle使用迁移系统来处理智能合约部署。迁移是一种额外的特殊智能合约,用于跟踪更改。test/:包含我们智能合约的JavaScript和Solidity测试truffle-config.js:配置文件
宠物店TruffleBox里面有额外的文件和文件夹,但我们暂时不用担心这些。
我们将通过编写充当后端逻辑和存储的智能合约来启动我们的dapp。
在contracts/目录中创建一个名为Adoption.sol的新文件。
将以下内容添加到文件中:
pragmasolidity^0.5.0;contractAdoption{}注意事项:
所需的最低版本的Solidity在合同顶部注明:pragmasolidity^0.5.0;。pragma命令表示“只有编译器关心的附加信息”,而插入符号(^)表示“指示的版本或更高版本”。与JavaScript或PHP一样,语句以分号结尾。
Solidity是一种静态类型语言,这意味着必须定义字符串、整数和数组等数据类型。Solidity有一种独特的类型,称为地址。地址是以太坊地址,存储为20字节值。以太坊区块链上的每个账户和智能合约都有一个地址,并且可以向该地址发送和接收以太币。
在下一行添加以下变量contractAdoption{.
address[16]publicadopters;注意事项:
我们定义了一个变量:采用者。这是一组以太坊地址。数组包含一种类型,并且可以具有固定或可变长度。在这种情况下,类型是地址,长度是16。
您还会注意到采用者是公开的。公共变量具有自动getter方法,但在数组的情况下,键是必需的,并且只会返回一个值。稍后,我们将编写一个函数来返回整个数组以在我们的UI中使用。
让我们允许用户提出采用请求。
//Adoptingapetfunctionadopt(uintpetId)publicreturns(uint){require(petId>=0&&petId<=15);adopters[petId]=msg.sender;returnpetId;}注意事项:
在Solidity中,必须指定函数参数和输出的类型。在这种情况下,我们将接收一个petId(整数)并返回一个整数。
我们正在检查以确保petId在我们的采用者数组的范围内。Solidity中的数组从0开始索引,因此ID值需要在0到15之间。我们使用require()语句来确保ID在范围内。
如果ID在范围内,然后我们将调用的地址添加到我们的采用者数组中。调用此函数的人或智能合约的地址由msg.sender表示。
最后,我们返回作为确认提供的petId。
如上所述,数组getter仅从给定键返回单个值。我们的UI需要更新所有宠物的收养状态,但是进行16次API调用并不理想。所以我们的下一步是编写一个函数来返回整个数组。
在我们上面添加的adopt()函数之后,将以下getAdopters()函数添加到智能合约中:
//RetrievingtheadoptersfunctiongetAdopters()publicviewreturns(address[16]memory){returnadopters;}注意事项:
现在我们已经编写了智能合约,接下来的步骤是编译和迁移它。
Solidity是一种编译语言,这意味着我们需要将Solidity编译为字节码,以供以太坊虚拟机(EVM)执行。将其视为将我们人类可读的Solidity转换为EVM可以理解的东西。
在终端中,确保您位于包含dapp的目录的根目录中,然后键入:
trufflecompile
Youshouldseeoutputsimilartothefollowing:
Compilingyourcontracts...===========================>Compiling./contracts/Adoption.sol>Compiling./contracts/Migrations.sol>Artifactswrittento/Users/cruzmolina/Code/truffle-projects/metacoin/build/contracts>Compiledsuccessfullyusing:-solc:0.5.0+commit.1d4f565a.Emscripten.clangMigration现在我们已经成功编译了我们的合约,是时候将它们迁移到区块链了!
您会在migrations/目录中看到一个JavaScript文件:1_initial_migration.js。这处理部署Migrations.sol合约以观察后续智能合约迁移,并确保我们将来不会重复迁移未更改的合约。
现在我们准备创建自己的迁移脚本。
在migrations/目录中创建一个名为2_deploy_contracts.js的新文件。
将以下内容添加到2_deploy_contracts.js文件中:
varAdoption=artifacts.require("Adoption");module.exports=function(deployer){deployer.deploy(Adoption);};在我们将合约迁移到区块链之前,我们需要运行一个区块链。在本教程中,我们将使用Ganache,这是一个用于以太坊开发的个人区块链,您可以使用它来部署合约、开发应用程序和运行测试。如果您还没有,请下载Ganache并双击图标以启动应用程序。这将生成一个在7545端口本地运行的区块链。
在Ganache中,请注意区块链的状态已更改。区块链现在显示当前块,以前为0,现在为4。此外,虽然第一个帐户最初有100个以太币,但由于迁移的交易成本,现在它更低了。稍后我们将更多地讨论交易成本。
Truffle在智能合约测试方面非常灵活,因为测试可以用JavaScript或Solidity编写。在本教程中,我们将使用Chai和Mocha库在Javascript中编写测试。在test/目录中创建一个名为testAdoption.test.js的新文件。将以下内容添加到testAdoption.test.js文件中:
constAdoption=artifacts.require("Adoption");contract("Adoption",(accounts)=>{letadoption;letexpectedAdopter;before(async()=>{adoption=awaitAdoption.deployed();});describe("adoptingapetandretrievingaccountaddresses",async()=>{before("adoptapetusingaccounts[0]",async()=>{awaitadoption.adopt(8,{from:accounts[0]});expectedAdopter=accounts[0];});});});我们通过导入来启动合约:
*Adoption:我们想要测试的智能合约。我们通过使用artifacts.require导入我们的Adoption合约来开始我们的测试。注意:编写此测试时,我们的回调函数采用参数帐户。这为我们提供了使用此测试时网络上可用的帐户。
然后,我们利用before为以下内容提供初始设置:
*领养id为8的宠物并将其分配给网络上测试帐户中的第一个帐户。
*该函数稍后用于检查petId:8是否被accounts[0]采用。
###测试采用功能
要测试采用函数,请记住,成功后它会返回给定的采用者。我们可以确保返回基于给定petID的采用者,并与采用函数中的预期采用者进行比较。
describe("adoptingapetandretrievingaccountaddresses",async()=>{before("adoptapetusingaccounts[0]",async()=>{awaitadoption.adopt(8,{from:accounts[0]});expectedAdopter=accounts[0];});it("canfetchtheaddressofanownerbypetid",async()=>{constadopter=awaitadoption.adopters(8);assert.equal(adopter,expectedAdopter,"Theowneroftheadoptedpetshouldbethefirstaccount.");});});注意事项:
我们调用智能合约方法采用者,看看哪个地址采用了petID为8的宠物。Truffle为用户导入Chai,因此我们可以使用断言函数。我们将实际值、预期值和失败消息(如果测试未通过,则会打印到控制台)传递给assert.equal()。###测试所有宠物主人的检索
由于数组只能在给定单个键的情况下返回单个值,因此我们为整个数组创建自己的getter。
在testAdoption.test.js中之前添加的函数下面添加这个函数。
it("canfetchthecollectionofallpetowners'addresses",async()=>{constadopters=awaitadoption.getAdopters();assert.equal(adopters[8],expectedAdopter,"Theowneroftheadoptedpetshouldbeinthecollection.");});由于采用者是一个数组,并且我们从第一次采用测试中知道我们采用了petId为8的宠物,因此我们将合约的地址与我们期望找到的地址进行比较。
truffletestIfallthetestspass,you'llseeconsoleoutputsimilartothis:Usingnetwork'development'.Compilingyourcontracts...===========================>Compiling./test/TestAdoption.sol>Artifactswrittento/var/folders/z3/v0sd04ys11q2sh8tq38mz30c0000gn/T/test-11934-19747-g49sra.0ncrr>Compiledsuccessfullyusing:-solc:0.5.0+commit.1d4f565a.Emscripten.clangTestAdoptiontestUserCanAdoptPet(91ms)testGetAdopterAddressByPetId(70ms)testGetAdopterAddressByPetIdInArray(89ms)3passing(670ms)创建用户界面以与智能合约交互现在我们已经创建了智能合约,将其部署到我们的本地测试区块链并确认我们可以通过控制台与之交互,是时候创建一个UI,以便Pete可以为他的宠物店使用一些东西!
宠物店TruffleBox中包含应用程序前端的代码。该代码存在于src/目录中。
前端不使用构建系统(webpack、grunt等)以尽可能容易上手。应用程序的结构已经存在;我们将填写以太坊独有的功能。这样,您可以将这些知识应用到您自己的前端开发中。
在文本编辑器中打开/src/js/app.js。
检查文件。请注意,有一个全局App对象来管理我们的应用程序,在init()中加载宠物数据,然后调用函数initWeb3()。web3JavaScript库与以太坊区块链交互。它可以检索用户帐户、发送交易、与智能合约交互等等。
从initWeb3中删除多行注释并将其替换为以下内容:
首先,我们检查我们是否使用现代dapp浏览器或更新版本的MetaMask,其中将以太坊提供程序注入到窗口对象中。如果是这样,我们使用它来创建我们的web3对象,但我们还需要使用ethereum.enable()显式请求访问帐户。
如果以太坊对象不存在,我们然后检查注入的web3实例。如果存在,则表明我们使用的是较旧的dapp浏览器(如Mist或较旧版本的MetaMask)。如果是这样,我们获取它的提供者并使用它来创建我们的web3对象。
如果不存在注入的web3实例,我们将基于本地提供者创建web3对象。(此回退适用于开发环境,但不安全且不适合生产环境。)
现在我们可以通过web3与以太坊进行交互,我们需要实例化我们的智能合约,以便web3知道在哪里可以找到它以及它是如何工作的。Truffle有一个名为@truffle/contract的库来帮助解决这个问题。它使有关合约的信息与迁移保持同步,因此您无需手动更改合约的部署地址。
仍然在/src/js/app.js中,从initContract中删除多行注释并将其替换为以下内容:
$.getJSON('Adoption.json',function(data){//Getthenecessarycontractartifactfileandinstantiateitwith@truffle/contractvarAdoptionArtifact=data;App.contracts.Adoption=TruffleContract(AdoptionArtifact);//SettheproviderforourcontractApp.contracts.Adoption.setProvider(App.web3Provider);//UseourcontracttoretrieveandmarktheadoptedpetsreturnApp.markAdopted();});注意事项:
我们首先为我们的智能合约检索工件文件。工件是关于我们合约的信息,例如其部署地址和应用程序二进制接口(ABI)。ABI是一个JavaScript对象,定义了如何与合约交互,包括其变量、函数及其参数。
一旦我们的回调中有工件,我们将它们传递给TruffleContract()。这会创建一个我们可以与之交互的合约实例。
实例化合约后,我们使用之前在设置web3时存储的App.web3Provider值设置其web3提供程序。
然后我们调用应用程序的markAdopted()函数,以防之前访问时已经收养了任何宠物。我们将其封装在一个单独的函数中,因为我们需要在任何时候对智能合约的数据进行更改时更新UI。
仍然在/src/js/app.js中,从markAdopted中删除多行注释并将其替换为以下内容:
varadoptionInstance;App.contracts.Adoption.deployed().then(function(instance){adoptionInstance=instance;returnadoptionInstance.getAdopters.call();}).then(function(adopters){for(i=0;i 我们访问部署的Adoption合约,然后在该实例上调用getAdopters()。 使用call()允许我们从区块链读取数据而无需发送完整的交易,这意味着我们不必花费任何以太币。 在调用getAdopters()之后,我们遍历所有这些,检查是否为每只宠物存储了一个地址。由于数组包含地址类型,以太坊用16个空地址初始化数组。这就是我们检查空地址字符串而不是null或其他虚假值的原因。 一旦找到对应地址的petId,我们禁用它的adopt按钮并将按钮文本更改为“Success”,以便用户得到一些反馈。 任何错误都会记录到控制台。 仍然在/src/js/app.js中,从handleAdopt中删除多行注释并将其替换为以下内容: varadoptionInstance;web3.eth.getAccounts(function(error,accounts){if(error){console.log(error);}varaccount=accounts[0];App.contracts.Adoption.deployed().then(function(instance){adoptionInstance=instance;//ExecuteadoptasatransactionbysendingaccountreturnadoptionInstance.adopt(petId,{from:account});}).then(function(result){returnApp.markAdopted();}).catch(function(err){console.log(err.message);});});注意事项: 我们使用web3来获取用户的帐户。在错误检查后的回调中,我们然后选择第一个帐户。 发送交易的结果就是交易对象。如果没有错误,我们继续调用我们的markAdopted()函数以将UI与我们新存储的数据同步。 我们现在可以启动本地Web服务器并使用dapp。我们正在使用lite-server库来提供我们的静态文件。这与宠物店TruffleBox一起提供,但让我们来看看它是如何工作的。 在文本编辑器(在项目的根目录中)打开bs-config.json并检查内容: {"server":{"baseDir":["./src","./build/contracts"]}}这告诉lite-server哪些文件要包含在我们的基本目录中。我们为我们的网站文件添加./src目录,为合约工件添加./build/contracts目录。 我们还在项目根目录的package.json文件中的脚本对象中添加了一个dev命令。scripts对象允许我们将控制台命令别名为单个npm命令。在这种情况下,我们只执行一个命令,但可能有更复杂的配置。这是您的外观: "scripts":{"dev":"lite-server","test":"echo\"Error:notestspecified\"&&exit1"},当我们从控制台执行npmrundev时,这告诉npm运行我们的lite-server本地安装。