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
61#[cfg(any(test, feature = "testutils"))]
62use crate::{testutils, xdr, FromVal};
63
64pub trait Event {
65 fn topics(&self, env: &Env) -> Vec<Val>;
66 fn data(&self, env: &Env) -> Val;
67
68 fn publish(&self, env: &Env) {
69 env.events().publish_event(self);
70 }
71
72 /// Convert this event and the given contract_id into a [`xdr::ContractEvent`] object.
73 /// Used to compare Events to emitted events in tests.
74 #[cfg(any(test, feature = "testutils"))]
75 fn to_xdr(&self, env: &Env, contract_id: &crate::Address) -> xdr::ContractEvent {
76 xdr::ContractEvent {
77 ext: xdr::ExtensionPoint::V0,
78 type_: xdr::ContractEventType::Contract,
79 contract_id: Some(contract_id.contract_id()),
80 body: xdr::ContractEventBody::V0(xdr::ContractEventV0 {
81 topics: self.topics(env).into(),
82 data: xdr::ScVal::from_val(env, &self.data(env)),
83 }),
84 }
85 }
86}
87
88pub trait Topics: IntoVal<Env, Vec<Val>> {}
89
90impl<T> Topics for Vec<T> {}
91
92impl Events {
93 #[inline(always)]
94 pub(crate) fn env(&self) -> &Env {
95 &self.0
96 }
97
98 #[inline(always)]
99 pub(crate) fn new(env: &Env) -> Events {
100 Events(env.clone())
101 }
102
103 /// Publish an event defined using the [`contractevent`][crate::contractevent] macro.
104 #[inline(always)]
105 pub fn publish_event(&self, e: &(impl Event + ?Sized)) {
106 let env = self.env();
107 internal::Env::contract_event(env, e.topics(env).to_object(), e.data(env))
108 .unwrap_infallible();
109 }
110
111 /// Publish an event.
112 ///
113 /// Consider using [`contractevent`][crate::contractevent] instead of this function.
114 ///
115 /// Event data is specified in `data`. Data may be any value or
116 /// type, including types defined by contracts using [contracttype].
117 ///
118 /// Event topics must not contain:
119 ///
120 /// - [Vec]
121 /// - [Map]
122 /// - [Bytes]/[BytesN][crate::BytesN] longer than 32 bytes
123 /// - [contracttype]
124 #[deprecated(note = "use the #[contractevent] macro on a contract event type")]
125 #[inline(always)]
126 pub fn publish<T, D>(&self, topics: T, data: D)
127 where
128 T: Topics,
129 D: IntoVal<Env, Val>,
130 {
131 let env = self.env();
132 internal::Env::contract_event(env, topics.into_val(env).to_object(), data.into_val(env))
133 .unwrap_infallible();
134 }
135}
136
137#[cfg(any(test, feature = "testutils"))]
138#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
139impl testutils::Events for Events {
140 fn all(&self) -> testutils::ContractEvents {
141 let env = self.env();
142 let vec: std::vec::Vec<xdr::ContractEvent> = self
143 .env()
144 .host()
145 .get_events()
146 .unwrap()
147 .0
148 .into_iter()
149 .filter_map(|e| {
150 if !e.failed_call
151 && e.event.type_ == xdr::ContractEventType::Contract
152 && e.event.contract_id.is_some()
153 {
154 Some(e.event)
155 } else {
156 None
157 }
158 })
159 .collect();
160 testutils::ContractEvents::new(&env, vec)
161 }
162}