New to blockchain software development? Read my beginners guide here

A guide to emitting events in Solidity

Created on October 2022 • Tags: guidessolidityethereum

An in-depth guide to events in Solidity and events on the EVM/Ethereum smart contracts


Table of Contents for A guide to emitting events in Solidity


Events are a very useful feature of smart contracts on the ethereum blockchain.

You can emit events (which can be shown when looking at a transaction on sites like etherscan, or listened to by JS libraries such as web3/ethers).

Emitted events are stored on the blockchain, and are easy to access/read when looking at a smart contract event - but smart contracts themselves cannot see emitted events (for their own contract or other contracts).

There are two main parts - defining it and emitting it.

How to define an event in Solidity

define the type of event. This has two parts:

  • the event name itself (yourEventName in the following example)
  • and an optional list of parameters (with indexed or anonomous modifiers), such as a number (uint) in the following case:
event YourEventName(uint anEventParameter);
event TransferSomething(address indexed from, address indexed to, uint256 amount);

The second example event (above) has 3 params - two are address, one is a uint256. The two address ones also have indexed (more on that later).

Where to define events in Solidity - these normally go at the top of your smart contract.

Note: each param for an event is stored as a separate 32 byte bit of data. So storing less than uint256 (e.g. uint8 or even bool) doesn’t save data.

Under the hood it calls opcodes such as log4 - which logs 4 ‘topics’. The first topic is the keccak256 hash of the event signature. Each topic is 32 bytes. (there are other opcodes such as log3 etc)

Using (emitting) an event in Solidity

Once an event is defined, you can emit it in your functions:

emit yourEventName(1234);

Full example of a smart contract using events in Solidity

pragma solidity ^0.8.16;

contract CryptoGuideDevExample {
uint256 public myCounter = 0;

event Increment(address whoIncremented, uint256 currentValue);

function addToCounter() public {
emit Increment(msg.sender, myCount);
value += 1;
}
}

In the Solidity example above, when addToCounter() is called, it will emit the Increment event. that event contains two parameters - who incremented it (the address of who called the function), and the current value (before incrementing the value)

What are indexed events

You can add the indexed keyword when defining event parameters in Solidity:

  event MyEvent(
uint256 indexed amount,
address from,
address indexed to
);

This makes it much easier for you to search for events (in an off-chain application).

You can have up to three indexed parameters for a Solidity event.

The indexed params are stored in a concept known as topics instead of the normal data part of the event log.

This can help to access them later (you can search for them).

If there is an indexed param where the type is a reference type (which is larger than 32 bytes - for example strings), then what gets stored as the topic is the keccack256 hash of the data.

Note: non indexed attributes are encoded (abi-encoded) and stored on the data part of the event log.

How to get the keccak256 hash selector for an event

You can use the event.selector (e.g. myEvent.selector), which returns a bytes32 hash.

For a guide on function selectors, see here

How the event logging functions work (deep dive)

There are five EVM opcodes for storing event logs: log0, log1, log2, log3, log4`.

(note: when you write solidity you don’t need to use them, you just call emit YourEvent(param1), but under the hood it will translate it to these opcodes. For just learning Solidity you don’t need to read this deep dive section).

log1 (costs 375 gas) has 2 params - offset, size. This adds a event log record, with no topics. (remember: topics = indexed params). log1 costs more : 750 gas. And it adds another param, for the first (and only topic) which is a 32 byte value. log2 costs 1125 gas, log3 costs 1500 and finally log4 costs 1875.

(note: it also costs gas to use the memory, at 3 gas per byte, and gas per byte of log data at 8 gas).

The offsets are used for the main data (non indexed params).

Strings/bytes can only be more than 32 bytes in event logs if they’re indexed. As as explained above, then the keccack256 digest is stored (which is a pointer to where the data exists).

If you work in Solidity, like most of us, then you can have at most 3 topics (topics = indexed params). But really the EVM supports 4 topics. Solidity uses the first topic as the event’s signature. (this is indexed - so this is why you can filter by event type).

The simplest way to add an event log is with log0. This logs data, but has no topics (indexed params).

log0(0xfefefe); 
// log0(offset, size);
// log without topics and data mem[offset...(offset+size))

This will log data (from offset, to offset+size)

But if you want to start using topics, then it gets more interesting

Remember, topics are 32 bytes of data. You send the data (offset, size), and also 32 bytes for each topic.

e.g. log2 would have 3 args - first is for the data offset/size, then two 32 byte topics

log2(0xfefefe, 0x00000001, 0x11111110);

Spotted a typo or have a suggestion to make this crypto dev article better? Please let me know!

See all posts (70+ more)

See all posts (70+ more)

Was this post helpful? 📧

If you liked this content and want to receive emails about future posts like this, enter your email. I'll never spam you.

Or follow me on @CryptoGuide_Dev on twitter

By using this site, you agree that you have read and understand its Privacy Policy and Terms of Use.
Use any information on this site at your own risk, I take no responsibility for the accuracy of safety of the information on this site.