Skip to content

Smart Contract Event Monitoring in Solidity

In the context of Solidity and the Ethereum blockchain, events provide a means for smart contracts to emit logs that are stored on the blockchain and can be retrieved by external entities. They are essential tools for monitoring changes to smart contracts and have wide-ranging applications, from tracking transactions to updating user interfaces. In other cases, developers use events to conveniently store certain pieces of data on-chain that would otherwise not be easily retrievable. This practice often is used to increase data redundancy.

Understanding Events

In Solidity, events are directly defined within the interface definition or the contract and dispatched using the emit keyword. It's good to have event definitions in the contract body for smaller contracts, as a separate interface definition might be redundant.

1
2
3
4
5
6
7
contract EventGreeter {
    event Greeting(address indexed sender, string message);

    function say(string _message) public {
        emit Greeting(msg.sender, _message);
    }
}

In this example, the Greeting event is emitted whenever the say function is called. It logs the sender's address and the greeting string passed to the function.

For more complex systems, events should be located in the interface, together with the error and function header definitions and other supporting data. This helps neatly separate the business logic from the rest of the code and increase readability.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// e.g. in interfaces/IEventGreeter.sol
interface IEventGreeter {
    event Greeting(address indexed sender, string message);

    function say(string calldata _message) external;
}

// e.g. in EventGreeter.sol
contract EventGreeter is IEventGreeter{
    function say(string calldata _message) public {
        emit Greeting(msg.sender, _message);
    }
}

Events can also have indexed parameters (up to three) denoted by the indexed keyword. Indexed parameters are unique in that they filter logs based on these parameters. The Ethereum JSON-RPC API standard provides the eth_getLogs method to retrieve logs based on indexed parameters. This method returns an array of log objects based on a filter object that the user provides.

Event Signatures

Firstly, the event signature is required to filter for Greeting events. It can be generated from the Kecchak-256 hash of the event definition; in this case, it's Greeting(address,bytes32). Note that the spaces between the commas and the variable names have been removed. This results in the signature 0xbd6c018604e259478c316f248542b74f84025ab0e9274fa09d682a3589d115df. In the JSON-RPC naming scheme, events are named logs with an array of topics. The first topic is always the event signature, which can be accessed as topic[0]. Any subsequent topics are indexed fields of the events. Data that is not indexed is stored as-is and cannot be efficiently queried or filtered. This is why it's easy to query for all Greetings coming from a particular address, but not efficiently possible to filter all greetings based on the message they contain.

Reference Types

A topic can only hold 32 bytes of data. It is however possible to add a reference type to an event. This can be a Solidity mapping, array, or a struct. In this case, Solidity will take the Kecchak-256 hash of the type's value as the topic data.

More information on reference types can be found in the Solidity documentation.

For example, let's say we're interested in Greeting logs where the sender is a specific address (0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa for this example).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "jsonrpc":"2.0",
    "id":1,
    "method":"eth_getLogs",
    "params":[{
        "fromBlock": "0x1",
        "toBlock": "latest",
        "address": "0x<contractAddress>",
        "topics": [
            "0xbd6c018604e259478c316f248542b74f84025ab0e9274fa09d682a3589d115df",
            "0x000000000000000000000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
        ]
    }]
}

In the params object, fromBlock and toBlock specify the range of blocks to search. The address field contains the contract's address emitting the events. topics is an array where each item is a topic to search for. As explained previously, the first item of topics is always the hash of the event signature. The other items in the topics array are the indexed parameters, left-padded to 32 bytes. In the example's case, the first item is the address to filter by in hexadecimal format.

Please note that only specific values can be searched for when dealing with indexed event parameters. Wildcard searches or partial matching is not supported due to the way Ethereum nodes store indexed data.

When to Use Events

Events should log all significant state changes in a smart contract. Prime examples are accounting-related actions such as deposits and withdrawals. The final result will be a detailed and reliable log of all financial transactions.

Beyond this, events are also helpful for monitoring changes to contract settings made by authorized personnel. For example, suppose the owner of a smart contract can change specific system parameters. In that case, every change should be accompanied by an event. Users and third-party infrastructure can then pick up on the event and reflect the change accordingly, e.g., on a frontend or a dashboard.

Chingiz' Advice

My good colleague and friend Chingiz Mardanov, who is not only skilled at security audits but also at smart contract development has put it as follows:

Over the years I came to the conclusion that at the very least what should be logged is the new state of any storage variable that was affected. Additionally, I like to emit the actor that was affected and the actor who triggered the change. The latter is optional.

Third-Party Services

While Ethereum nodes can listen for and retrieve logs, the process is not always straightforward. Fortunately, third-party services can significantly simplify the task of indexing and analyzing event logs and integrating the results into custom software. Some notable services include:

In conclusion, events are crucial to Solidity and smart contract development. Developers can effectively track and respond to critical actions and changes on their contracts by implementing a robust event logging system and utilizing third-party services. This will lead to more secure, transparent, and reliable decentralized applications.