1

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

1 Answer 1

1
+50

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

7
  • 1
    Thank you so much, @UsmanFarooq. It worked :) How did you figure out that the attribute I was looking for (reason) was the last 64 characters? What does that depend on? Is that because each data variable is encoded with 16 characters, and there were 4 variables prior to one I was looking for? Commented Jul 15, 2023 at 20:22
  • You are welcome, I am happy to help @adam . To know this first you have to understand how event logging works. while logging an event, indexed and non-indexed values are separated, indexed values are recorded in a special data structure called Topics, from where you are getting your amount value, and then non-indexed values are ABI-encoded into the Data part of the log. ABI-encoding works by separating static and dynamic values, static values are encoded first and then dynamic values. Since reason is a string and it is dynamic that's why it is encoded at the end. Commented Jul 15, 2023 at 23:58
  • 1
    That's how I figured out. and yes each data variable is encoded with 64 characters. I hope I made sense to you. Find more about Event Logging and ABI-encoding Commented Jul 15, 2023 at 23:59
  • Usman Farooq do you know how I would encode the second data variable, which is the address of the sender? I tried doing w3.to_text(log["data"][64:128]). I used that range because it's the second variable in the Data logs. But I keep getting an error. Commented Jul 17, 2023 at 16:37
  • 2
    @AdamSchroeder Yes you are right but in this case, you also need to consider two more things, 1- The first 0x is not part of encoded data, it is there just because every Hex value is returned with a 0x prefix so you need to start counting from the 3rd index, considering this our counting should start as log["data"][66:130], when you do this you get your 64 characters which is 32 bytes. 2- An address consists of only 20 bytes, which is why you have to ignore the first 12 bytes which are the first 24 characters, hence log["data"][90:130]. Commented Jul 17, 2023 at 20:20

Not the answer you're looking for? Browse other questions tagged or ask your own question.