最近,我有时间在以太坊区块链上玩智能合约。此示例将重点关注使用Nethereum库与智能合约进行交互。
最初我想看看Eth Gas Station上发现的合约0x2a0c0dbecc7e4d658f48e01e3fa353f44050c208中发生的事件,但是在轻模式下获得geth同步被证明是一个非常大的挑战。在快速模式下同步也很慢:几个小时取决于你的连接速度。由于我希望我的示例尽可能无缝地工作,因此在测试区块链上部署起来更容易:ganache-cli。
因此,示例的第一部分是使用ganache-cli本地区块链,并从Nethereum文档部署示例合约。
ganache-cli
Ganache-cli将在每次运行时生成测试帐户。为了确保它们不会更改(更容易进行测试,但从不在生产中使用这些帐户!),使用助记符参数。不要忘记映射用于连接节点的端口8545。
docker run --rm -ti -p 8545:8545 trufflesuite/ganache-cli --mnemonic "johnny mnemonic"
这个命令的结果是:
Ganache CLI v6.1.0 (ganache-core: 2.1.0)
Available Accounts
==================
(0) 0x4efd4ab8293b3b8179d9dbf9ade1ae00d83eb314
(1) 0xaffe3d426b8517b17b54290c882f0827f65ec187
(2) 0x6824385051e746ef4c1c6b4ae7e56a381a95d54a
(3) 0x338da826cf7a3c9a68f00a9e922eeed5ca1e8211
(4) 0x8dcc6e12c380a80329e6108ad9b04b78e561d65a
(5) 0xcee6938394b0fa55c45d08ad588cd84af89a14df
(6) 0x70ab1d88d6980b0d192f3521f8862ac4dca68567
(7) 0xe20547f96055fbd2c3a2263e71cd4adb74b69349
(8) 0x79bef342f6bc8e0b34bcf51e55bf73611aeeb2c4
(9) 0xa24982b1e3c6be086465971c9e7e7e8ad44fdf48
Private Keys
==================
(0) 3f22cc3e1757c4a69de7e249c99e4217d4a0017157247a863cc7fb61e5a16ec8
(1) 8cfce94fa87c2e937d2c901b6193802900e3a9b15bdfe9aaeb1cbb9e9b46d485
(2) 3b9f3f9087260beb3c81a7fa105e0c0714cf30964d726a25cafb883bf3589c2e
(3) 14886ce9c977b5b181cfc01ded5a9f4753f4340623bed643cd7ea89a777e36e7
(4) c8486cd25cb5278481eef49cb689905511f0ebd77101c3f87bb25c152d8a248a
(5) 6ba903be3121fdacb40b677f8f5c0a400e9ff06d0632f4f9c0fcaf5ef4cf989e
(6) 3141fae5ba12e4a00260f9aec8fafa0d60e0339a4d68145c4950eaab67e55e79
(7) cb3a94619a4ced15e0efea940e74c8b5c45b4af7dddc38ad50a0b474cc633ed1
(8) a3b3c325cb77497c5f6ac65a169aa9bdbdc1387d9367f5ab4d83c76b18ed2651
(9) 59b034e79634893393a58ed2be861c459c200be8622a04c267f90ad7a44607ca
HD Wallet
==================
Mnemonic: johnny mnemonic
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
现在我们已准备好本地测试区块链并等待合约。
Nethereum客户端
与以太坊区块链交互涉及几个步骤。
- 创建合约代码
- 编译二进制文件
- 解锁帐户以发送要处理的交易
- 部署二进制文件
- 与合约互动
合约代码
我们将使用Nethereum文档中的示例:
contract test {
uint _multiplier;
event Multiplied(uint indexed a, address indexed sender, uint result);
function test(uint multiplier){
_multiplier = multiplier;
}
function multiply(uint a) returns(uint r) {
r = a * _multiplier;
emit Multiplied(a, msg.sender, r);
return r;
}
}
代码编译将成为下一篇文章的一部分。目前我只需复制并粘贴应用程序二进制接口(ABI)和字节码:)
解锁帐户
如果你要发送需要支付费用的交易,则需要解锁该帐户。这笔费用是以gas支付的,gas价格各不相同。
在此示例中,帐户已解锁30分钟,但在现实生活中,这应该更少:)
Log($"Using geth node {node}");
var web3 = new Web3(node);
Log($"Unlocking account {senderAddress}");
var unlockAccountResult = await web3.Personal.UnlockAccount.SendRequestAsync(senderAddress, senderPassword, 1200).ConfigureAwait(false);
Log($"-> Success: {unlockAccountResult}");
var funds = await web3.Eth.GetBalance.SendRequestAsync(senderAddress).ConfigureAwait(false);
Log($"Account {senderAddress} has {UnitConversion.Convert.FromWei(funds.Value)} eth");
gas估算和部署
Nethereum提供了一种方便的功能来估算函数和合约部署的gas成本。否则你需要提供任意金额(希望足够高)。
发送交易后,我们将等待它被挖掘:然后我们可以获得包含交易信息的收据:
- 区块号
- 合约地址
- 用过的gas
var gasPrice = await web3.Eth.GasPrice.SendRequestAsync().ConfigureAwait(false);
Log($"Gas price is {gasPrice.Value} wei");
var multiplier = 7;
var gasDeploy = await web3.Eth.DeployContract.EstimateGasAsync(abi, contractByteCode, senderAddress, multiplier).ConfigureAwait(false);
Log($"Deploying contract with multiplier {multiplier} using {gasDeploy.Value} gas");
var receipt = await web3.Eth.DeployContract.SendRequestAndWaitForReceiptAsync(abi, contractByteCode, senderAddress, gasDeploy, null, multiplier).ConfigureAwait(false);
Log($"-> Done at block {receipt.BlockNumber.Value} using {receipt.CumulativeGasUsed.Value} gas");
Log($"-> Contract address is {receipt.ContractAddress}");
调用函数
函数调用遵循相同的模式:估计gas,发送交易并等待接收。
var contract = web3.Eth.GetContract(abi, receipt.ContractAddress);
var multiplyFunction = contract.GetFunction("multiply");
var gas7 = await multiplyFunction.EstimateGasAsync(7).ConfigureAwait(false);
Log($"Multiply 7 by {multiplier} using {gas7.Value} gas");
var receipt7 = await multiplyFunction.SendTransactionAndWaitForReceiptAsync(senderAddress, gas7, null, null, 7).ConfigureAwait(false);
Log($"-> Done at block {receipt7.BlockNumber.Value} using {receipt7.CumulativeGasUsed.Value} gas");
监听事件
在监听事件之前,我们需要为它创建一个过滤器。之后,将检索在创建过滤器之后(或在最后一次调用GetFilterChanges之后)发生的事件。
Log($"Creating filter for all events");
var filterAll = await multiplyEvent.CreateFilterAsync().ConfigureAwait(false);
Log("Get all events");
var log = await multiplyEvent.GetFilterChanges<MultipliedEvent>(filterAll).ConfigureAwait(false);
Log($"-> Got {log.Count}");
foreach (var evt in log)
{
Log($"->