In today's article, we will take a look at Solidity event, more commonly known as logs in Ethereum and EVM. We'll see how to use them, their definitions and how to use event topic hashes and signatures to filter logs, as well as some advice on when you should use these.
We will also cover inspection-events-interactions pattern, this well-known pattern is traditionally applied to reentrancy of state variables, but we will see why such a pattern should also be applied to triggering events and the potential risks and security vulnerabilities involved.
How to define events in Solidity?
Can be usedeventKeyword defines events in Solidity as shown below.
Please note that only after Solidity v0.8.15, events.selector Only members can use it.
If you look at any blockchain logs issued, you will find that the logs Index of topic0 (first) The entry's corresponding event topic hash. Since topics are searchable content across logs, we can filter using the event topic hash:
Search for a specific event within a smart contract at a specific address.
In the area Search all contracts on the blockchain for a specific event.
us As will be seen further below, anonymousanonymous Events are exceptions to this rule. The anonymous keyword makes them not searchable and therefore Use the term "anonymous".
Based on this fact, we can also infer that Solidity The simplest event defined, with no parameters, such as the event defined aboveBulbReplacedorSwitchedON , will use the LOG1 opcode under the hood. Trigger topics in the log because the events themselves are searchable.
More themes can be added, other themes will be used< code>LOG2, LOG3, LOG4andLOG5< /code>, as long as these parameters are marked as indexed code>. Let's take a look at index parameters in the next section.
Event parameters and index parameters
Events can accept parameters of any type, including value types (uintN, bytesN< /span>, bool, address...),struct, enum and user-defined value type.
Based on my research while writing this article, the only types that are not allowed are internal functions type. External function types are allowed, but internal function types are not. For example, the following code will not compile.
If the event is declared asanonymous, in the contract ABI, event"anonymous"The field will be marked as true.
One advantage of anonymous events is that it makes your contract cheaper to deploy, and also cheaper in terms of gas when triggered.
A good use case for anonymous events is for contracts with only one event. It makes sense to listen to all events in the contract because only this one event will appear in the event log. Subscribing to its name is irrelevant since only a single event is defined to be emitted by the contract. Therefore, you can define events as anonymous and subscribe to all event logs from the contract and confirm that they are all the same event.
See examples of anonymous events being used in popular code libraries, such as on DappHub DS-Note contract [7].
We can As seen in the code snippet, since the event is declared anonymous, this makes it possible to define a fourth "indexed" parameter.
Please note that since anonymous events do not have a bytes32 topic hash, anonymous events do not Supports the .selector member.
Use LOG opcode to trigger events in assembly
It is possible to trigger events in assembly, using logN code> instruction, which corresponds to the opcode in the EVM instruction set.
To trigger an event in assembly, you must include all the data to be emitted by the event Stored at a specific location in memory.
Once you have the data to be emitted by the event stored in memory, you can then The following parameters are specified for the logN directive:
p = The memory location to start fetching data from. Basically this is a memory pointer, or an "offset" or "memory index", depending on what you call it.
s = you want to start at p in the event The number of bytes emitted in .
All other parameterst1, t2, t3 and t4 code> are all event parameters you want to be indexable. Please note that there are two important things here: 1) these parameters should be the same as the parameters defined in your event definition in the same order, 2) these parameters should be placed in memory to obtain the data.
The code snippet below shows how to Do this in assembly.
function _emitEventAssembly(bytes32 tokenId) internal{ bytes32 topicHash = ExampleEventAsm.selector;
assembly { let freeMemoryPointer := mload(0x40) mstore(freeMemoryPointer, topicHash) mstore(add(freeMemoryPointer, 32), tokenId) < br> // emit the `ExampleEventAsm` event with 2 topics log2( freeMemoryPointer , // `p` = starting offset in memory 64, // `s` = number of bytes in memory from `p` to include in the event data< br> topicHash, // topic for filtering the event itself tokenId // 1st indexed parameter ) } }
Gas cost of event
All record opcodes (LOG0, LOG1, LOG2, LOG3, LOG4) all need to consume gas. The more parameters (topics) they have, the more gas they consume.
In addition, Other factors like indexing or data size can also cause event emission to consume more gas.
Check-Event-Interactive Mode
Check-valid - Interactive mode [9] also works for events.
One way to detect these patterns is to use the Remix static analysis tool.
This pattern can also be detected by Slither. When running slither against a contract that fires an event after an external call, you will get a discovery that says "Reentrant Event".
So, for dApps, order is important so that you can get it right to see which event was emitted first, next, and last. This is especially important in the case of recursive or reentrant calls. If the event is triggered after an external call, and this external call makes a reentrant call, then:
The first event emitted is the event after the second reentrant call has completed.
The second event emitted is the initial transaction event emitted later.
Understanding this also makes it possible to A clear audit trail is provided off-chain to monitor contract calls. You can see which functions were called first and last, and the order in which each routine was run during the execution of a trade.
slither detector documentation[10] - Solidity and Vyper's static analyzer.
This potential vulnerability is also present in Trail of Bits against Liquity[ 11] Found and reported in the audit of smart contracts.
When should the event be triggered?
There may be several situations in your contract where triggering an event may be important and useful.
When When restricted users and addresses perform certain actions (for example: owner or contract administrator). This includes, for example, the popular transfer ownership (address) Function that can only be called by the owner to change the owner of the contract.
Change some key variables or arithmetic parameters, which are responsible for the core logic of the contract. This is especially important in the context of DeFi protocols.
More information about these situations is described in the Slither detector documentation [12].
This is also described in Trail's audit report on LooksRare.
Monitor contracts deployed in production to detect anomalies.
View the details of 0xprotocol[13] for security-related issues surrounding the incident.
Reference
Anonymous event usage Missing documentation for purpose (know why)[14]
[Advantages of anonymous events]
Preview
Gain a broader understanding of the crypto industry through informative reports, and engage in in-depth discussions with other like-minded authors and readers. You are welcome to join us in our growing Coinlive community:https://t.me/CoinliveSG