Events
Events are custom data structures that are emitted by smart contracts during execution. They provide a way for smart contracts to communicate with the external world by logging information about specific occurrences in a contract.
Events play a crucial role in the creation of smart contracts. Take, for instance, the Non-Fungible Tokens (NFTs) minted on Starknet. All of these are indexed and stored in a database, then displayed to users through the use of these events. Neglecting to include an event within your NFT contract could lead to a bad user experience. This is because users may not see their NFTs appear in their wallets (wallets use these indexers to display a user's NFTs).
Defining events
All the different events in the contract are defined under the Event enum, which implements the starknet::Event trait, as enum variants. This trait is defined in the core library as follows:
trait Event<T> {
fn append_keys_and_data(self: T, ref keys: Array<felt252>, ref data: Array<felt252>);
fn deserialize(ref keys: Span<felt252>, ref data: Span<felt252>) -> Option<T>;
}
The #[derive(starknet::Event)] attribute causes the compiler to generate an implementation for the above trait,
instantiated with the Event type, which in our example is the following enum:
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
StoredName: StoredName,
}
#[derive(Drop, starknet::Event)]
struct StoredName {
#[key]
user: ContractAddress,
name: felt252
}
Each event variant has to be a struct of the same name as the variant, and each variant needs to implement the starknet::Event trait itself.
Moreover, the members of these variants must implement the Serde trait (c.f. Appendix C: Serializing with Serde), as keys/data are added to the event using a serialization process.
The auto implementation of the starknet::Event trait will implement the append_keys_and_data function for each variant of our Event enum. The generated implementation will append a single key based on the variant name (StoredName), and then recursively call append_keys_and_data in the impl of the Event trait for the variant’s type .
In our contract, we define an event named StoredName that emits the contract address of the caller and the name stored within the contract, where the user field is serialized as a key and the name field is serialized as data.
To index the key of an event, simply annotate it with the #[key] as demonstrated in the example for the user key.
When emitting the event with self.emit(StoredName { user: user, name: name }), a key corresponding to the name StoredName, specifically sn_keccak(StoredName), is appended to the keys list. useris serialized as key, thanks to the #[key] attribute, while address is serialized as data. After everything is processed, we end up with the following keys and data: keys = [sn_keccak("StoredName"),user] and data = [address].
Emitting events
After defining events, we can emit them using self.emit, with the following syntax:
self.emit(StoredName { user: user, name: name });