与合约交互并处理日志#

点击顶部栏的 🚀 -> Binder 在线运行此示例!

准备工作#

import pprint
from conflux_web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://test.confluxrpc.com"))
account = w3.account.create()
w3.cfx.default_account = account

faucet = w3.cfx.contract(name="Faucet")
faucet.functions.claimCfx().transact().executed()
w3.cfx.get_balance(account.address).to("CFX")
1000 CFX

与合约交互#

准备工作部分,我们已经展示了如何与合约进行交互。合约实例可以通过 w3.cfx.contract 初始化,使用 name 参数可以创建一些常用的合约实例。然而,我们经常需要编译和部署自己编写的合约。本部分将展示如何编译和部署合约,以及如何与新部署的合约进行交互。

编译和部署合约#

合约是运行在区块链上的程序。如果我们想要与自己编写的程序进行交互,首先需要将合约部署到区块链上。

这是一个简单的智能合约:

// SPDX-License-Identifier: MIT
// modified from https://solidity-by-example.org/first-app/
pragma solidity ^0.8.13;

contract Counter {
    uint public count;

    event Change(address indexed sender, uint new_value);

    constructor(uint init_value) {
        count = init_value;
    }

    // Function to get the current count
    function get() public view returns (uint) {
        return count;
    }

    // Function to increment count by 1
    function inc() public {
        count += 1;
        emit Change(msg.sender, count);
    }
}

在合约编译和部署完成后,你可以:

  • 通过 get 接口读取 count 的值

  • 通过调用 inc 来增加变量 count 的值

此外,当 inc 执行后,合约会发出一个 Change 事件,我们可以通过分析日志来了解链上发生了什么。

现在我们来编译这个合约。如果你在本地环境运行这些代码,可能需要执行 pip install py-solc-x

# py-solc-x is already installed in the test environment
from solcx import install_solc, compile_source
source_code = r"""
// SPDX-License-Identifier: MIT
// modified from https://solidity-by-example.org/first-app/
pragma solidity ^0.8.13;

contract Counter {
    uint public count;

    event Change(address indexed sender, uint new_value);

    constructor(uint init_value) {
        count = init_value;
    }

    // Function to get the current count
    function get() public view returns (uint) {
        return count;
    }

    // Function to increment count by 1
    function inc() public {
        count += 1;
        emit Change(msg.sender, count);
    }
}
"""
metadata = compile_source(
    source_code,
    output_values=['abi', 'bin'],
    solc_version=install_solc(version="0.8.13")
).popitem()[1]
# "abi" defines the interface, "bin" is the contract bytecode
assert "abi" in metadata and "bin" in metadata
# init contract from metadata, bytecode is needed to deploy a contract
factory = w3.cfx.contract(abi=metadata["abi"], bytecode=metadata["bin"])

# deploy the contract
tx_receipt = factory.constructor(init_value=0).transact().executed()
contract_address = tx_receipt["contractCreated"]
assert contract_address is not None
print(f"contract deployed: {contract_address}")

# init a contract with address param, now we can interact with it
deployed_contract = w3.cfx.contract(address=contract_address, abi=metadata["abi"])
contract deployed: cfxtest:acaxdwzd845fj0sgg97aky6r78tw2kfdzezfes8w01

与已部署的合约交互#

tx_hash = deployed_contract.functions.inc().transact()
inc_receipt = w3.cfx.wait_for_transaction_receipt(tx_hash)

# "call" a contract means virtually execute the transaction without actually sending a transaction
# 2 ways to call, either is ok
current_counter = deployed_contract.functions.get().call()
current_counter_ = deployed_contract.caller().get()
assert current_counter == current_counter_ == 1
print("counter added to 1")
counter added to 1

处理日志#

我们可以通过检查交易日志来了解交易执行后发生了什么。但是,原始日志是以十六进制编码的,不易阅读。

