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

add ./src/anchor_in_depth/events.md #84

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
- [Intermediate](./anchor_in_depth/intermediate.md)
- [Cross-Program Invocations](./anchor_in_depth/CPIs.md)
- [PDAs](./anchor_in_depth/PDAs.md)
- [Events]()
- [Events](./anchor_in_depth/events.md)
- [Constants]()
- [Zero-Copy]()
- [Access Control]()
Expand Down
200 changes: 200 additions & 0 deletions src/anchor_in_depth/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Events

Events are an incredibly powerful feature in Anchor. Rather than polling or refreshing to
find out if an account's state has changed, events are like callbacks that save space, compute,
and enable asynchronous programming.

The main downside to events are that they are base64 encoded, and therefore not human readable.
Despite this tradeoff, the UI can easily decode them, and the user benefits from this
compact mode of logging.

There are a couple of ways to use events, which vary somewhat:
- to provide a return value
- as a callback or interrupt for event-driven programming
- as a compact and efficient means of data logging

Return values: Here we use an event to immediately provide a return value, such as details related
to a transaction. Since calls to certain programs only return a tx hash, this may not provide sufficient detail to your UI. An event here can provide a richer experience, by returning values about how a particular transaction was executed, for example.

As a callback: you may want to monitor an event and be notified only once a transaction was confirmed.
Instead of polling / querying the blockchain and looking for an account's values to update, we can build our client to only act when the program has emitted an event, saving on compute resources and fees.

Cheap storage: since events are stored in base64, they can be an economical way to store data
without the trouble of account creation and provisioning for rent-exemption, for example.

## Example

The simplest way to use events is to tack them onto the tail of your program's functions, allowing them to emit
structured data when the function completes without error.

Start by creating a new anchor project:
```bash
$ anchor init Events
```

Next, we'll merely modify the program in `src/lib.rs` to add an event, and the accompanying data to the `initialize`
function.
```rust,ignore
// ...
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
emit!(MyEvent {
data: 5,
label: "hello".to_string(),
});
Ok(())
}

// ...
#[event]
pub struct MyEvent {
pub data: u64,
#[index]
pub label: String,
}

```

Observe that we are using the event to provide a return value to a successful call to `initialize`.
The data returned is fairly trivial, an `int` and a short `String`. Without the event, we would have no specific information about the outcome of initialize function, besides that it was successful.

Note that the event is declared with the `#[event]` macro, and defined using a `struct`. To supply the values,
the pattern `emit!(MyEvent {...}` is used. The code above is a basic stamp that can be used for creating and consuming events within your program.

Next, nothing fancy but we'll add a new function called `test_event` with a new event that emits some other values. Complete code for `src/lib.rs` is shown below:

```rust,ignore
use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
pub mod events {
use super::*;

pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
emit!(MyEvent {
data: 5,
label: "hello".to_string(),
});
Ok(())
}

pub fn test_event(_ctx: Context<TestEvent>) -> Result<()> {
emit!(MyOtherEvent {
data: 6,
label: "bye".to_string(),
});
Ok(())
}
}

#[derive(Accounts)]
pub struct Initialize {}

#[derive(Accounts)]
pub struct TestEvent {}

#[event]
pub struct MyEvent {
pub data: u64,
#[index]
pub label: String,
}

#[event]
pub struct MyOtherEvent {
pub data: u64,
#[index]
pub label: String,
}
```

## Consuming Events

To test our program, we'll build a test client that will be able to consume the events. The structure for doing this is essentially:
```javascript
// ...
let listener = null;

let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);
// ...
```

We set up a `listener` that will catch when the event is emitted by the program. Since it's an asynchronous activity, we must include `await` to receive our return values. We call the first function using `program.rpc.initialize()` and when the event happens, we destructure the output into variables `event` and `slot`. Finally, when it's done, we close the listener. We can access our output values as follows: `event.data.toNumber()` or `event.label`.

With the explanation out of the way, edit the file in `tests/events.ts`. Copy-paste the test code into the file as follows, then run `$ anchor test` from the command line to verify that it's working as intended:

```javascript
import * as anchor from "@project-serum/anchor";
// import { Program } from "@project-serum/anchor";
// import { Events } from "../target/types/events";

const anchor = require("@project-serum/anchor");
const { assert } = require("chai");

describe("events", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Events;

it("Is initialized!", async () => {
let listener = null;

let [event, slot] = await new Promise((resolve, _reject) => {
listener = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});
await program.removeEventListener(listener);

assert.isAbove(slot, 0);
assert.strictEqual(event.data.toNumber(), 5);
assert.strictEqual(event.label, "hello");
});

it("Multiple events", async () => {
// Sleep so we don't get this transaction has already been processed.
await sleep(2000);

let listenerOne = null;
let listenerTwo = null;

let [eventOne, slotOne] = await new Promise((resolve, _reject) => {
listenerOne = program.addEventListener("MyEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.initialize();
});

let [eventTwo, slotTwo] = await new Promise((resolve, _reject) => {
listenerTwo = program.addEventListener("MyOtherEvent", (event, slot) => {
resolve([event, slot]);
});
program.rpc.testEvent();
});

await program.removeEventListener(listenerOne);
await program.removeEventListener(listenerTwo);

assert.isAbove(slotOne, 0);
assert.strictEqual(eventOne.data.toNumber(), 5);
assert.strictEqual(eventOne.label, "hello");

assert.isAbove(slotTwo, 0);
assert.strictEqual(eventTwo.data.toNumber(), 6);
assert.strictEqual(eventTwo.label, "bye");
});
});

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
```
This is the simplest example for creating and consuming events, but the pattern can be duplicated using dynamic data to provide asynchronous, actionable output for your UI or dApps.