Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update events docs #297

Merged
merged 7 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 93 additions & 12 deletions docs/basics/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccountId>,

#[ink(topic)]
to: Option<AccountId>,
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
An event's fields can be annotated with `#[ink(topic)]` (see example), which will result in a
An event's field can be annotated with `#[ink(topic)]` (see example), which will result in a

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is meant to convey that multiple individual fields can be annotated with the topic.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it actually configurable? I thought ink is hardcoded to use blake 256 still

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type of the resulting topic is configurable yes. So if it is shorter I guess it will be truncated and longer padded.

https://github.com/paritytech/ink/blob/27407b5f59f24233cddc3cea59cc2213ba2c6674/crates/env/src/engine/on_chain/impls.rs#L157

Our way of generating the topic value itself is hardcoded to blake2.

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<AccountId>,Option<AccountId>,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

Expand Down Expand Up @@ -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.
4 changes: 3 additions & 1 deletion docs/macros-attributes/event.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: "#[ink(event)]"
title: "#[ink::event]"
slug: /macros-attributes/event
hide_title: true
---
Expand All @@ -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.