# get_logs parameter definitions: https://developer.confluxnetwork.org/conflux-doc/docs/json_rpc#cfx_getlogs
from_epoch = inc_receipt["epochNumber"]
assert contract_address is not None
# use get_logs to get raw logs
logs = w3.cfx.get_logs({"fromEpoch": from_epoch, "address": contract_address})
print("raw log: ")
pprint.pprint(dict(logs[0]))
raw log: 
{'address': 'cfxtest:acaxdwzd845fj0sgg97aky6r78tw2kfdzezfes8w01',
 'blockHash': HexBytes('0x46af0e6efae89cc3d4c179359992dc41a6f7bc11ce899d74e5ff372d4119f625'),
 'data': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
 'epochNumber': 99722005,
 'logIndex': 0,
 'topics': [HexBytes('0x05b5d46649ab2015d3a08705cbaa391e094d9594c393ce89d3afffe960744da1'),
            HexBytes('0x00000000000000000000000016e78b2b41c9d216eba6008c244e5338096e9278')],
 'transactionHash': HexBytes('0x799bcf4d207d6adf2d515ca6ca47a43617fc27e2a69d1fe0d4fe3083fe053a57'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

在接下来的部分中,我们将介绍几种处理和过滤日志的方法。

我们可以使用 contract.event 来处理日志。处理后的日志中的 args 字段包含了我们需要的信息。

processed_logs = deployed_contract.events.Change.process_receipt(inc_receipt)
processed_log = processed_logs[0]
assert processed_log["args"]["sender"] == w3.cfx.default_account
assert processed_log["args"]["new_value"] == 1

# for log processed from transaction receipt, field "logIndex" is None
pprint.pprint(dict(processed_log))
{'address': 'cfxtest:acaxdwzd845fj0sgg97aky6r78tw2kfdzezfes8w01',
 'args': AttributeDict({'sender': 'CFXTEST:TYPE.USER:AANSTC3NJHE7EF1NY2AJ2KCSMP6AW5YWTA3MMFYGZX', 'new_value': 1}),
 'blockHash': HexBytes('0x46af0e6efae89cc3d4c179359992dc41a6f7bc11ce899d74e5ff372d4119f625'),
 'epochNumber': 99722005,
 'event': 'Change',
 'logIndex': None,
 'transactionHash': HexBytes('0x799bcf4d207d6adf2d515ca6ca47a43617fc27e2a69d1fe0d4fe3083fe053a57'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

此外,我们可以使用 contract.events 来编码合约主题以过滤日志

# generate topics to use getLogs
filter_topics = deployed_contract.events.Change.get_filter_topics(
    sender=w3.cfx.default_account
)
new_logs = w3.cfx.get_logs(fromEpoch=from_epoch, topics=filter_topics)
print("log filtered by topics:")
pprint.pprint(dict(new_logs[0]))
log filtered by topics:
{'address': 'cfxtest:acaxdwzd845fj0sgg97aky6r78tw2kfdzezfes8w01',
 'blockHash': HexBytes('0x46af0e6efae89cc3d4c179359992dc41a6f7bc11ce899d74e5ff372d4119f625'),
 'data': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
 'epochNumber': 99722005,
 'logIndex': 0,
 'topics': [HexBytes('0x05b5d46649ab2015d3a08705cbaa391e094d9594c393ce89d3afffe960744da1'),
            HexBytes('0x00000000000000000000000016e78b2b41c9d216eba6008c244e5338096e9278')],
 'transactionHash': HexBytes('0x799bcf4d207d6adf2d515ca6ca47a43617fc27e2a69d1fe0d4fe3083fe053a57'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

或者我们可以使用 contract.events 提供的 get_logs 接口

# event get_logs will return processed logs
new_processed_logs = deployed_contract.events.Change.get_logs(
    argument_filters={
        "sender": w3.cfx.default_account
    },
    from_epoch=from_epoch
)
print("processed log from contract event get_logs")
pprint.pprint(dict(new_processed_logs[0]))
processed log from contract event get_logs
{'address': 'cfxtest:acaxdwzd845fj0sgg97aky6r78tw2kfdzezfes8w01',
 'args': AttributeDict({'sender': 'CFXTEST:TYPE.USER:AANSTC3NJHE7EF1NY2AJ2KCSMP6AW5YWTA3MMFYGZX', 'new_value': 1}),
 'blockHash': HexBytes('0x46af0e6efae89cc3d4c179359992dc41a6f7bc11ce899d74e5ff372d4119f625'),
 'epochNumber': 99722005,
 'event': 'Change',
 'logIndex': 0,
 'transactionHash': HexBytes('0x799bcf4d207d6adf2d515ca6ca47a43617fc27e2a69d1fe0d4fe3083fe053a57'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

conflux_web3 的 API 与 web3.py 的 API 保持一致。你可以参考 web3.py 文档中的示例: