Commit dff9656
authored
Add contractevent macro and event support to contract spec (#1473)
### What
Add `contractevent` macro for defining events, and event support to
contract spec.
In it's most basic form, as follows, where an automatic prefix topic
based on the name will be published, with any topic fields appended to
the topic list, and any other fields emitted as map entries in a map:
```rust
#[contractevent]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct MyEvent {
#[topic]
pub my_topic: u32,
pub my_event_data: u32,
pub more_event_data: u64,
}
```
But also supporting more advanced customisable forms, where prefix
topics can be overriden and a max of two provided, and where the data
section can be defined as either being a `vec` or a `single-value`
instead.
### Why
The `contractevent` macro will achieve a couple things for events.
It will allow developers to define the event as a type, in a similar way
that they can define an event in Solidity contracts. Defining it as a
type makes it easier to reuse the event, and to define the types safely
and be clear about what types within the event are being published.
It will provide a foundation that the Rust SDK can use to include in the
contract interface (spec) an entry for the event. That entry can then be
used by downstream systems to bring more meaning to the raw event data
that is visible in transaction / ledger close meta. For an example of
what that looks like, see
https://github.com/orgs/stellar/discussions/1724#discussioncomment-13118291.
The advanced customisations are a necessity to be able to make the
events and the event schema describe existing contract events. The
network already has a variety of events and some contracts use two
fields as fixed prefix topics, such as the Soroswap contracts. Some use
vecs for the data section, and even a single value such as an `amount:
i128`, such as the Stellar Asset Contract.
Close #1097
### Docs for the `contractevent` attribute macro
Generates conversions from the struct into a published event.
Fields of the struct become topics and data parameters in the published
event.
Includes the event in the contract spec so that clients can generate
bindings for the type and downstream systems can understand the meaning
of the event.
#### Examples
##### Basic Contract Event
Defining a basic contract event.
The event will have a single fixed prefix topic matching the name of the
struct in lower snake case. The fixed prefix topic will appear before
any topics listed as fields. In the example below, the topics for the
event will be:
* `"my_event"`
* u32 value from the `my_topic` field
The event’s data will be a [`Map`](struct.Map.html "struct
soroban_sdk::Map"), containing a key-value pair for each field with the
key being the name as a [`Symbol`](struct.Symbol.html "struct
soroban_sdk::Symbol"). In the example below, the data for the event will
be:
* key: my\_event\_data => val: u32
* key: more\_event\_data => val: u64
```rust
#![no_std]
use soroban_sdk::contractevent;
// Define the event using the `contractevent` attribute macro.
#[contractevent]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct MyEvent {
// Mark fields as topics, for the value to be included in the events topic list so
// that downstream systems know to index it.
#[topic]
pub my_topic: u32,
// Fields not marked as topics will appear in the events data section.
pub my_event_data: u32,
pub more_event_data: u64,
}
```
##### Prefix Topics
Defining a contract event with a custom set of fixed prefix topics.
The prefix topic list can be set to another value. In the example below,
the topics for the event will be:
* `"my_contract"`
* `"an_event"`
* u32 value from the `my_topic` field
```rust
#![no_std]
use soroban_sdk::contractevent;
// Define the event using the `contractevent` attribute macro.
#[contractevent(topics = ["my_contract", "an_event"])]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct MyEvent {
// Mark fields as topics, for the value to be included in the events topic list so
// that downstream systems know to index it.
#[topic]
pub my_topic: u32,
// Fields not marked as topics will appear in the events data section.
pub my_event_data: u32,
pub more_event_data: u64,
}
```
##### Data Format
Defining a contract event with a different data format. The data format
of the event is by default a map, but can alternatively be defined as a
`vec` or `single-value`.
###### Vec
In the example below, the data for the event will be a
[`Vec`](struct.Vec.html "struct soroban_sdk::Vec") containing:
* u32
* u64
```rust
#![no_std]
use soroban_sdk::contractevent;
// Define the event using the `contractevent` attribute macro.
#[contractevent(data_format = "vec")]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct MyEvent {
// Mark fields as topics, for the value to be included in the events topic list so
// that downstream systems know to index it.
#[topic]
pub my_topic: u32,
// Fields not marked as topics will appear in the events data section.
pub my_event_data: u32,
pub more_event_data: u64,
}
```
###### Single Value
In the example below, the data for the event will be a u32.
When the data format is a single value there must be no more than one
data field.
```rust
#![no_std]
use soroban_sdk::contractevent;
// Define the event using the `contractevent` attribute macro.
#[contractevent(data_format = "single-value")]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct MyEvent {
// Mark fields as topics, for the value to be included in the events topic list so
// that downstream systems know to index it.
#[topic]
pub my_topic: u32,
// Fields not marked as topics will appear in the events data section.
pub my_event_data: u32,
}
```
##### A Full Example
Defining a contract event, publishing it in a contract, and testing it.
```rust
#![no_std]
use soroban_sdk::{contract, contractevent, contractimpl, contracttype, symbol_short, Env, Symbol};
// Define the event using the `contractevent` attribute macro.
#[contractevent]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct Increment {
// Mark fields as topics, for the value to be included in the events topic list so
// that downstream systems know to index it.
#[topic]
pub change: u32,
// Fields not marked as topics will appear in the events data section.
pub count: u32,
}
#[contracttype]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct State {
pub count: u32,
pub last_incr: u32,
}
#[contract]
pub struct Contract;
#[contractimpl]
impl Contract {
/// Increment increments an internal counter, and returns the value.
/// Publishes an event about the change in the counter.
pub fn increment(env: Env, incr: u32) -> u32 {
// Get the current count.
let mut state = Self::get_state(env.clone());
// Increment the count.
state.count += incr;
state.last_incr = incr;
// Save the count.
env.storage().persistent().set(&symbol_short!("STATE"), &state);
// Publish an event about the change.
Increment {
change: incr,
count: state.count,
}.publish(&env);
// Return the count to the caller.
state.count
}
/// Return the current state.
pub fn get_state(env: Env) -> State {
env.storage().persistent()
.get(&symbol_short!("STATE"))
.unwrap_or_else(|| State::default()) // If no value set, assume 0.
}
}
#[test]
fn test() {
let env = Env::default();
let contract_id = env.register(Contract, ());
let client = ContractClient::new(&env, &contract_id);
assert_eq!(client.increment(&1), 1);
assert_eq!(client.increment(&10), 11);
assert_eq!(
client.get_state(),
State {
count: 11,
last_incr: 10,
},
);
}
```1 parent 72eebb5 commit dff9656
27 files changed
Lines changed: 2888 additions & 32 deletions
File tree
- soroban-sdk-macros
- src
- soroban-sdk
- src
- tests
- test_snapshots/tests/contract_event
- soroban-spec-rust/src
- tests
- events
- src
- test_snapshots/test
- fuzz/fuzz
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
0 commit comments