干货 | 智能合约开发简介

干货 | 智能合约开发简介

我最近在 Blockgeeks 上读了一篇好文章,在这篇文章里 Ameer 尝试把关于去中心化应用(DApp)编码的所有知识都放到其中。这是一件多么伟大的工作啊!当然这里边有很多要素可以讨论,我会详细阐述其中的一些工具,以及它们是如何用于智能合约开发的。

这一系列的文章不是为智能合约 101 或以太坊平台写的。当然你可以在 Blockgeeks 中发现许多有用的东西。但是我确定,通过浏览这篇文章,你会对这些工具有所了解,我也希望这会对你使用 Solidity 编写智能合约有所帮助。

我首先简要介绍我所选的智能合约。然后在四种环境中部署这个合约。以下是四种环境的概要。

下图为合约编译和部署的简易流程。

智能合约例子:收益共享

此应用摘自 A. Bahga 和 V. Madisetti 撰写的 Blockchain Applications: A Hands-On Approach一书第4章:“以太坊账户”中的智能合约。 它已经按照最新版 Solidity 的要求修改过了。

这个应用被称为收益共享。简而言之,在合约部署时会产生一组给定的地址。任何人都可以发送一定数量的资金(这里指的是以太币或者低面额单位的以太币),这些资金会被平均分配到每一个地址上。这是一个相当简单的Solidity智能合约。

这是智能合约的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() 函数与智能合约有相同的名字。它是构造函数,只会在合约部署时被调用一次。我们可以看到,这个合约通过构造函数接收了一个地址数组,并且这个地址数组是保存在另一个叫做 shareholders 的数组中的。

shareRevenue() 函数是这个智能合约中唯一的主函数。当以一定量的以太币(由 msg.value 所指定)来执行这个函数的时候,这些以太币会按照shareholders的数量(即 numShareholders 的值)均分,shareholder 数组中的每个地址都会得到其中一份。我们在演示中会执行这个函数。

kill() 函数用于移除智能合约,我们在演示中不会用到这个函数。

注意,所有的变量都被定义为 public 变量。这有助于我们观察到合约中的更多细节。在实际编程中,出于安全考虑,当我们把变量或函数设为 public 时要非常小心。

Remix

概览

Remix 是一套与以太坊区块链进行交互来调试交易(由此处直接引用)的工具。有 IDE 版本(Remix IDE)和在线版本,我们用的是在线版本。

Remix中有许多工具,但我们只对以下工具感兴趣:

Solidity编译器。它会生成我们将在另一个环境下用到的许多有用的信息。

运行环境。Remix提供了三个:

嵌入的Web3:例如由Mist或者MetaMask所提供的

Web3提供者:通过IPC从本机获取

JavaScript虚拟机:一个模拟环境

在这几个运行环境中,我们使用 JavaScript 虚拟机,在 JavaScript 虚拟机中,Remix 由 5 个以太坊账户组成,每个账户都存有 100 以太币。这对于测试我们的合约来说就足够了。而且,挖矿是不需要的,因为它是自动完成的。

