sails_rs/gstd/
events.rs

1//! Functionality for notifying off-chain subscribers on events happening in on-chain programs.
2
3use crate::{Encode, Output, utils::MaybeUninitBufferWriter};
4use core::marker::PhantomData;
5use gcore::stack_buffer;
6
7/// Trait for encoding events that can be emitted by Sails programs.
8///
9/// This trait is used to define events that can be emitted by Sails programs, allowing them to be
10/// encoded into a byte slice.
11///
12/// The `SailsEvent` trait extends the `Encode` trait, which means that any type implementing `SailsEvent`
13/// must also implement the `Encode` trait.
14///
15/// The `skip_bytes` method is used to determine how many bytes should be skipped when encoding the event,
16/// which is particularly relevant for enum variants where the first byte is reserved for the index of the variant.
17///
18/// /// # Examples
19///
20/// Given an event definition:
21///
22/// ```rust,ignore
23/// #[sails_rs::event]
24/// #[derive(sails_rs::Encode, sails_rs::TypeInfo)]
25/// #[codec(crate = sails_rs::scale_codec)]
26/// #[scale_info(crate = sails_rs::scale_info)]
27/// pub enum Events {
28///     MyEvent {
29///         sender: uint128,
30///         amount: uint128,
31///         note: String,
32///     },
33/// }
34/// ```
35pub trait SailsEvent: Encode {
36    /// Returns the encoded event name as a byte slice.
37    fn encoded_event_name(&self) -> &'static [u8];
38
39    /// The number of bytes to skip when encoding the event.
40    ///
41    /// For enums, this is always 1 byte, which is reserved for the index of the event enum variant.
42    fn skip_bytes() -> usize {
43        1
44    }
45}
46
47#[allow(dead_code)]
48fn with_optimized_event_encode<T, E: SailsEvent, F: FnOnce(&[u8]) -> T>(
49    prefix: &[u8],
50    event: E,
51    f: F,
52) -> T {
53    let encoded_event_name = E::encoded_event_name(&event);
54    let encoded_size = Encode::encoded_size(&event);
55    let skip_bytes = E::skip_bytes();
56    let size = prefix.len() + encoded_event_name.len() + encoded_size - skip_bytes;
57    stack_buffer::with_byte_buffer(size, |buffer| {
58        let mut buffer_writer = MaybeUninitBufferWriter::new(buffer);
59        buffer_writer.write(prefix);
60        buffer_writer.write(encoded_event_name);
61        buffer_writer.skip_next(skip_bytes); // skip the first byte, which is the index of the event enum variant
62        Encode::encode_to(&event, &mut buffer_writer);
63        buffer_writer.with_buffer(f)
64    })
65}
66
67/// An event emitter that allows emitting events of type `T` for a specific route.
68///
69/// This is lightweight and can be cloned.
70#[derive(Clone, Debug, Eq, PartialEq)]
71pub struct EventEmitter<T> {
72    route: &'static [u8],
73    _marker: PhantomData<T>,
74}
75
76impl<T> EventEmitter<T> {
77    pub fn new(route: &'static [u8]) -> Self {
78        Self {
79            route,
80            _marker: PhantomData,
81        }
82    }
83}
84
85impl<T: SailsEvent> EventEmitter<T> {
86    /// Emits an event.
87    #[cfg(target_arch = "wasm32")]
88    pub fn emit_event(&mut self, event: T) -> crate::errors::Result<()> {
89        with_optimized_event_encode(self.route, event, |payload| {
90            gstd::msg::send_bytes(gstd::ActorId::zero(), payload, 0)?;
91            Ok(())
92        })
93    }
94
95    #[cfg(not(target_arch = "wasm32"))]
96    #[cfg(not(feature = "std"))]
97    pub fn emit_event(&mut self, _event: T) -> crate::errors::Result<()> {
98        unimplemented!(
99            "`emit_event` is implemented only for the wasm32 architecture and the std future"
100        )
101    }
102}
103
104#[cfg(feature = "ethexe")]
105impl<T: super::EthEvent> EventEmitter<T> {
106    /// Emits an event for the Ethexe program.
107    #[cfg(target_arch = "wasm32")]
108    pub fn emit_eth_event(&mut self, event: T) -> crate::errors::Result<()> {
109        super::ethexe::__emit_eth_event(event)
110    }
111
112    #[cfg(not(target_arch = "wasm32"))]
113    #[cfg(not(feature = "std"))]
114    pub fn emit_eth_event(&mut self, _event: T) -> crate::errors::Result<()> {
115        unimplemented!(
116            "`emit_eth_event` is implemented only for the wasm32 architecture and the std future"
117        )
118    }
119}
120
121#[cfg(not(target_arch = "wasm32"))]
122#[cfg(feature = "std")]
123impl<T: 'static> EventEmitter<T> {
124    /// Emits an event.
125    pub fn emit_event(&mut self, event: T) -> crate::errors::Result<()> {
126        event_registry::push_event(self.route, event);
127        Ok(())
128    }
129
130    #[cfg(feature = "ethexe")]
131    pub fn emit_eth_event(&mut self, event: T) -> crate::errors::Result<()> {
132        event_registry::push_event(self.route, event);
133        Ok(())
134    }
135
136    /// Takes the events emitted for this route and returns them as a `Vec<T>`.
137    pub fn take_events(&mut self) -> crate::Vec<T> {
138        event_registry::take_events(self.route).unwrap_or_else(|| crate::Vec::new())
139    }
140}
141
142#[cfg(not(target_arch = "wasm32"))]
143#[cfg(feature = "std")]
144mod event_registry {
145    use core::any::{Any, TypeId};
146    use std::{boxed::Box, collections::BTreeMap, sync::Mutex, vec::Vec};
147
148    type Key = (&'static [u8], TypeId);
149
150    std::thread_local! {
151        /// thread-local registry mapping `(key, TypeId)` -> boxed `Vec<T>`
152        static ROUTE_EVENTS: Mutex<BTreeMap<Key, Box<dyn Any>>> = Mutex::new(BTreeMap::new());
153    }
154
155    /// Push a `value: T` onto the `Vec<T>` stored under `key`.
156    /// If none exists yet, we create a `Vec<T>` for that `(key,TypeId::of::<T>())`.
157    pub(super) fn push_event<T: 'static>(key: &'static [u8], value: T) {
158        ROUTE_EVENTS.with(|mtx| {
159            let mut map = mtx.lock().expect("failed to lock ROUTE_EVENTS mutex");
160            let slot = map
161                .entry((key, TypeId::of::<T>()))
162                .or_insert_with(|| Box::new(Vec::<T>::new()));
163            // SAFETY: we just inserted a Box<Vec<T>> for exactly this TypeId,
164            // so downcast_mut must succeed.
165            let vec: &mut Vec<T> = slot
166                .downcast_mut::<Vec<T>>()
167                .expect("type mismatch in route-events registry");
168            vec.push(value);
169        });
170    }
171
172    /// Take `Vec<T>` for the given `key`, or `None` if nothing was ever pushed.
173    pub(super) fn take_events<T: 'static>(key: &'static [u8]) -> Option<Vec<T>> {
174        ROUTE_EVENTS.with(|mtx| {
175            let mut map = mtx.lock().expect("failed to lock ROUTE_EVENTS mutex");
176            map.remove(&(key, TypeId::of::<T>())).map(|boxed| {
177                // SAFETY: we just inserted a Box<Vec<T>> for exactly this TypeId,
178                // so downcast must succeed.
179                *boxed
180                    .downcast::<Vec<T>>()
181                    .expect("type mismatch in route-events registry")
182            })
183        })
184    }
185
186    #[cfg(test)]
187    mod tests {
188        use super::*;
189        use std::vec;
190
191        #[test]
192        fn event_registry() {
193            push_event(b"/foo", 42_u32);
194            push_event(b"/foo", 7_u32);
195
196            assert_eq!(take_events::<u32>(b"/foo"), Some(vec![42, 7]));
197            assert!(take_events::<u32>(b"/foo").is_none()); // removed
198            assert!(take_events::<i32>(b"/foo").is_none()); // wrong type
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use crate::prelude::*;
207
208    #[derive(Encode, TypeInfo)]
209    enum TestEvents {
210        Event1(u32),
211        Event2 { p1: u16 },
212    }
213
214    impl SailsEvent for TestEvents {
215        fn encoded_event_name(&self) -> &'static [u8] {
216            match self {
217                TestEvents::Event1(_) => &[24, 69, 118, 101, 110, 116, 49],
218                TestEvents::Event2 { .. } => &[24, 69, 118, 101, 110, 116, 50],
219            }
220        }
221    }
222
223    #[test]
224    fn trait_optimized_event_encode() {
225        let event = TestEvents::Event1(42);
226        with_optimized_event_encode(&[1, 2, 3], event, |payload| {
227            assert_eq!(
228                payload,
229                [1, 2, 3, 24, 69, 118, 101, 110, 116, 49, 42, 00, 00, 00]
230            );
231        });
232
233        let event = TestEvents::Event2 { p1: 43 };
234        with_optimized_event_encode(&[], event, |payload| {
235            assert_eq!(payload, [24, 69, 118, 101, 110, 116, 50, 43, 00]);
236        });
237    }
238}