The nitty-gritty of Ethereum and Solidity : Events.

Alberto Molina
Coinmonks
5 min readSep 7, 2023

--

Ethereum events are logs that smart contracts can generate. At a very low-level, events are bytes of information saved onto the blockchain, they cannot be modified, they cannot be removed, they cannot be read by smart contracts, only off-chain applications can retrieve events.

They are essential for creating transparency, enabling communication between smart contracts and DApps, and enhancing the overall efficiency of the Ethereum ecosystem.

Events purpose

At its core, Ethereum is a decentralized and immutable ledger. It records transactions, contract executions, and state changes in a series of blocks, forming a chain of data. However, simply having this data stored on the blockchain is not enough to create an interactive and responsive decentralized application.

Ethereum events bridge the gap between the blockchain and DApps by allowing smart contracts to communicate important information about their state changes. In other words, events are logs that help dapps trace historical changes of a given smart contract.

Events are linked to the smart contract that emitted them so that off-chain applications can track only the events they are interested in (they can filter by smart contract, by event name etc…)

Events structure

The structure of an Ethereum event is relatively straightforward. It includes a name, which describes the event, and a set of indexed and non-indexed parameters.

Indexed parameters are filterable, making it easier to retrieve specific events from the blockchain. DApps and services can use these parameters to efficiently query and retrieve relevant information. An event can have at most 3 indexed parameters.

Events in Solidity

Let’s define an event called “MyEvent” that we will use throughout the whole blog as an example.

MyEvent will have 2 indexed parameters (an address called “sender” and a uint256 called “timestamp”) and one non-indexed parameter (a string called “data”)

Now let’s define the MyEvent event in Solidity (using Remix) and implement a function “emitEvent” that emits it :

event MyEvent(
address indexed sender,
uint256 indexed timestamp,
string data
);

function emitEvent(string calldata _data) external {
emit MyEvent(msg.sender, block.timestamp, _data);
}

Events layout and decoding

Whenever we call the emitEvent function, the transaction response will include information about the MyEvent event that emitEvent emits. It will look like this:

[
{
"logIndex":1,
"blockNumber":5,
"blockHash":"0xdb55f321abf99a5970017e4a6ee8b4f9919ab3f7bfbedaa32d9c0474d919d433",
"transactionHash":"0x0db2f7d8bd9199b0771c3db834c5174725eeaa028c8d12318d5eafeaa2e3dbdb",
"transactionIndex":0,
"address":"0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B",
"data":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e6161616161616161616161616161616161616161616161616161616161610000",
"topics":[
"0x07500aea334cf38e05f3afb4bf357ed3591d1dcbff9a5b4d07b607bef9768ef9",
"0x0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4",
"0x0000000000000000000000000000000000000000000000000000000064f97028"
],
"id":"log_26e56e8e"
}
]

The response will include a list of events that were emitted during the transaction by any function that was executed (from any contract invoked during the transaction) in the exact same order they were emitted.

It is important to note that the “timestamp” of all the events within the same transaction is the same, but the events will help us check the order in which the functions were executed, which can be very helpful debugging code issues.

Here’s a breakdown of the standard layout of an event:

  1. Topics: Ethereum events have one or more topics, which are 32-byte hash values. The first topic is typically the hash of the event signature (the event’s name and parameter types), and the subsequent topics correspond to indexed parameters of the event. Topics must be 32 bytes long, so if you index a value that is less than 32 bytes long it will get 0 padded, on the other hand if you index a value that is more than 32 bytes long it will get hashed (*). Since an event can have at most 3 indexed parameters, the maximum number of topics is 4.
  2. Data: The data section of an event log contains the event’s non-indexed parameters. This data is not directly searchable or filterable, and its contents are encoded and stored in hexadecimal format.
  3. Address: The address of the smart contract that emitted the event is included in the log entry. This helps identify which contract emitted the event.
  4. Block Information: Each log entry also contains information about the block in which the event was included, such as the block number, block hash, etc….

(*) It is important to note that, if you index a string or a bytes parameter, the value will be hashed and it will be impossible to decode it.

Once decoded the event will actually look like this:

this may depend on the library you are using to decode the event, but the information will in most cases be very similar

[
{
"from": "0xD7ACd2a9FD159E69Bb102A1ca21C9a3e3A5F771B",
"topic": "0x07500aea334cf38e05f3afb4bf357ed3591d1dcbff9a5b4d07b607bef9768ef9",
"event": "MyEvent",
"args": {
"0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"1": "1694068776",
"2": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"sender": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"timestamp": "1694068776",
"data": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}
]

You can test it yourself using remix just like I did.

Events definition

Events must be defined in the smart contracts ABIs for off-chain programs to track and decode them. This is how the MyEvent event looks like in the contract’s ABI:

{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "timestamp",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "data",
"type": "string"
}
],
"name": "MyEvent",
"type": "event"
}

Events opcodes

The EVM opcodes used by smart contract to emit events are the following ones :

  • LOG0 : emits an event with 0 topics (no indexed parameter, no event signature), only X bytes of non-indexed data = X bytes.
  • LOG1 : emits an event with 1 topic (the event signature) plus X bytes of non-indexed data = 32 + X bytes.
  • LOG2 : emits an event with 2 topics (the event signature + 1 indexed parameter) plus X bytes of non-indexed data = 64 + X bytes.
  • LOG3 : emits an event with 3 topics (the event signature + 2 indexed parameter) plus X bytes of non-indexed data = 96 + X bytes.
  • LOG4 : emits an event with 4 topics (the event signature + 3indexed parameter) plus X bytes of non-indexed data = 128 + X bytes.

Events Gas cost

Events are considered to be relatively cheap, they can sometimes be used to store information in the blockchain instead of using a smart contract’s storage (this has however some disadvantages, but this topic is beyond the scope of this blog).

An events gas cost depends on the number of bytes been emitted (data_size), the number of indexed topics the event has (num_topics) plus the cost of memory expansion required for the event (mem_expansion_cost).

The “memory expansion” is 0 if the LOG operation does not access a memory address higher than the existing highest referenced address.

The formula to calculate the gas of a LOGx opcode is :

gas_cost = 375 + 375 * num_topics + 8 * data_size + mem_expansion_cost

--

--