如何使用Python测试你的以太坊智能合约?

背景

最近开始做以太坊区块链开发工作。大多数开发工具,包括solidity语言都倾向JavaScript。

当我开始使用Tunffle进行测试时,我发现JavaScript测试(IMO)太长,不必要。

但这是语言的本质问题。我一直比较喜欢Python,而不是js。我喜欢并在以前的项目中使用了py.test。所以如果我能选择使用py.test去测试我的智能合约话,我肯定会尝试一下。

这时候我碰到了Populus,不过目前看它还不够强大(我目前这么认为,官方可不这么看)。

最初,这是一个个人项目,它正式通过以太坊官方组织(github)的推荐,确实已经为已经完成的出色工作增加极高的可信度。

你可以在这里阅读官方英文版的文件。

测试

正如我前面提到的,我来选择尝试用py.test测试智能合约。

编写测试(连同web3.py)相当简单。

populus test中进行试验(和truffle test类似)。它是通过py.test,执行命令是这样的py.test <folder_containing_tests>/

这里我遇到了一个“小故障”,挺有意思,这个是问题解决办法

我喜欢使用py.test的一点原因是我不需要明确地启动testrpc

对于truffle testtestrpcgeth需要明确运行。否则你会得到以下错误:

1
2
3
4
5
6
$ truffle test
Could not connect to your Ethereum client. Please check that your Ethereum client:
- is running
- is accepting RPC connections (i.e., "--rpc" option is used in geth)
- is accessible over the network
- is properly configured in your Truffle configuration file (truffle.js)

我并不是说这有什么不对,只是可以省略一步,对于懒人来说很好。

创建本地链

另外一个很好的特点是populustruffle创建本地geth实例的功能。

这可以很容易地实现:

1
populus chain new <chain_name>

这个功能包括了以下几个方面:

  • 创建一个genesis.json(通常需要手工创建,或者从某个地方复制现成的并进行修改)
  • 创建一个帐户,并且有足够的余额。
  • 建两个脚本。
    • 一个创建创世纪块(init_chain.sh
    • 另一个启动“节点”(run_chain.sh

好处是,新手不需要理解genesis.jsongeth命令行中很长的列表选项和各种选项的复杂性。

这样一句话就行了。

这对有一定经验的开发人员也是有用的。可以根据需要修改genesis.json和脚本。

由于没有“修改密码”的概念,我们不能创建一个我们认为“更好”的密码,却不用删除现有的帐户,就能完成修改一个密码。这意味着需要修改run_chain.sh脚本,因为它提到了一个解锁参数的帐户。还需要修改genesis.json,因为“预置”帐户是在alloc下的。

但也是可以的接受的,因为populus这样做都是为了更方便开发。

阅读关于使用populus创建本地链的详细教程

还有一个命令:populus chain reset <chain_name>,但有时不起作用。我在这里提出了一个问题。

部署智能合约

对于简单的一次性合约,有一个命令行版本:

1
populus deploy

对于稍微复杂的合约部署,尤其是当需要将参数传递给合约的构造函数时,需要编写自己的Python代码。

这与在truffle中编写迁移脚本没有什么不同,不过在truffle ini上可以获得默认脚本,我们这里没有。

用命令行部署一个合约

部署是通过部署命令$ populus deploy来处理的,下面这些都是自动处理的。

  • 选择应该被部署到哪个区块链。
  • 运行给定区块链。
  • 项目合约的编制。
  • 导出库依赖关系。
  • 库链接。
  • 个人合同部署。

让我们部署一个简单的钱包合约。首先,我们需要一个合约在我们的./contracts目录中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ./contracts/Wallet.sol
contract Wallet {
mapping (address => uint) public balanceOf;

function deposit() {
balanceOf[msg.sender] += 1;
}

function withdraw(uint value) {
if (balanceOf[msg.sender] < value) throw;
balanceOf[msg.sender] -= value;
if (!msg.sender.call.value(value)()) throw;
}
}

在上面代码中,可以看到基本部署的输出。

如果在项目目录之外,请使用:

1
$ populus -p /path/to/my/project deploy Wallet -c local_a

以编程方式部署合约

还可以使用Python脚本部署合约。如果你的智能合约采用构造函数参数或需要更复杂的初始化调用,这是一种合适的方法。

示例(deploy_testnet.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
"""Deploy Edgeless token and smart contract in testnet.

A simple Python script to deploy contracts and then do a smoke test for them.
"""
from populus import Project
from populus.utils.wait import wait_for_transaction_receipt
from web3 import Web3


def check_succesful_tx(web3: Web3, txid: str, timeout=180) -> dict:
"""See if transaction went through (Solidity code did not throw).

:return: Transaction receipt
"""

# http://ethereum.stackexchange.com/q/6007/620
receipt = wait_for_transaction_receipt(web3, txid, timeout=timeout)
txinfo = web3.eth.getTransaction(txid)

# EVM has only one error mode and it's consume all gas
assert txinfo["gas"] != receipt["gasUsed"]
return receipt


def main():

project = Project()

# This is configured in populus.json
# We are working on a testnet
chain_name = "ropsten"
print("Make sure {} chain is running, you can connect to it, or you'll get timeout".format(chain_name))

with project.get_chain(chain_name) as chain:

# Load Populus contract proxy classes
Crowdsale = chain.get_contract_factory('Crowdsale')
Token = chain.get_contract_factory('EdgelessToken')

web3 = chain.web3
print("Web3 provider is", web3.currentProvider)

# The address who will be the owner of the contracts
beneficiary = web3.eth.coinbase
assert beneficiary, "Make sure your node has coinbase account created"

# Random address on Ropsten testnet
multisig_address = "0x83917f644df1319a6ae792bb244333332e65fff8"

# Deploy crowdsale, open since 1970
txhash = Crowdsale.deploy(transaction={"from": beneficiary}, args=[beneficiary, multisig_address, 1])
print("Deploying crowdsale, tx hash is", txhash)
receipt = check_succesful_tx(web3, txhash)
crowdsale_address = receipt["contractAddress"]
print("Crowdsale contract address is", crowdsale_address)

# Deploy token
txhash = Token.deploy(transaction={"from": beneficiary}, args=[beneficiary])
print("Deploying token, tx hash is", txhash)
receipt = check_succesful_tx(web3, txhash)
token_address = receipt["contractAddress"]
print("Token contract address is", token_address)

# Make contracts aware of each other
print("Initializing contracts")
crowdsale = Crowdsale(address=crowdsale_address)
token = Token(address=token_address)
txhash = crowdsale.transact({"from": beneficiary}).setToken(token_address)
check_succesful_tx(web3, txhash)

# Do some contract reads to see everything looks ok
print("Token total supply is", token.call().totalSupply())
print("Crowdsale max goal is", crowdsale.call().maxGoal())

print("All done! Enjoy your decentralized future.")


if __name__ == "__main__":
main()

完整的示例代码在这里

迁徙

populus在旧版本中有这个特性,但是它被移除了。gitter中问的时候,被告知有一个计划会把他们带回来。

最后的想法

虽然我想用populus作为唯一的选择,但我认为这与truffle相比还是不太成熟。

现在,我用truffle作为我与他人分享的项目(因为truffle似乎更为受大家欢迎),但对于我的个人项目,我继续使用populus(并报告问题,讨论并发送PR如果我知道问题在哪儿的话)

注意:您可以在这里查看我的(正在进行中的)代码。它既有truffle,也有populus配置文件。测试只在Python中进行。我在Python中有一个部署脚本。

  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。

汇智网原创翻译,转载请标明出处。这里是原文