Learn Solidity: Events

How to use events for logging

wissal haji
Better Programming

--

person smiling as they check their smart phone
Photo by Emmanuel on Unsplash

Welcome to another article in the Learn Solidity series, which aims to introduce you to Ethereum programming.

In the last article, we have seen how to use web3.js to build a dapp. In today’s article, we will see how to use web3.js to read events from the blockchain.

Events in Solidity act like the logging functionality that you’re used to with other languages, except that instead of logging to the console or to a file, the logs are saved in the Ethereum blockchain. In the following sections we will see:

  • How to declare and trigger an event
  • What logsBloom is and how it helps to efficiently search the blockchain
  • How to read an event from the blockchain
  • How to subscribe to an event

How To Declare and Trigger an Event

In order to declare an event, you need to use the following syntax:

The definition of the event contains the name of the event and the parameters you want to save when you trigger the event.

Once you have declared your event, you can emit an event from within a function, as follows:

emit MyEvent(block.timestamp, 'hello');

Let’s build an example to see how this actually works.

In your terminal, create a new directory and use truffle init to build a new truffle project. Open the project using your favorite code editor and create a new contract named EventExample.sol in the contracts folder, then copy-paste the following code:

Create the corresponding migration file 2_deploy_eventExample.js:

const EventExample = artifacts.require("EventExample");module.exports = function (deployer) {
deployer.deploy(EventExample);
};

Let’s deploy now the contract using truffle migrate, but before migrating don’t forget to edit your truffle-config.js file to configure the network and the compiler.

In your terminal, start the Truffle console using truffle console.
We’re going to call the storeData function and examine the logs of the created transaction. Let’s start by getting an instance of the deployed contract:

let eventExample = await EventExample.deployed()

We can now call the function using:

let tx = await eventExample.storeData(10)

If you print the content of the transaction receipt logs tx.receipt.rawLogs, which store an array of the triggered events that happened during the execution of the transaction, you will get an array with one object that looks like this:

content of receipt transaction log
Transaction log

You can see that the event data is stored under the data field in hexadecimal.

How logsBloom Helps to Efficiently Search the Blockchain

To efficiently scan the blockchain looking for events with certain data, you can add the indexed keyword in front of the event data that you want to use to filter the events recorded in the blockchain.

event DataStored(uint256 data1, uint256 indexed data2)

Let’s try it out in the example we’ve just built:

You need to redeploy the contract using migrate --reset.

Repeat the same previous steps for getting an instance of the deployed contract and calling the storeData function:

> eventExample = await EventExample.deployed()
> tx = await eventExample.storeData(10, 15)

Then examine the tx.receipt.rawLogs. You will see the following result:

content of receipt transaction log
Transaction log

The data1 field is displayed under data whereas the indexed field data2 is displayed under the topics field. As a rule, the indexed parameters go under topics and the non-indexed parameters go under data.

The first topic is the hash of the signature of the event, described in the Solidity docs:

topics[0]: keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")
(canonical_type_of is a function that simply returns the canonical type of a given argument, e.g. for uint indexed foo, it would return uint256). If the event is declared as anonymous the topics[0] is not generated;

Note: The maximum number of parameters that can be indexed in an event is three.

Each block header contains a field called logsBloom and this is the Bloom filter composed of indexable information (log address and log topic) contained in the receipt for each transaction in the transactions list portion of the block.

When looking for specific events that belong to a certain address or contain certain data in the entire blockchain or part of it, the node goes through the block headers and examines the Bloom filter of each block in order to decide if that block contains the desired log.

How To Read an Event From the Blockchain

Events are meant to be consumed from outside the blockchain, and this can be done either from the frontend or from a Node.js project.

To make the client simple, I’m going to demonstrate how to read events from a Node project.

Start by creating a new folder called client inside the project and execute the following commands:

> npm init -y
> npm i web3 --save
> touch index.js

The content of index.js will be as follows:

I have used WebSocket provider instead of HTTP provider since the latter does not support subscriptions.

There are actually three ways to read events data from the blockchain:

  1. From the receipt of a transaction — This is what we did in the previous index.js. If you execute node index.js, you will get the data and topics.
  2. Subscribe to an event — As described in this section of the web3 docs, there are three ways to subscribe to an event:
  • once: Subscribes to an event and unsubscribes immediately after the first event or error; will only fire for a single event. Remove the code containing the transaction execution in index.js file and replace it with the following:
eventExample.once("DataStored", (error, event) => {
if (!error) console.log(event);
});

From your terminal, start the Node project by executing node index.js. You won’t see anything printed since it’s still waiting for the first DataStored event. Open another terminal and get the deployed contract instance. Then call the storeData function. You will now see an event printed in the first console.

  • events: Subscribe to an event. The subscription is done as follows: myContract.events.MyEvent([options][, callback]). In this example, we will watch for the DataStored event that contains data2 with a value of 5 or 9.
eventExample.events
.DataStored({ filter: { data2: [5, 9] } })
.on("data", (event) => {
console.log(event);
});
  • events.allEvents: This is the same as events but receives all events from this smart contract. Optionally the filter property can filter those events.

3. Get previous events — This can be done using getPastEvents by calling it as follows: myContract.getPastEvents(event[, options][, callback]). the full list of options is described in the docs. One important option is fromBlock since if it’s not specified, you will get only the events of the latest block.

eventExample.getPastEvents("DataStored", { fromBlock: 0 }).then((events) => console.log(events));

Conclusion

As further reading, you can take a look at the events section in the Solidity documentation.

I hope that you’ve enjoyed this article. If you did, then stay tuned for upcoming articles.

--

--