Skip to main content

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}