Arbitrum Stylus logo

Stylus by Example

Events

Events allow for data to be logged publicly to the blockchain. Log entries provide the contract's address, a series of up to four topics, and some arbitrary length binary data. The Stylus Rust SDK provides a few ways to publish event logs described below.

Learn More

Log

Using the evm::log function in the Stylus SDK is the preferred way to log events. It ensures that an event will be logged in a Solidity ABI-compatible format. The log function takes any type that implements Alloy SolEvent trait. It's not recommended to attempt to implement this trait on your own. Instead, make use of the provided sol! macro to declare your Events and their schema using Solidity-style syntax to declare the parameter types. Alloy will create ABI-compatible Rust types which you can instantiate and pass to the evm::log function.

Log Usage

1// sol! macro event declaration
2// Up to 3 parameters can be indexed.
3// Indexed parameters helps you filter the logs efficiently
4sol! {
5    event Log(address indexed sender, string message);
6    event AnotherLog();
7}
8
9#[storage]
10#[entrypoint]
11pub struct Events {}
12
13#[public]
14impl Events {
15fn user_main(_input: Vec<u8>) -> ArbResult {
16    // emits a 'Log' event, defined above in the sol! macro
17    evm::log(Log {
18        sender: Address::from([0x11; 20]),
19        message: "Hello world!".to_string(),
20    });
21
22    // no data, but 'AnotherLog' event will still emit to the chain
23    evm::log(AnotherLog {});
24
25    Ok(vec![])
26}
27}
1// sol! macro event declaration
2// Up to 3 parameters can be indexed.
3// Indexed parameters helps you filter the logs efficiently
4sol! {
5    event Log(address indexed sender, string message);
6    event AnotherLog();
7}
8
9#[storage]
10#[entrypoint]
11pub struct Events {}
12
13#[public]
14impl Events {
15fn user_main(_input: Vec<u8>) -> ArbResult {
16    // emits a 'Log' event, defined above in the sol! macro
17    evm::log(Log {
18        sender: Address::from([0x11; 20]),
19        message: "Hello world!".to_string(),
20    });
21
22    // no data, but 'AnotherLog' event will still emit to the chain
23    evm::log(AnotherLog {});
24
25    Ok(vec![])
26}
27}

Raw Log

The evm::raw_log affordance offers the ability to send anonymous events that do not necessarily conform to the Solidity ABI. Instead, up to four raw 32-byte indexed topics are published along with any arbitrary bytes appended as data.

NOTE: It's still possible to achieve Solidity ABI compatibility using this construct. To do so you'll have to manually compute the ABI signature for the event, following the equation set in the Solidity docs. The result of that should be assigned to TOPIC_0, the first topic in the slice passed to raw_log.

Raw Log Usage

1// set up local variables
2let user = Address::from([0x22; 20]);
3let balance = U256::from(10_000_000);
4
5// declare up to 4 topics
6// topics must be of type FixedBytes<32>
7let topics = &[user.into_word()];
8
9// store non-indexed data in a byte Vec
10let mut data: Vec<u8> = vec![];
11// to_be_bytes means 'to big endian bytes'
12data.extend_from_slice(balance.to_be_bytes::<32>().to_vec().as_slice());
13
14// unwrap() here 'consumes' the Result
15evm::raw_log(topics.as_slice(), data.as_ref()).unwrap();
1// set up local variables
2let user = Address::from([0x22; 20]);
3let balance = U256::from(10_000_000);
4
5// declare up to 4 topics
6// topics must be of type FixedBytes<32>
7let topics = &[user.into_word()];
8
9// store non-indexed data in a byte Vec
10let mut data: Vec<u8> = vec![];
11// to_be_bytes means 'to big endian bytes'
12data.extend_from_slice(balance.to_be_bytes::<32>().to_vec().as_slice());
13
14// unwrap() here 'consumes' the Result
15evm::raw_log(topics.as_slice(), data.as_ref()).unwrap();

Result

Combining the above examples into the boiler plate provided below this section, deploying to a Stylus chain and then invoking the deployed contract will result in the following three events logged to the chain:

logs

