soroban_sdk/events.rs
1//! Events contains types for publishing contract events.
2use core::fmt::Debug;
3
4#[cfg(doc)]
5use crate::{contracttype, Bytes, Map};
6use crate::{env::internal, unwrap::UnwrapInfallible, Env, IntoVal, Val, Vec};
7
8// TODO: consolidate with host::events::TOPIC_BYTES_LENGTH_LIMIT
9const TOPIC_BYTES_LENGTH_LIMIT: u32 = 32;
10
11/// Events publishes events for the currently executing contract.
12///
13/// ```
14/// use soroban_sdk::Env;
15///
16/// # use soroban_sdk::{contract, contractimpl, vec, map, Val, BytesN};
17/// #
18/// # #[contract]
19/// # pub struct Contract;
20/// #
21/// # #[contractimpl]
22/// # impl Contract {
23/// # pub fn f(env: Env) {
24/// let event = env.events();
25/// let data = map![&env, (1u32, 2u32)];
26/// // topics can be represented with tuple up to a certain length
27/// let topics0 = ();
28/// let topics1 = (0u32,);
29/// let topics2 = (0u32, 1u32);
30/// let topics3 = (0u32, 1u32, 2u32);
31/// let topics4 = (0u32, 1u32, 2u32, 3u32);
32/// // topics can also be represented with a `Vec` with no length limit
33/// let topics5 = vec![&env, 4u32, 5u32, 6u32, 7u32, 8u32];
34/// event.publish(topics0, data.clone());
35/// event.publish(topics1, data.clone());
36/// event.publish(topics2, data.clone());
37/// event.publish(topics3, data.clone());
38/// event.publish(topics4, data.clone());
39/// event.publish(topics5, data.clone());
40/// # }
41/// # }
42///
43/// # #[cfg(feature = "testutils")]
44/// # fn main() {
45/// # let env = Env::default();
46/// # let contract_id = env.register(Contract, ());
47/// # ContractClient::new(&env, &contract_id).f();
48/// # }
49/// # #[cfg(not(feature = "testutils"))]
50/// # fn main() { }
51/// ```
52#[derive(Clone)]
53pub struct Events(Env);
54
55impl Debug for Events {
56 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
57 write!(f, "Events")
58 }
59}
60
61pub trait Event {
62 fn topics(&self, env: &Env) -> Vec<Val>;
63 fn data(&self, env: &Env) -> Val;
64
65 fn publish(&self, env: &Env) {
66 env.events().publish_event(self);
67 }
68}
69
70pub trait Topics: IntoVal<Env, Vec<Val>> {}
71
72impl<T> Topics for Vec<T> {}
73
74impl Events {
75 #[inline(always)]
76 pub(crate) fn env(&self) -> &Env {
77 &self.0
78 }
79
80 #[inline(always)]
81 pub(crate) fn new(env: &Env) -> Events {
82 Events(env.clone())
83 }
84
85 /// Publish an event defined using the [`contractevent`][crate::contractevent] macro.
86 #[inline(always)]
87 pub fn publish_event(&self, e: &(impl Event + ?Sized)) {
88 let env = self.env();
89 internal::Env::contract_event(env, e.topics(env).to_object(), e.data(env))
90 .unwrap_infallible();
91 }
92
93 /// Publish an event.
94 ///
95 /// Consider using [`contractevent`][crate::contractevent] instead of this function.
96 ///
97 /// Event data is specified in `data`. Data may be any value or
98 /// type, including types defined by contracts using [contracttype].
99 ///
100 /// Event topics must not contain:
101 ///
102 /// - [Vec]
103 /// - [Map]
104 /// - [Bytes]/[BytesN][crate::BytesN] longer than 32 bytes
105 /// - [contracttype]
106 #[deprecated(note = "use the #[contractevent] macro on a contract event type")]
107 #[inline(always)]
108 pub fn publish<T, D>(&self, topics: T, data: D)
109 where
110 T: Topics,
111 D: IntoVal<Env, Val>,
112 {
113 let env = self.env();
114 internal::Env::contract_event(env, topics.into_val(env).to_object(), data.into_val(env))
115 .unwrap_infallible();
116 }
117}
118
119#[cfg(any(test, feature = "testutils"))]
120use crate::{testutils, xdr, Address, TryIntoVal};
121
122#[cfg(any(test, feature = "testutils"))]
123#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
124impl testutils::Events for Events {
125 fn all(&self) -> Vec<(crate::Address, Vec<Val>, Val)> {
126 let env = self.env();
127 let mut vec = Vec::new(env);
128 self.env()
129 .host()
130 .get_events()
131 .unwrap()
132 .0
133 .into_iter()
134 .for_each(|e| {
135 if let xdr::ContractEvent {
136 type_: xdr::ContractEventType::Contract,
137 contract_id: Some(contract_id),
138 body: xdr::ContractEventBody::V0(xdr::ContractEventV0 { topics, data }),
139 ..
140 } = e.event
141 {
142 vec.push_back((
143 Address::from_contract_id(env, contract_id.0 .0),
144 topics.try_into_val(env).unwrap(),
145 data.try_into_val(env).unwrap(),
146 ))
147 }
148 });
149 vec
150 }
151}