Events and Logging in Solidity

Solidity events are integral for smart contract developers, allowing smart contracts to be tested for specific variables, frontends to be changed in an automated manner, and much more. In general, knowing how to use events in Solidity makes smart contract development a whole lot easier.

In this post, we’ll look into the logging and events feature of the Ethereum Virtual Machine (EVM) from a smart contract developer’s perspective, covering what logging and events are used for, indexed events, and how to use logging and events in Hardhat and Brownie. 

The EVM is what makes Ethereum and many other blockchains tick. The EVM has a logging functionality used to “write” data to a structure outside smart contracts. One such important piece of data is Solidity events. Events allow us to “print” information on the blockchain in a way that is more searchable and gas efficient than just saving to public storage variables in our smart contracts. 

Logs are a special data structure on the blockchain. They cannot be accessed by smart contracts, and give information about what goes on in transactions and blocks. It’s their inaccessibility to smart contracts that makes them cheaper to emit. 

You can also watch our video on events and logging in Solidity here: 

So, What Is an Event?

Events allow us to easily query “stuff” that happened in blocks and transactions. If you run a blockchain node, you can “listen” for certain events by subscribing to them. In fact, this is how the Chainlink Network works: The networks subscribe to certain events at certain addresses and return data from the real world based on the contents of the events that are emitted. 

What Are Events For?

Now, if you’re not a Chainlink or Ethereum node operator, you may be asking how this affects you. With Solidity events, you can:

  1. Test your smart contracts for specific variables;
  2. Index variables to rebuild storage state;
  3. Listen for events to change a front end;
  4. Create subgraphs for reading data faster;

And many other things. Events have a wide variety of use cases and can save your life as an engineer. In fact, events are one of the core pieces of how Chainlink nodes work! Chainlink nodes listen for data request and external computation events, and that’s how they know to respond. 

What Does an Event Look Like?

This is what defining an event looks like in Solidity:

event storedNumber(
    uint256 indexed oldNumber,
    uint256 indexed newNumber,
    uint256 addedNumber,
    address sender
);

You can think of this as a new special type. We have created a “type” of event called `storedNumber`. The event name is called `storedNumber` and can hold a number of variables. There are two kinds of parameters in this one event: indexed and non-indexed. Indexed parameters are also known as “topics”, and are the searchable parameters in events. We’ll talk more about those shortly. 

We can then emit an event like so:

uint256 favoriteNumber;

function store(uint256 _favoriteNumber) public {
    emit storedNumber(
        favoriteNumber,
        _favoriteNumber,
        _favoriteNumber + favoriteNumber,
        msg.sender
    );
    favoriteNumber = _favoriteNumber;
}

Here is a full example contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract SimpleStorage {
    uint256 favoriteNumber;

    event storedNumber(
        uint256 indexed oldNumber,
        uint256 indexed newNumber,
        uint256 addedNumber,
        address sender
    );

    function store(uint256 _favoriteNumber) public {
        emit storedNumber(
            favoriteNumber,
            _favoriteNumber,
            _favoriteNumber + favoriteNumber,
            msg.sender
        );
        favoriteNumber = _favoriteNumber;
    }

    function retrieve() public view returns (uint256) {
        return favoriteNumber;
    }
}

Now, anytime we call the `store` function in this example, it will emit an event of type storedNumber. Let’s look at a sample transaction that calls the `store` function with an input of `1`. We can see the transaction on Kovan Etherscan.

Scrolling over to the `logs` section of the transaction, we see the following image: 

Screenshot of transaction on Etherscan, decoded

An event is broken down like so:

Address: The address of the contract or account the event is emitted from.

Topics: The indexed parameters of the event.

Data: The ABI-Encoded or “hashed” non-indexed parameters of the event. Since we know the ABI of the contract (since we verified the contract on Etherscan), we can view it in “Dec” or “decoded” mode, or in its raw “hex”, “Hexidecimal”, or “Encoded” mode. If we didn’t have the contract verified, we wouldn’t be able to see the decoded version.

Screenshot of transaction on Etherscan, not decoded

You can read more about the layout of an event in the Solidity docs. “Logs” and “Events” are often used interchangeably, since as smart contract developers we usually only care about the logged “events”. However, a log technically also consists of the `blockhash`, `address`, and everything else returned by calling `eth_getLogs` to your blockchain node. You can also read more about the bloom filter, which is how these events are so easily queried.

Events in Hardhat

Now that we’ve learned what events are, let’s learn how to access and work with them in Hardhat. You can clone the following repo and follow along:

git clone https://github.com/PatrickAlphaC/hardhat-events-logs
cd hardhat-events-logs

You’ll need to follow along with the `README.md` to get the prerequisites, which include Node, Yarn, and Git. 

If you follow along the `README.md`, you’ll be able to:

  1. Deploy a smart contract; 
  2. Create a transaction that emits events;
  3. View the contexts of those events.

If you run into an issue along the way, open up an issue on the Github repo! We can see logs by checking the logs attribute of a `transactionReceipt` object.

console.log(transactionReceipt.events[0].args.oldNumber.toString())

Events in Brownie

Events in Brownie are nearly identical since the contracts are exactly the same. 

You can clone the following repo and follow along:

git clone https://github.com/PatrickAlphaC/brownie-events-logs
cd brownie-events-logs

You’ll need to follow along with the `README.md` to get the prerequisites, which include Node, Python, eth-brownie, and Git. 

If you follow along the `README.md`, you’ll be able to:

  1. Deploy a smart contract; 
  2. Create a transaction that emits events;
  3. View the contexts of those events.

If you run into an issue along the way, open up an issue on the Github repo! You’ll see the main difference here is that we use a print statement to print out the logs of a transaction:

print(tx.events[0]["oldNumber"])

Summary

Logs and events are an important part of smart contract development—and critical infrastructure for projects like Chainlink and The Graph. To learn more about building incredibly powerful smart contracts (utilizing your new event skills), be sure to head over to the Chainlink documentation to start building today.