这是区块链专家K. C. Tam关于智能合约开发工具和环境的详细分析与总结。通过此文,相信我们将获得关于这些工具的一些深入理解,对使用Solidity开展智能合约将由非常大的帮助。
首先简要介绍本文选择的智能合约。然后在四种环境中部署此合约。以下是在这里展示的四种环境的简要总结。
下图为合约编译和部署的简化流程
智能合约范例:收益共享
此应用程序摘自A. Bahga和V. Madisetti 的书籍“ 区块链与应用实践方法 ” 第4章:以太坊帐户中的智能合约。它已被修改以符合最新版本的Solidity的一些要求。
这个应用程序被称为收益共享。简而言之,部署合约时会给出地址列表。任何人都可以发送一定数量的资金(这里使用ethers或ethers的面额),这笔资金平均分配到清单上的地址。
一个相当简单的Solidity合约如下:
pragma solidity ^0.4.8;contract RevenueSharing {address public creator;mapping(uint => address) public shareholders;uint public numShareholders;event Disburse(uint _amount, uint _numShareholders);function RevenueSharing(address[] addresses) {creator = msg.sender;numShareholders = addresses.length;for (uint i=0; i< addresses.length; i++) {shareholders[i] = addresses[i];}}function shareRevenue() payable returns (bool success) {uint amount = msg.value / numShareholders;for (uint i=0; i<numShareholders; i++) {if (!shareholders[i].send(amount)) revert();}Disburse(msg.value, numShareholders);return true;}function kill() {if (msg.sender == creator) selfdestruct(creator);}}
关于这份智能合约的简单说明:
该合约名为RevenueSharing。
函数RevenueSharing()与合约本身具有相同的名称。它是构造函数,并且只在合约部署时调用一次。我们在这份合约中看到提供了一系列地址,这个地址数组存储在另一个名为股东的数组中。
函数shareRevenue()是此联系人中唯一的主要功能。当用一定数量的ethers(以msg.value)执行这个函数时,金额被分成股东数量(股东数量),股东阵列中的每个地址将得到该部分。我们将在演示中执行此功能。
函数kill()用于删除合约。我们不会在演示中使用这个功能。
请注意,所有变量都是使用public定义的。这有助于我们观察合约中的更多细节。在现实生活中,由于安全考虑,我们在公开变量或功能时应该小心。
Remix概述
Remix是一套工具,用于与以太坊区块链交互以调试交易(直接引用此处)。有一个IDE版本(Remix IDE)和一个在线版本(如下图)。
Remix中有很多工具,但以下工具符合我们的兴趣,
• Solidity编译器。这会产生很多有用的信息,我们将在另一个环境中使用
• 运行环境。Remix提供了三种:
• Injected Web3:Mist或MetaMask
• Web3 Provider:通过ipc到本地主机
• JavaScript VM:一个模拟环境
在运行时环境中,我们使用JavaScript VM。在JavaScript VM中,Remix附带五个以太坊帐户,每个帐户都存放100个ethers。这足以测试我们的智能合约。另外,由于它是自动完成的,因此不需要挖掘。
您可以从任何浏览器轻松访问Remix(网址:http://remix.ethereuem.org)。以上是Remix的截图。
屏幕分为几个模块区域。
• 智能合约区:我们在此粘贴合约的合约代码。
• 编译区和运行区:在编译标签中,此处显示任何编译错误或警告。在运行标签中,我们部署合约并执行合约功能。
• 事务日志区:此处可以观察所有事务细节。
编译合约
我们将智能合约代码粘贴到Remix中。
我们注意到代码是自动编译的,并且有一些警告。由于它们不是严重错误,因此我们可以继续前进。
如果我们点击详细信息,我们会看到很多关于此合约的信息。其中他们是,
• 字节码
• ABI
• Web3部署
在另一个环境中部署此合约时需要使用它们,我们稍后会回顾这一点。
由于我们在编译后看到没有错误,所以我们可以将此合约运行到Remix JavaScript环境。
部署合约
1.运行(Run)一览
下图为运行菜单界面一览。
从环境中选择JavaScript VM。
选择JavaScript虚拟机后,我们会在帐户字段中看到一些帐户。
如前所述,每一种都是预先充入100 ethers用于测试。因为我们稍后会使用这些帐户,所以我们可以先复制它们。
Gas limit是指明我们可以在任何交易中需要多少GAS。当我们处于测试环境中时,我们并不担心这一点。我已经尝试了一些大的合约部署,默认设置Gas limit不足(anyway,它可以在需要时调整到任何值)。
Value 是我们在合约部署和执行功能期间发送ethers的数量。在我们的案例中,我们没有在合约部署中放置任何Value,但是在执行该功能时放置了一些ethers。请参阅下面的更多细节。
2.部署合约
现在我们看到已经选择了RevenueSharing合约(我们的代码中只有一个合约)。我们将使用“创建”按钮将此合约部署到JavaScript VM上。
需要的东西,如输入区域中所暗示的:“地址[]地址”,当部署合约时。记得这份合约要求将地址列表作为共享目标吗?为了演示目的,我们将使用上面列出的第3,第4和第5个地址作为地址列表。因此,将其粘贴到创建按钮旁边:
现在确保我们已经选择:
• 环境:JavaScript VM
• 帐户:部署此合约时的第一个帐户(以0xca3 ...开头)
• 将地址数组粘贴到Create按钮旁边
按下Create,我们将看到以下情况。
3.合约部署后
该合约现在部署在JavaScript VM(内存)中,并显示合约地址(0x692 ...)。我们不在演示中使用此地址。在需要时可以在其他情况下提及该地址。
此外,我们看到标记为“public”的变量现在显示,它们是,
• shareholders
• numShareholders
• creator
我们在本合约中定义的两项功能,
• shareRevenue()
• Kill()
在此之前,我们观察到帐户余额减少了少量的ethers。差额(417,626 weis,1 wei = 10-18 ether)是部署此合约的成本。在现实生活中,这是在您部署合约时从您的帐户中扣除的真正的ethers。
与已部署的合约进行交互
1.检查变量
我们可以首先通过按下变量按钮来检查变量。在这里我们检查numShareholder和creator。对股东(shareholders)来说,因为它是一个数组,所以我们需要指定一个索引(0,1或2),对应于我们在部署(创建)合约时放置的地址。
所有的变量都是我们所期望的。
2.执行shareRevenue()函数
现在我们执行shareRevenue()。在执行此功能时,我们使用第一个帐户来存放30个ethers(这仅用于此功能,在很多情况下这不是必需的)。根据契约逻辑,30个ethers将被分配到账户列表中,即我们账户列表中的第3,第4和第5个账户。截至目前,他们每个人的余额仍然是100个ethers。
我们使用相同的地方来执行该功能。在这里我们确保,
• 在帐户字段中,选择第一个帐户(以0xca3 ...开头)
• 在价值中放置30个ethers
然后按shareRevenue。
函数执行后,我们检查每个帐户的余额,并根据我们的设计查看它是否被执行。
首先,我们看到30个ethers被从第1个帐户中扣除,并且名单上的所有三个帐户现在都有110个ethers。因此,从第一个账户中扣除的30个账户现在分配到这三个账户中。该部分按照合约进行完善。
另外,如果我们仔细检查第一个帐户的余额,则会扣除一些额外的ethers。差额为47,776 wei,这是此次交易的成本。每笔交易,功能的执行或合约的部署都需要花费一定数量的ethers。
事务日志
我们在测试过程中没有碰到事务日志,但是一切都保存在日志中,甚至是变量的查询。让我们来看看两个选定日志的细节。
1.合约部署
我们可以看到谁已经部署了此合约,合约地址以及部署它所需的交易成本。
2.执行shareRevenue()函数
我们再次将其视为交易成本。在shareRevenue()中,有一个返回 boolean值,我们看到“ecoded out”有一个“True”返回。此外,我们有一个成功发行的动作(我们在“LOG”中可以看到它)
总结:
这就是Remix如何帮助测试我们开发的代码。它具有非常方便的功能和直观的用户界面。在下一篇文章中,我们将使用另一个环境testrpc来处理同一个合约并了解它是如何工作的。
用于智能合约开发的最佳工具第2部分:关于TestRPC的Web3
概述
TestRPC是以太坊区块链的模拟,它带有10个预定义的以太坊帐户并支持助记符(即,可以使用相同的一组助记符生成相同的帐户集合)。它没有带有用户界面作为Remix,我们需要节点控制台加上web3库来与这个区块链进行交互。
前期准备
演示通过命令行或终端完成。使用支持屏幕分割的终端工具。我在我的Mac上使用iTerm2。
安装节点和npm:请参考此处在您的平台上进行安装。
备注:我最近发现用npm安装web3时,安装了1.0.0 beta版本,之前使用的命令(基于0.20.4)不起作用。因此我们改为指定web3的版本。
以下所有命令都在版本0.20.0中。
打开一个终端并将屏幕分成两部分。左侧是节点控制台,我们将在那里工作大部分时间。右侧是我们运行TestRPC的地方。
启动TestRPC
在右侧,启动TestRPC
这里我们可以看到:
• TestRPC是一个节点应用程序,模拟内存中的以太坊区块链。
• 10个帐户是预先定义的。
• 这些帐户是通过助记符生成的,每次启动TestRPC时都不相同。为了保持同一组帐户,我们可以在运行TestRPC时使用上面显示的助记符作为参数。
• 此外,RPC在localhost:8545上打开。Web3正在通过此访问区块链。
我们不会再触及这一部分,假设在以太坊区块链中一切正常。现在我们更关注节点控制台(左侧)。在测试过程中,我们不断看到命令和日志发布到TestRPC端显示的区块链。
Web3对象
我们需要指示节点控制台,我们正在使用web3并指向区块链web3进行连接。
这正是TestRPC中创建的帐户。
显示余额的方便功能
我找到了一个方便的功能(链接),可以显示所有帐户的余额。
只需将此功能复制粘贴到节点控制台即可。现在我们可以随时调用函数checkAllBalances(),并且它会以ether的形式显示所有账户的余额。请注意,在我们退出节点控制台后,此功能将消失,但我们可以随时将其添加回去。
部署合约
1.编译合约
现在一切都准备好了。我们可以部署我们的收益分享合约。
我们需要重新打开Remix,因为我们正在利用Remix上的编译器。在我们将合约代码粘贴到Remix后,它会自动编译。这里我们使用的是合约部署的结果。
单击编译标签上的详细信息,那里有很多信息。
在这些信息中,有三个是我们感兴趣的:字节码,ABI和Web3Deploy
字节码是编译后的合约的二进制版本,以及要在以太坊虚拟机(EVM)中运行的指令集,ABI(应用程序二进制接口)是我们与合约字节码交互的接口。
Remix足以准备Web3Deploy中的代码,其中字节码和ABI已包含在命令中。因此我们只需要使用Web3Deploy部分。
2.部署合约
首先,根据合约的要求,我们需要定义一个目标账户列表。为了演示目的,使用从第二个帐户开始的三个帐户,即从eth.accounts [1]到eth.accounts [3]。
然后我们按照Web3Deploy的建议。
根据ABI创建一个收入分成合约类。只需从Web3Deploy复制该行即可。
node console> var revenuesharingContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"creator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"kill","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"numShareholders","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"shareholders","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"shareRevenue","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[{"name":"addresses","type":"address[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_amount","type":"uint256"},{"indexed":false,"name":"_numShareholders","type":"uint256"}],"name":"Disburse","type":"event"}]);
现在用字节码来部署契约,加上必要的信息。再次,我们可以从Web3Deploy复制该行。已部署的合约是一个名为收益共享的对象。
node console> var revenuesharing = revenuesharingContract.new(addresses,{from: web3.eth.accounts[0],data: '0x6060604052341561000f57600080fd5b60405161049d38038061049d833981016040528080518201919050506000336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508151600281905550600090505b81518110156100f957818181518110151561009157fe5b906020019060200201516001600083815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550808060010191505061007a565b50506103938061010a6000396000f30060606040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806302d05d3f1461007257806341c0e1b5146100c757806368eca613146100dc578063ab377daa14610105578063e579a0bd14610168575b600080fd5b341561007d57600080fd5b61008561018a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100d257600080fd5b6100da6101af565b005b34156100e757600080fd5b6100ef610240565b6040518082815260200191505060405180910390f35b341561011057600080fd5b6101266004808035906020019091905050610246565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610170610279565b604051808215151515815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561023e576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b565b60025481565b60016020528060005260406000206000915054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008060006002543481151561028b57fe5b049150600090505b60025481101561031d576001600082815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050151561031057600080fd5b8080600101915050610293565b7f9c26340b8d01b4e039192edfd25f4a56ed070d45afe866b8685658b1ed3cd74d34600254604051808381526020018281526020019250505060405180910390a1600192505050905600a165627a7a72305820f0e717ba935e00c43896cc9266a85af91a519061c044503be0a52b93f721d1610029',gas: '4700000'}, function (e, contract){console.log(e, contract);if (typeof contract.address !== 'undefined') {console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);}})
我们将会(几乎立即)看到合约已经开采。
现在我们可以使用对象收益共享与此已部署的合约进行交互。
与已部署的合约进行交互
已部署的合约通过对象收益共享进行访问。
1.检查公共变量
我们可以检查那些标记为“公开”的变量。
2.执行shareRevenue()函数
在我们执行shareRevenue()函数之前,让我们看看余额。
请注意,某些金额在已部署合约的帐户[0]中扣除。为部署付费的ethers数量为417,626枚。当我们在Remix中这样做时,您可以检查它是确切的交易成本。
现在我们执行该功能。
node console> revenuesharing.shareRevenue({from: web3.eth.accounts[0], value: web3.toWei(30), gas: 4700000});
在这里,我们调用函数shareRevenue(),并指定它由账户[0]执行,其中30个ethers(wei是web3中的函数,用于将30个ethers转换为wei,因为wei是命令中接受的单位)。我们还把我们允许花费的气体(这是超过要求的方式,但执行后我们会得到退款)。
交易执行后,我们可以再次查看余额。
我们已经实现了我们所预期的目的:从账户[0]中扣除30个账户并分配到账户[1]到账户[3](现在每个账户都有110个账户)。除此之外,还有一部分金额用于执行此次交易。它仍然是47,776 weis,与我们在Remix中观察到的相匹配。
总结
我们已经在TestRPC上成功完成了同样的任务。除了我们必须在节点控制台和web3上与TestRPC中的区块链进行交互之外,整体流程与Remix中的流程几乎相同。