Interact with Contracts and Process Logs

Run this example online by clicking 🚀 -> Binder on the top bar!

Preparation

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

Interact with Contracts

In the Preparation part, we have already shown how to interact with a contract. A contract instance can be initialized from w3.cfx.contract, and with the name paramter, some frequently used contract instance can be created. However, it is quite often that we need to compile and deploy contracts written by ourselves. This part will show how to compile and deploy a contract, and how to interact with the newly-deployed one.

Compile and Deploy a Contract

A contract is a program running on the blockchain. And we need to firstly deploy a contract to the blockchain if we want to interact with a program written by ourselves.

Here is a simple smart contract:

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

After this contract is compiled and deployed, you can:

  • read the value of count from interface get

  • add the variable count by invoking inc

Besides, the contract will emit an event Change after inc is executed, we can know what has happened on chain by analyzing the logs.

Now we are going to compile the contract. You might need to run pip install py-solc-x if you are running the the codes in your local environment.

# 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

Interact with the Deployed Contract

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

Process Logs

We can check transaction logs to know what has happened after transaction execution. However, raw logs are encoded in hex and is hard to read.

# 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:acaxdwzd845fj0sgg97aky6r78tw2kfdzezfes8w01',
 'blockHash': HexBytes('0x46af0e6efae89cc3d4c179359992dc41a6f7bc11ce899d74e5ff372d4119f625'),
 'data': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001'),
 'epochNumber': 99722005,
 'logIndex': 0,
 'topics': [HexBytes('0x05b5d46649ab2015d3a08705cbaa391e094d9594c393ce89d3afffe960744da1'),
            HexBytes('0x00000000000000000000000016e78b2b41c9d216eba6008c244e5338096e9278')],
 'transactionHash': HexBytes('0x799bcf4d207d6adf2d515ca6ca47a43617fc27e2a69d1fe0d4fe3083fe053a57'),
 'transactionIndex': 0,
 'transactionLogIndex': 0}

In the following parts, we present several approaches to process and filter logs.

We can use contract.event to process logs. The args field of processed log presents the information we need.

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}

Besides, we can use contract.events to encode contract topics to filter logs

# 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=fromEpoch, 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}

Or we can use get_logs interface from contract.events

# event get_logs will return processed 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: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’s apis are consistent with web3.py’s apis. You can also try the examples from web3.py’s documentation.