1[
2  {
3    "address": "0x6cf4a18ac8efd6b0b99d3200c4fb9609dd60d4b3",
4    "topics": [
5      "0x0738f4da267a110d810e6e89fc59e46be6de0c37b1d5cd559b267dc3688e74e0",
6      "0x0000000000000000000000001111111111111111111111111111111111111111"
7    ],
8    "data": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20776f726c64210000000000000000000000000000000000000000",
9    "blockHash": "0xfef880025dc87b5ab4695a0e1a6955dd7603166ecba79ce0f503a568b2ec8940",
10    "blockNumber": "0x94",
11    "transactionHash": "0xc7318dae2164eb441fb80f5b869f844e3e97ae83c24a4639d46ec4d915a30818",
12    "transactionIndex": "0x1",
13    "logIndex": "0x0",
14    "removed": false
15  },
16  {
17    "address": "0x6cf4a18ac8efd6b0b99d3200c4fb9609dd60d4b3",
18    "topics": ["0xfe1a3ad11e425db4b8e6af35d11c50118826a496df73006fc724cb27f2b99946"],
19    "data": "0x",
20    "blockHash": "0xfef880025dc87b5ab4695a0e1a6955dd7603166ecba79ce0f503a568b2ec8940",
21    "blockNumber": "0x94",
22    "transactionHash": "0xc7318dae2164eb441fb80f5b869f844e3e97ae83c24a4639d46ec4d915a30818",
23    "transactionIndex": "0x1",
24    "logIndex": "0x1",
25    "removed": false
26  },
27  {
28    "address": "0x6cf4a18ac8efd6b0b99d3200c4fb9609dd60d4b3",
29    "topics": ["0x0000000000000000000000002222222222222222222222222222222222222222"],
30    "data": "0x0000000000000000000000000000000000000000000000000000000000989680",
31    "blockHash": "0xfef880025dc87b5ab4695a0e1a6955dd7603166ecba79ce0f503a568b2ec8940",
32    "blockNumber": "0x94",
33    "transactionHash": "0xc7318dae2164eb441fb80f5b869f844e3e97ae83c24a4639d46ec4d915a30818",
34    "transactionIndex": "0x1",
35    "logIndex": "0x2",
36    "removed": false
37  }
38]
1[
2  {
3    "address": "0x6cf4a18ac8efd6b0b99d3200c4fb9609dd60d4b3",
4    "topics": [
5      "0x0738f4da267a110d810e6e89fc59e46be6de0c37b1d5cd559b267dc3688e74e0",
6      "0x0000000000000000000000001111111111111111111111111111111111111111"
7    ],
8    "data": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c48656c6c6f20776f726c64210000000000000000000000000000000000000000",
9    "blockHash": "0xfef880025dc87b5ab4695a0e1a6955dd7603166ecba79ce0f503a568b2ec8940",
10    "blockNumber": "0x94",
11    "transactionHash": "0xc7318dae2164eb441fb80f5b869f844e3e97ae83c24a4639d46ec4d915a30818",
12    "transactionIndex": "0x1",
13    "logIndex": "0x0",
14    "removed": false
15  },
16  {
17    "address": "0x6cf4a18ac8efd6b0b99d3200c4fb9609dd60d4b3",
18    "topics": ["0xfe1a3ad11e425db4b8e6af35d11c50118826a496df73006fc724cb27f2b99946"],
19    "data": "0x",
20    "blockHash": "0xfef880025dc87b5ab4695a0e1a6955dd7603166ecba79ce0f503a568b2ec8940",
21    "blockNumber": "0x94",
22    "transactionHash": "0xc7318dae2164eb441fb80f5b869f844e3e97ae83c24a4639d46ec4d915a30818",
23    "transactionIndex": "0x1",
24    "logIndex": "0x1",
25    "removed": false
26  },
27  {
28    "address": "0x6cf4a18ac8efd6b0b99d3200c4fb9609dd60d4b3",
29    "topics": ["0x0000000000000000000000002222222222222222222222222222222222222222"],
30    "data": "0x0000000000000000000000000000000000000000000000000000000000989680",
31    "blockHash": "0xfef880025dc87b5ab4695a0e1a6955dd7603166ecba79ce0f503a568b2ec8940",
32    "blockNumber": "0x94",
33    "transactionHash": "0xc7318dae2164eb441fb80f5b869f844e3e97ae83c24a4639d46ec4d915a30818",
34    "transactionIndex": "0x1",
35    "logIndex": "0x2",
36    "removed": false
37  }
38]

