Web3.py – How to Decode Log[‘data’] HexBytes from a Solidity Event Using Web3.py

etheretherscanpolygonweb3.py

I need help pulling zkEVM log data from testnet.

I have this simple solidity event in my smart contract (https://testnet-zkevm.polygonscan.com/address/0x98544219dd60eCc071302dAfBfce22F74334f244) that I deployed to the zkEMV testnet:

event LogData(
    uint256 indexed amount,
    string reason,
    address donatorAddress,
    uint256 timestamp
);

With the code below, I was able to iterate over the blocks and retrieve the event logs. But I can't read the second event argument (reason) as a string. It's written as hexBytes data: b'\x00\x00\x00…

Can someone please help me decode the event's reason which is inside the log['data'], on liine 49 of the code.

I tried using Web3.to_text(log['data']) and many others proposed solution but I wasn't able to decode it. The decoded data from both logs should be the following strings: "testnow" and "for adam"

from dash import Dash, html, callback, clientside_callback, Output, Input, State, no_update
import dash_bootstrap_components as dbc
from web3 import Web3
import json


app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG], external_scripts=[{'src': '../static/signerinfo.js', 'type':'module'}])
app.layout = dbc.Container([
    html.H1('Donation Decentralized Application'),

    dbc.Button("See donations over 45 wei", id="get-data", color='info', n_clicks=0),
    html.Div(id='placeholder', children=""),
    html.Div(id='placeholder2', children=""),

])


@callback(
    Output("placeholder2", "children"),
    Input("get-data", "n_clicks"),
    prevent_initial_call=True,
)
def access_data(alert_text):
    public_zkevm_rpc = 'https://rpc.public.zkevm-test.net'
    w3 = Web3(Web3.HTTPProvider(public_zkevm_rpc))
    print(w3.is_connected())
    abi = json.loads(
        '[{"inputs": [],"stateMutability": "payable","type": "constructor"},{"anonymous": false,"inputs": [{"indexed": true,"internalType": "uint256","name": "amount","type": "uint256"},{"indexed": false,"internalType": "string","name": "reason","type": "string"},{"indexed": false,"internalType": "address","name": "donatorAddress","type": "address"},{"indexed": false,"internalType": "uint256","name": "timestamp","type": "uint256"}],"name": "LogData","type": "event"},{"inputs": [{"internalType": "string","name": "donationReason","type": "string"}],"name": "offerDonation","outputs": [],"stateMutability": "payable","type": "function"},{"inputs": [],"name": "onlineEducator","outputs": [{"internalType": "address payable","name": "","type": "address"}],"stateMutability": "view","type": "function"}]')
    address = w3.to_checksum_address(
        '0x98544219dd60eCc071302dAfBfce22F74334f244')
    contract = w3.eth.contract(address=address, abi=abi)


    event_signature = "LogData(uint256,string,address,uint256)"
    over_45_wei = []
    # Iterate over the blocks from earliest to latest and retrieve the event logs
    for block_number in range(1444600, 1444700):
        logs = w3.eth.get_logs({
            "address": '0x98544219dd60eCc071302dAfBfce22F74334f244',
            "fromBlock": block_number,
            "toBlock": block_number,
            "topics": [w3.keccak(text=event_signature).hex()]
        })

        for log in logs:
            print(log)
            event_data = {
                "amount": int.from_bytes(log['topics'][1], byteorder='big'),
                "reason": log["data"],
            }
            print(event_data['amount'])
            print(event_data['reason'])

            if event_data['amount'] > 45:
                over_45_wei.append(event_data['amount'])
    print(over_45_wei)

    return no_update


if __name__ == '__main__':
    app.run(debug=True)


The code above is fully functional. For it to run on your computer, all you need to do is:

  1. save the code as a .py file
  2. pip install dash
  3. pip install dash-bootstrap-components
  4. pip install web3

Best Answer

As you can see in the image below the Data field consists of five different attributes and by calling through the below web3 API it returns the hex bytes of those five attributes combined.

w3.eth.get_logs({
            "address": '0x98544219dd60eCc071302dAfBfce22F74334f244',
            "fromBlock": block_number,
            "toBlock": block_number,
            "topics": [w3.keccak(text=event_signature).hex()]
        })

So basically when you do this "reason": log["data"], you are assigning the hex bytes of all five attributes.

all you have to do is to take the hex bytes of your required attribute which is the last 64 characters in this case and then convert it to text. below is the solution.

event_data = {
                "amount": int.from_bytes(log['topics'][1], byteorder='big'),
                "reason": w3.to_text(log["data"][-64:]),
            }

PS: w3.to_string() wasn't working for me, so I used w3.toString().

Event log