你可以很容易的从任意浏览器上下载Remix(下载连接:http://remix.ethereum.org)。这是Remix的屏幕截图。

屏幕被分成了几个区:

智能合约区:我们在这里粘贴智能合约的Solidity代码。

编译和运行区:在编译标签中,展示了所有的编译错误和警告。在运行标签中,我们部署合约并且执行合约函数。

交易日志区:所有的交易细节都可以在这里看到。

合约编译

我们把智能合约代码粘贴到 Remix 中。

我们可以看到,代码是自动编译的,并且有一些警告。由于这并不是什么重大的错误,我们可以安全地继续进行下一步。

如果我们点击Details按钮,我们可以看到这个合约的许多信息。其中:

Bytecode 字节码

ABI 应用二进制接口

Web3 Deploy Web3Deploy

在另一个环境部署这个合约时,这些信息都是需要的。我们一会儿再回到这里。

因为编译没有错误,我们可以在Remix提供的JavaScript虚拟机环境下运行这个合约。

合约部署

1.Run标签一瞥

这就是Run标签里面的内容。

环境一栏选择 JavaScript VM

选择 JavaScript VM 之后,我们看到账户框里填充上了一些账户。

正如之前所说,每个账户都有100以太币的预存款。因为我们一会儿才会用到这些账户,我们可以先把这些账户复制下来。

Gas 限制是为了指明我们能在任一交易中花费的 gas 数量。由于我们处于测试环境中,所以并不太担心这一点。我已经试过一些大的合约部署,并且默认给出的 gas limit 都不合适。 不过,这个值可以在需要时随意增加。

Value 部分是我们在合约部署和执行一个函数时发送的以太币的数量。在我们这个例子中,部署合约不需要设定以太币,但执行函数需要设定以太币。更多细节请往下看。

2.部署合约

现在我们可以看到收益共享合约已经被选中(我们的代码中只有这一个合约)。我们会使用 Create 按钮来把这个合约部署到 JavaScript 虚拟机上。

就像输入区中提示的那样,在部署合约时需要指定 “address[] addresses”。还记得这个合约需要一个地址列表作为共享目标吗?出于演示目的,我们将使用上面列出的第3,第4和第5个地址作为地址列表。所以把它们粘贴到 Create 按钮的输入框里。

现在确保我们已经选了:

环境:JavaScript 虚拟机

账户:部署合约时的第一个账户(以 0xca3 开头)

把上面的地址数组粘贴到创建按钮的输入框里

点击 Create 按钮我们就可以看到接下来会发生的事情。

3.合约部署之后

这个合约现在已经在 JavaScript 虚拟机上部署好了,合约的地址已经显示出来了 (0x692…)。在我们的演示里我们不用这个地址。需要时这个地址可以在其他的用例中使用。

还有,public变量已经出现了,它们是:

shareholders 股东

numShareholders 股东数

creator 创建者

还有我们在合约中定义的两个函数:

shareRevenue()

kill()

在做其他事之前,我们观察到账户余额少了一小部分。差额(417,626 wei,1 wei = 10^(-18) ether)是部署合约的费用。在真实环境中,当你部署合约时会从你的账户中扣除真正的以太币。

4.与已部署的合约进行交互

(1)检查变量

我们首先可以通过点击变量按钮检查变量。在这里我们检查股东数量和创建者。对于股东数量,由于这是一个数组,我们需要指定数组索引(0、1或2),这与我们在部署(创建)合约时所传入的地址列表相符。

所有的变量都是我们期望的样子。

(2)执行 shareRevenue() 函数

现在我们执行 shareRevenue() 函数。在执行这个函数时,我们用第一个账户存入30以太币(这仅仅在这个函数里需要,在许多用例中它是不需要的)。根据合约逻辑,30以太币被均分到列表里的每个账户中,也就是第3、第4和第5个账户。目前,他们每个人的余额仍然是100以太币。

我们用相同的界面来执行这个函数。 在这里我们要确保:

在 Account 输入框,选中第一个账户(以 0xca3 开头的账户)

Value 设置为 30 以太币

然后点击 shareRevenue 。

函数执行之后,我们查看每个账户的余额,检查它是否根据我们的设计进行了执行。

首先,我们看到,第一个账户减去了 30 以太币,其他 3 个都变成了 110 以太币。 也就是说,这 30 以太币从第一个账户中减去,然后平均分配到了其他三个账户。 这完全是根据合约执行的。

还有,如果我们仔细检查第一个账户的余额,它还减去了另一小笔以太币。差额是47,776 wei,这是交易费。每一个交易、函数的执行,或者合约部署都会花费一小笔以太币。

5.交易日志

在我们的例子中并没有接触到交易日志,不过所有操作都保存在了日志里,即使是对变量的查询也保存了。让我们了解下两个特定日志的细节。

(1)合约部署

我们可以看到是谁部署了这个合约,这个合约的地址,以及部署合约的花费。

(2)执行 shareRevenue() 函数

我们再一次看到了交易的花费。在 shareRevenue() 函数中,有一个 boolean 类型的返回值,在解码输出里我们看到这是一个真值。还有,我们在 “logs” 中看到了一个标识分发成功的事件(event)。

6.总结

这就是 Remix 如何帮助我们测试代码的。它有非常方便的功能和直观的用户界面。 在下一篇文章中,我们将使用另一个环境,testrpc,来处理同一个合约,并了解它是如何工作的。

智能合约开发的最佳工具第二部分: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 中创建的账户。

一个显示余额的便利的函数

我发现了一个非常便利的函数(链接在这里),可以显示所有账户的余额。就是这个函数。

简单地把这个函数复制粘贴到节点控制台里就可以了。现在我们能随时调用这个函数 checkAllBalance() ,而且它会把账户余额以以太币为单位显示出来。注意,在我们退出节点控制台之后这个函数就不能用了,但我们随时都可以把它加进去。

合约部署

1.合约编译

现在所有准备工作都做好了。我们可以部署收益共享合约了。

我们需要重新打开 Remix,因为我们要沿用 Remix 上的编译器。我们把合约代码粘贴到 Remix 上之后,它就自动编译了。这里我们要用到合约部署的结果。

点击 Detail 和 Compile 标签,那里有很多信息。

在这些信息中,我们对其中的三个比较感兴趣,字节码、ABI(应用二进制接口)和Web3Deploy

字节码是合约编译后的二进制版本,是能够在以太坊虚拟机上运行的指令集,ABI(应用二进制接口)是我们与合约字节码进行交互的接口。

Remix 已经足够友好,它能够为我们准备用于通过 web3 来进行部署的代码,字节码和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中复制这一行。部署后的合约是一个被命名为 revenuesharing 的对象。

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);    } })

