diff --git a/docs/basics/events.md b/docs/basics/events.md index 098d80fba1..229bd6f1de 100644 --- a/docs/basics/events.md +++ b/docs/basics/events.md @@ -62,32 +62,104 @@ for an elaborate example which uses events. ## Event Definition +Since ink! version 5.0, events can be defined independently of the contract which emits them. +Events can now be defined once and shared across multiple contracts. + +This is useful for events for contracts which conform to standards such as ERC-20: +contract indexers/explorers are now able to group all e.g. `Transferred` events. + This is how an event definition looks: ```rust -#[ink(event)] +use ink::primitives::AccountId; + +#[ink::event] pub struct Transferred { #[ink(topic)] from: Option, - #[ink(topic)] to: Option, + amount: u128, +} +``` +> Note that generics are [not currently supported](https://github.com/paritytech/ink/issues/2044) +> , so the concrete types of `Environment` +> specific types such as `AccountId` must match up with the types used in the contract. - amount: Balance +This definition can exist within a contract definition module (inline events), in a different +module in the same crate or even in a different crate to be shared by multiple contracts. -} +### Legacy syntax for inline Event definitions + +Events defined within a `#[ink::contract]` module can continue to use the original syntax for an +event definition, using the `#[ink(event)]` attribute. Under the covers this is simply expanded +to the new top level `#[ink::event]` macro, so both events defined using the legacy style and +using the new `event` attribute macro directly will behave exactly the same. + +### Topics + +When an event is emitted, 0 or more topics can be associated with it. The event is then indexed +together with other events with the same topic value. + +An event's fields can be annotated with `#[ink(topic)]` (see example), which will result in a +topic derived from the value of that field being emitted together with the event. + +Topics are by default a 32 byte array (`[u8; 32]`), although this is configurable on the +Polkadot SDK runtime level. If the SCALE encoded bytes of a field value are <= 32, then the +encoded bytes are used directly as the topic value. + +For example, in the common case of indexing a field of type `AccountId`, where the default +`AccountId` type is 32 bytes in length, the topic value will be the encoded account id itself. This +makes it easy to filter for all events which have a topic of a specific `AccountId`. + +If however the size of the encoded bytes of the value of a field exceeds 32, then the encoded +bytes will be hashed using the `Blake2x256` hasher. + +> Topics are a native concept in the Polkadot SDK, and can be queried via [`EventTopics`](https://docs.rs/frame-system/latest/frame_system/pallet/storage_types/struct.EventTopics.html) + +How to choose which fields to make topics? A good rule of thumb is to ask yourself if somebody +might want to search for this topic. For this reason the `amount` in the example `Transferred` event +above was not made indexable ‒ there will most probably be a lot of different events with differing +amounts each. + +#### Signature Topic + +By default all events have a signature topic. This allows indexing of all events of the same +type, emitted by different contracts. The `#[ink::event]` macro generates a signature topic at +compile time by hashing the name of the event concatenated with the *names of the types* of the all +the field +names: +``` +blake2b("Event(field1_type,field2_type)")` ``` +So for our `Transferred` example it will be: +``` +blake2b("Transferred(Option,Option,u128)")` +``` +> Important caveat: because the *name* of the field type is used, refactoring an event +> definition to use a type alias or a fully qualified type will change the signature topic, even +> though the underlying type is the same. Two otherwise identical definitions of an event with the +> same name and same field types but different field type names will have different signature +> topics. + +When decoding events emitted from a contract, signature topics are now required to determine which +type of event to decode into. -Add the `#[ink(topic)]` attribute tag to each item in your event that you want to have indexed. -A good rule of thumb is to ask yourself if somebody might want to search for this topic. -For this reason the `amount` in the exemplary event above was not -made indexable ‒ there will most probably be a lot of different events with -differing amounts each. +#### Anonymous Events -The signature of the event is by default one of the topics of the event, except -if you annotate the event with `#[ink(anonymous)]`. -See [here](/macros-attributes/anonymous) for details on this attribute. +Annotating an event definition with `#[ink(anonymous)]` (See [here](/macros-attributes/anonymous) +for details on this attribute) prevents a signature topic from being generated and published with the +event. +It means that an indexer will not be able to index over the type of the event, which may be +desirable for some contracts, and would be a small gas cost optimization if necessary. + +However, when interacting with the contract from a client, no signature topic means that another +way is required to determine the type of the event to be decoded into (i.e. how do we know it is +a `Transferred` event, not an `Approval` event. One way would be to try decoding for each type +of event defined in the metadata of the contract until one succeeds. If calling a specific +`message`, it may be known up front what type of event that message will raise, so the client +code could just decode into that event directly. ## Emitting Events in a Constructor @@ -128,3 +200,12 @@ pub fn transfer(&mut self, to: AccountId, amount: Balance) -> Result { Ok(()) } ``` + +## Cost of using Events + +When using events and topics, developers should be mindful of the costs associated. Firstly: if +optimizing for contract size, using events will increase the size of the final code size. So +minimizing or eliminating event usage where necessary will reduce contract size. The same can be +said for the execution (aka gas) costs when using events. We recommend considering the cost of +events when using them, and measuring the code size and gas costs with different usage patterns +when optimizing. diff --git a/docs/macros-attributes/event.md b/docs/macros-attributes/event.md index 1534754f35..f6edcc68a6 100644 --- a/docs/macros-attributes/event.md +++ b/docs/macros-attributes/event.md @@ -1,5 +1,5 @@ --- -title: "#[ink(event)]" +title: "#[ink::event]" slug: /macros-attributes/event hide_title: true --- @@ -10,5 +10,7 @@ Applicable on `struct` definitions. Defines an ink! event. A contract can define multiple such ink! events. +Events can now be defined independently of contracts. The legacy syntax of events defined +within a contract module using `#[ink(event)]` continues to be valid. [See our section on Events](/basics/events) for a detailed description and examples.