与智能合约交互,处理日志

单击顶部栏上的🚀 -> 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
# 根据metadata的abi与bytecode字段初始化contract对象
factory = w3.cfx.contract(abi=metadata["abi"], bytecode=metadata["bin"])

# 部署合约
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}")

# 使用address参数初始化合约,这样我们可以调用该对象的链上接口
deployed_contract = w3.cfx.contract(address=contract_address, abi=metadata["abi"])
contract deployed: cfxtest:acc0mmk9t5sukfybrg8sy2sbw49tar6u8jf4r1u7tg

与已部署的合约交互

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

# "call" 代表着模拟执行但不真正发送交易
# 下面两种方法都能调用"call"
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
fromEpoch = inc_receipt["epochNumber"]
# use get_logs to get raw logs
logs = w3.cfx.get_logs(fromEpoch=fromEpoch, address=contract_address)
print("raw log: ")
pprint.pprint(dict(logs[0]))
raw log: 
{'address': 'cfxtest:acc0mmk9t5sukfybrg8sy2sbw49tar6u8jf4r1u7tg',
 'blockHash': HexBytes('0x5d7f3bc654370883359835aa22be509a7a36a8215d011e21bf0c2ca4592c6665'),
 'data': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
 'epochNumber': 99721993,
 'logIndex': 0,
 'topics': [HexBytes('0x05b5d46649ab2015d3a08705cbaa391e094d9594c393ce89d3afffe960744da1'),
            HexBytes('0x0000000000000000000000001f4bffef33cdf6e6b854f6aaf2c4498f18027bb1')],
 'transactionHash': HexBytes('0x1d703cd4602bb02603b8154ca43ec4f4ff8107e757afd11b4b8d118db03cc222'),
 '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

# 从交易receipt拿到的日志中, 字段 "logIndex" 为 None
pprint.pprint(dict(processed_log))
{'address': 'cfxtest:acc0mmk9t5sukfybrg8sy2sbw49tar6u8jf4r1u7tg',
 'args': AttributeDict({'sender': 'CFXTEST:TYPE.USER:AATY199TGTG9R3Z2MX5MZ60EKGHVUAX50EC9SFY3HB', 'new_value': 1}),
 'blockHash': HexBytes('0x5d7f3bc654370883359835aa22be509a7a36a8215d011e21bf0c2ca4592c6665'),
 'epochNumber': 99721993,
 'event': 'Change',
 'logIndex': None,
 'transactionHash': HexBytes('0x1d703cd4602bb02603b8154ca43ec4f4ff8107e757afd11b4b8d118db03cc222'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

此外,我们可以使用contract.events对合约topics进行编码以用于过滤日志

# 编码topics以用于get_logs
filter_topics = deployed_contract.events.Change.get_filter_topics(
    sender=w3.cfx.default_account
)
new_logs = w3.cfx.get_logs(fromEpoch=fromEpoch, topics=filter_topics)
print("log filtered by topics:")
pprint.pprint(dict(new_logs[0]))
log filtered by topics:
{'address': 'cfxtest:acc0mmk9t5sukfybrg8sy2sbw49tar6u8jf4r1u7tg',
 'blockHash': HexBytes('0x5d7f3bc654370883359835aa22be509a7a36a8215d011e21bf0c2ca4592c6665'),
 'data': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
 'epochNumber': 99721993,
 'logIndex': 0,
 'topics': [HexBytes('0x05b5d46649ab2015d3a08705cbaa391e094d9594c393ce89d3afffe960744da1'),
            HexBytes('0x0000000000000000000000001f4bffef33cdf6e6b854f6aaf2c4498f18027bb1')],
 'transactionHash': HexBytes('0x1d703cd4602bb02603b8154ca43ec4f4ff8107e757afd11b4b8d118db03cc222'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

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

# event get_logs 接口会直接返回已处理的日志
new_processed_logs = deployed_contract.events.Change.get_logs(
    argument_filters={
        "sender": w3.cfx.default_account
    },
    fromEpoch=fromEpoch
)
print("processed log from contract event get_logs")
pprint.pprint(dict(new_processed_logs[0]))
processed log from contract event get_logs
{'address': 'cfxtest:acc0mmk9t5sukfybrg8sy2sbw49tar6u8jf4r1u7tg',
 'args': AttributeDict({'sender': 'CFXTEST:TYPE.USER:AATY199TGTG9R3Z2MX5MZ60EKGHVUAX50EC9SFY3HB', 'new_value': 1}),
 'blockHash': HexBytes('0x5d7f3bc654370883359835aa22be509a7a36a8215d011e21bf0c2ca4592c6665'),
 'epochNumber': 99721993,
 'event': 'Change',
 'logIndex': 0,
 'transactionHash': HexBytes('0x1d703cd4602bb02603b8154ca43ec4f4ff8107e757afd11b4b8d118db03cc222'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

conflux_web3的 api 与web3.py的 api 一致。您还可以尝试web3.py文档中的示例。