Boilerplate

src/lib.rs

1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5use alloc::vec::Vec;
6use alloc::{string::ToString, vec};
7
8use stylus_sdk::alloy_primitives::U256;
9use stylus_sdk::{alloy_primitives::Address, alloy_sol_types::sol, evm, prelude::*, ArbResult};
10
11// sol! macro event declaration
12// Up to 3 parameters can be indexed.
13// Indexed parameters helps you filter the logs by the indexed parameter
14sol! {
15    event Log(address indexed sender, string message);
16    event AnotherLog();
17}
18
19#[storage]
20#[entrypoint]
21pub struct Events {}
22
23#[public]
24impl Events {
25fn user_main(_input: Vec<u8>) -> ArbResult {
26    // emits a 'Log' event, defined above in the sol! macro
27    evm::log(Log {
28        sender: Address::from([0x11; 20]),
29        message: "Hello world!".to_string(),
30    });
31
32    // no data, but event will still log to the chain
33    evm::log(AnotherLog {});
34
35    // set up local variables
36    let user = Address::from([0x22; 20]);
37    let balance = U256::from(10_000_000);
38
39    // declare up to 4 topics
40    // topics must be of type FixedBytes<32>
41    let topics = &[user.into_word()];
42
43    // store non-indexed data in a byte Vec
44    let mut data: Vec<u8> = vec![];
45    // to_be_bytes means 'to big endian bytes'
46    data.extend_from_slice(balance.to_be_bytes::<32>().to_vec().as_slice());
47
48    // unwrap() here 'consumes' the Result
49    evm::raw_log(topics.as_slice(), data.as_ref()).unwrap();
50
51    Ok(Vec::new())
52}
53}
1// Only run this as a WASM if the export-abi feature is not set.
2#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
3extern crate alloc;
4
5use alloc::vec::Vec;
6use alloc::{string::ToString, vec};
7
8use stylus_sdk::alloy_primitives::U256;
9use stylus_sdk::{alloy_primitives::Address, alloy_sol_types::sol, evm, prelude::*, ArbResult};
10
11// sol! macro event declaration
12// Up to 3 parameters can be indexed.
13// Indexed parameters helps you filter the logs by the indexed parameter
14sol! {
15    event Log(address indexed sender, string message);
16    event AnotherLog();
17}
18
19#[storage]
20#[entrypoint]
21pub struct Events {}
22
23#[public]
24impl Events {
25fn user_main(_input: Vec<u8>) -> ArbResult {
26    // emits a 'Log' event, defined above in the sol! macro
27    evm::log(Log {
28        sender: Address::from([0x11; 20]),
29        message: "Hello world!".to_string(),
30    });
31
32    // no data, but event will still log to the chain
33    evm::log(AnotherLog {});
34
35    // set up local variables
36    let user = Address::from([0x22; 20]);
37    let balance = U256::from(10_000_000);
38
39    // declare up to 4 topics
40    // topics must be of type FixedBytes<32>
41    let topics = &[user.into_word()];
42
43    // store non-indexed data in a byte Vec
44    let mut data: Vec<u8> = vec![];
45    // to_be_bytes means 'to big endian bytes'
46    data.extend_from_slice(balance.to_be_bytes::<32>().to_vec().as_slice());
47
48    // unwrap() here 'consumes' the Result
49    evm::raw_log(topics.as_slice(), data.as_ref()).unwrap();
50
51    Ok(Vec::new())
52}
53}

Cargo.toml

1[package]
2name = "stylus_events_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"
1[package]
2name = "stylus_events_example"
3version = "0.1.7"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
7
8[dependencies]
9alloy-primitives = "=0.7.6"
10alloy-sol-types = "=0.7.6"
11mini-alloc = "0.4.2"
12stylus-sdk = "0.6.0"
13hex = "0.4.3"
14
15[dev-dependencies]
16tokio = { version = "1.12.0", features = ["full"] }
17ethers = "2.0"
18eyre = "0.6.8"
19
20[features]
21export-abi = ["stylus-sdk/export-abi"]
22
23[lib]
24crate-type = ["lib", "cdylib"]
25
26[profile.release]
27codegen-units = 1
28strip = true
29lto = true
30panic = "abort"
31opt-level = "s"