我们会看到合约已经(几乎是立即)被挖出了(也就是被记录到区块链上了)。

我们现在就可以用 revenuesharing 对象和这个部署的合约进行交互了。

与部署的合约交互

部署后的合约是通过 revenuesharing 对象进行访问的。

1.检查public变量

我们可以检查那些被标记为 “public” 的变量。

2.执行 shareRevenue() 函数

在我们执行 shareRevenue() 函数之前,我们先看一下余额。

注意,accounts[0] 的余额减少了一点,accounts[0] 是部署合约的账户。部署合约的费用是 417,626wei。你可以检查这个费用,它确实就是先前在 Remix 中部署合约时所花费的交易费。

现在我们来执行这个函数。

node console > revenuesharing.shareRevenue({from: web3.eth.accounts[0], value: web3.toWei(30), gas: 4700000});

在这里,我们正在调用 shareRevenue() 函数,以30以太币作为输入(toWei 是一个 web3 的函数,用于把 30 以太币转化成 wei),并且指定它由 account[0] 来执行。我们还加入了我们允许在这次调用中花费的 gas 值(它显然超过了需求,但执行后我们会得退款)。

交易执行后,我们可以再次查看余额。

我们做到了我们希望做的:从账户 0 减去 30 以太币,并把它们平均分配给账户1~3(现在账户1~3都是110以太币了)。而且,为了执行这个交易支付了一小笔以太币。又是 47,776 wei,与我们之前在 Remix 中看到的相符。

总结

我们在 TestRPC 上做到了同样的事情。除了我们必须在节点控制台和 web3 上与 TestRPC 的区块链进行交互以外,整个流程几乎和在 Remix 中完全相同。下一次,我们将在私有以太坊区块链上做几乎相同的事情。

原文链接:

 https://blockgeeks.com/guides/smart-contract-development/

作者: 

KC TAM 

翻译&校对: 刘艳安 & 风静縠纹平

本文来自https://blockgeeks.com/,这里<https://www.linkedin.com/in/ktam1/>是作者的领英主页。

本文由作者授权 EthFans 翻译及再出版。

你可能还会喜欢:

教程 | 【Ethereum 智能合约开发笔记】编译和部署合约的第一种姿势:使用 Remix教程 | 你的第一个Truffle分布式app (2)教程 | 以太坊开发演练,Part-2:Truffle,Ganache,Geth 和 Mist

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Proudly powered by WordPress | Theme: HoneyWaves by SpiceThemes