如何使用 web3 解码日志并取回事件



我有这个交易,它从 2 个不同的合约发出 2 个不同的事件。假设我有两个合约的 ABI 文件,如何从logs字段中解析事件?

例如,下面是发出 2 个事件的此类事务 https://rinkeby.etherscan.io/tx/0xc6525195135a868897bd4c74ea0f6285b98492103be4df6fc5ea43f83b96b8eb#eventlog

这是getTransactionReceiptRPC调用的响应,如何从logs字段中解析事件名称及其参数?

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "blockHash": "0x3da27197fe084a82d76273b40c32e231d2662b229337795a43fde9f8d73b4d2c",
        "blockNumber": "0x39a62b",
        "contractAddress": null,
        "cumulativeGasUsed": "0x48ad9",
        "from": "0x20b53b91da0a2d9afdd442b2bb433a40ab7f9613",
        "gasUsed": "0x9e36",
        "logs": [
            {
                "address": "0x5328276603d169165d0f71ca67ccc89c45027df3",
                "blockHash": "0x3da27197fe084a82d76273b40c32e231d2662b229337795a43fde9f8d73b4d2c",
                "blockNumber": "0x39a62b",
                "data": "0x000000000000000000000000f63843b0b7fc5097bc1f92658379617513cce546000000000000000000000000ff0e3299e55efd859176d582fc805481e83449150000000000000000000000000000000000000000000000000000000000002245",
                "logIndex": "0x1",
                "removed": false,
                "topics": [
                    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
                ],
                "transactionHash": "0x5442a15c593b3fcf3069ad7f6a41d8805a66a49ed407d872e46ea95b1c9f6054",
                "transactionIndex": "0x3"
            },
            {
                "address": "0x7387e0b25165e9a621f624e47b3362a937892c7b",
                "blockHash": "0x3da27197fe084a82d76273b40c32e231d2662b229337795a43fde9f8d73b4d2c",
                "blockNumber": "0x39a62b",
                "data": "0x0000000000000000000000000000000000000000000000000000000000002245000000000000000000000000ff0e3299e55efd859176d582fc805481e8344915000000000000000000000000f63843b0b7fc5097bc1f92658379617513cce546000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000ffcb9e57d4000",
                "logIndex": "0x2",
                "removed": false,
                "topics": [
                    "0x9accbcf984c4cd67a675ee4d38143974e1fa62aa95da283bd4ca645e408ec283"
                ],
                "transactionHash": "0x5442a15c593b3fcf3069ad7f6a41d8805a66a49ed407d872e46ea95b1c9f6054",
                "transactionIndex": "0x3"
            }
        ],
        "logsBloom": "0x20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000010000000000000000000000000000000000000000000000004000000000000000000080000008000000000000000000000000000000000000000400000000000000000000000000002000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "status": "0x1",
        "to": "0x7387e0b25165e9a621f624e47b3362a937892c7b",
        "transactionHash": "0x5442a15c593b3fcf3069ad7f6a41d8805a66a49ed407d872e46ea95b1c9f6054",
        "transactionIndex": "0x3"
    }
}

我找不到任何现有的可以轻松解码事件日志,所以我做了以下操作:

from web3 import Web3
from eth_utils import decode_hex
from eth_abi import decode

def get_event_by_signature(abi, signature):
    for entry in abi:
        if entry['type'] == 'event':
            input_params = [f"({','.join([component['type'] for component in param['components']])})" if param['type'] == "tuple" else param['type'] for param in entry['inputs']]
            text_to_hash = f"{entry['name']}({','.join(input_params)})"
            if Web3.keccak(text=text_to_hash).hex() == signature:
                return entry
    return None

def decode_data(event, data):
    unindexed_params = [param for param in event['inputs'] if not param['indexed']]
    definition = [f"({','.join([component['type'] for component in param['components']])})" if param['type'] == "tuple" else param['type'] for param in unindexed_params]
    decoded_data = decode(definition, decode_hex(data))
    data_dict = {}
    for i, param in enumerate(unindexed_params):
        if param["type"] == "tuple":
            data_dict[param['name']] = {component['name']: decoded_data[i][j] for j, component in enumerate(param["components"])}
        else:
            data_dict[param['name']] = decoded_data[i]
    return data_dict

def decode_topics(event, topics):
    decoded_topics = {}
    topic_count = 1
    for param in event['inputs']:
        if param['indexed']:
            if param["type"] == "tuple":
                decoded_topic = topics[topic_count]
            else:
                decoded_topic = decode([param['type']], decode_hex(topics[topic_count]))[0]
            decoded_topics[param['name']] = decoded_topic
            topic_count += 1
    return decoded_topics

def decode_log(abi, result):
    signature = result['topics'][0]
    event = get_event_by_signature(abi, signature)
    data = result['data']
    topics = result['topics']
    decoded_data = decode_data(event, data)
    decoded_topics = decode_topics(event, topics)
    return {**decoded_topics, **decoded_data}

decode_log函数采用协定 ABI 和一个结果对象,并返回所有事件参数及其各自值的字典。其他函数只是 decode_log 使用的辅助函数。

如果其中一个参数是结构,则该值将是结构键和值的另一个字典。

如果其中一个参数是数组,则该值将是数组中值的列表。

如果其中一个参数是索引结构或数组,由于它大于 32 位,因此不适合主题,因此值的哈希存储在关联的主题中,并且不存储数据。在这种情况下,哈希将作为值返回

例:

结果对象:

{
  "removed": false,
  "logIndex": "0x37",
  "transactionIndex": "0xd",
  "transactionHash": "0xaa600a42c767cd6fa60a331ceb68c7d38e79d14ba45ef3f590916bdf65657aeb",
  "blockHash": "0x0479e3cad2a0e94d7cc3af096c40f440252bf658df4d4f2cfa45c2435f8b8831",
  "blockNumber": "0x881b1c",
  "address": "0x2ec255b90855c20c1c51d3fa6259bf9605412180",
  "data": "0x00000000000000000000000000000000000000000000000000000000000000020000000000000000000000001bc858e1b8c174fdcbc5df7fbbf3a361fefefa7100000000000000000000000000000000000000000000000000000000000003de000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000644ee55800000000000000000000000000000000000000000000000000000000644ee6c00000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000016546869732069732074657874206578616d706c652027000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004e000000000000000000000000000000000000000000000000000000000000004e",
  "topics": [
    "0x96969eb2a3d3e165bc0514f80c20d2535fdf2ae2d35de97fe807c028e95b616a",
    "0x0000000000000000000000000000000000000000000000000000000000000001",
    "0x0000000000000000000000000000000000000000000000000000000000000005",
    "0x5d60038e81b1a59543b60113b96879ca6954fee6ab55d62a75952c8c0381b4d5"
  ]
}

解码日志:

{
  "param1": 1,
  "param6": 5,
  "param7": "0x5d60038e81b1a59543b60113b96879ca6954fee6ab55d62a75952c8c0381b4d5",
  "param2": 2,
  "param3": {
    "ownerAddress": "0x1bc858e1b8c174fdcbc5df7fbbf3a361fefefa71",
    "balance": 990,
    "reward": 0,
    "startDateTime": 1682892120,
    "endDateTime": 1682892480
  },
  "param4": 3,
  "text": "This is text example '",
  "param8": [
    78,
    78,
    78
  ]
}

最新更新