1use crate::{Event, Topic};
2use thruput_xdr::event::TxEmittedEvent;
3
4pub const TOPIC_SIZE: usize = 32;
6
7pub struct EmittedEvent<'a>(&'a TxEmittedEvent);
10
11impl<'a> EmittedEvent<'a> {
12 fn topics_byte_len(&self) -> Option<usize> {
18 let event = self.0;
19 if event.num_topics > thruput_xdr::event::kMaxTopics {
20 return None;
21 }
22 let extent = TOPIC_SIZE * event.num_topics as usize;
23 if extent > event.serialized_event.len() {
24 return None;
25 }
26 Some(extent)
27 }
28
29 pub fn payload(&self) -> Option<&'a [u8]> {
37 let extent = self.topics_byte_len()?;
38 Some(&self.0.serialized_event[extent..])
39 }
40
41 pub fn topic(&self, index: u32) -> Option<&'a Topic> {
42 let event = self.0;
43
44 self.topics_byte_len()?;
48
49 if index >= event.num_topics {
50 return None;
51 }
52
53 let topic_offset = TOPIC_SIZE * index as usize;
54 let topic_extent = topic_offset + TOPIC_SIZE;
55
56 let topic_bytes = &event.serialized_event[topic_offset..topic_extent];
57
58 let topic: &[u8; TOPIC_SIZE] = topic_bytes.try_into().unwrap();
59
60 Some(topic)
61 }
62
63 pub fn num_topics(&self) -> u32 {
64 self.0.num_topics
65 }
66
67 pub fn raw(&self) -> &'a TxEmittedEvent {
69 self.0
70 }
71}
72
73impl<'a> From<&'a TxEmittedEvent> for EmittedEvent<'a> {
74 fn from(event: &'a TxEmittedEvent) -> Self {
75 Self(event)
76 }
77}
78
79enum BuildError {
84 Malformed { num_topics: u32, body_len: usize },
86 SignatureMismatch {
88 actual: Option<Topic>,
89 expected: &'static Topic,
90 type_name: &'static str,
91 },
92 TopicCountMismatch {
94 actual: u32,
95 expected: u32,
96 type_name: &'static str,
97 },
98 Decode(xdrs::ReadError),
100}
101
102impl core::fmt::Display for BuildError {
103 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104 match self {
105 BuildError::Malformed {
106 num_topics,
107 body_len,
108 } => write!(
109 f,
110 "TypedEvent::new: malformed event — {} topics do not fit a \
111 {}-byte body",
112 num_topics, body_len,
113 ),
114 BuildError::SignatureMismatch {
115 actual,
116 expected,
117 type_name,
118 } => write!(
119 f,
120 "TypedEvent::new: event type mismatch — topic 0 {:?} does \
121 not match the expected discriminant {:?} for {}",
122 actual, expected, type_name,
123 ),
124 BuildError::TopicCountMismatch {
125 actual,
126 expected,
127 type_name,
128 } => write!(
129 f,
130 "TypedEvent::new: topic count mismatch — event has {} \
131 topics but {} declares {}",
132 actual, type_name, expected,
133 ),
134 BuildError::Decode(e) => {
135 write!(f, "failed to decode event payload: {}", e)
136 }
137 }
138 }
139}
140
141pub struct TypedEvent<'a, E: Event> {
144 emitted: EmittedEvent<'a>,
145 payload: E::Payload<'a>,
146}
147
148impl<'a, E: Event> TypedEvent<'a, E> {
149 fn build(raw: &'a TxEmittedEvent) -> Result<Self, BuildError> {
153 let emitted = EmittedEvent::from(raw);
154 let payload_bytes =
158 emitted.payload().ok_or(BuildError::Malformed {
159 num_topics: raw.num_topics,
160 body_len: raw.serialized_event.len(),
161 })?;
162 if let Some(expected) = E::signature() {
163 match emitted.topic(0) {
164 Some(actual) if actual == expected => {}
165 other => {
166 return Err(BuildError::SignatureMismatch {
167 actual: other.copied(),
168 expected,
169 type_name: core::any::type_name::<E>(),
170 });
171 }
172 }
173 }
174 if raw.num_topics != E::num_topics() {
177 return Err(BuildError::TopicCountMismatch {
178 actual: raw.num_topics,
179 expected: E::num_topics(),
180 type_name: core::any::type_name::<E>(),
181 });
182 }
183 let payload =
184 E::decode_payload(payload_bytes).map_err(BuildError::Decode)?;
185 Ok(Self { emitted, payload })
186 }
187
188 #[track_caller]
197 pub fn new(raw: &'a TxEmittedEvent) -> Self {
198 match Self::build(raw) {
199 Ok(typed) => typed,
200 Err(e) => panic!("{}", e),
201 }
202 }
203
204 pub fn try_new(raw: &'a TxEmittedEvent) -> Option<Self> {
210 Self::build(raw).ok()
211 }
212
213 pub fn payload(&self) -> &E::Payload<'a> {
215 &self.payload
216 }
217
218 pub fn topic(&self, index: u32) -> Option<&'a Topic> {
220 self.emitted.topic(index)
221 }
222
223 pub fn num_topics(&self) -> u32 {
225 self.emitted.num_topics()
226 }
227
228 pub fn raw(&self) -> &EmittedEvent<'a> {
230 &self.emitted
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use thruput_xdr::event::TxEmittedEvent;
238 use thruput_xdr::types::ContractAddress;
239
240 fn raw_event(num_topics: u32, body: &'static [u8]) -> TxEmittedEvent {
243 TxEmittedEvent {
244 contract_address: ContractAddress::default(),
245 num_topics,
246 serialized_event: body.try_into().unwrap(),
247 }
248 }
249
250 #[test]
251 fn payload_is_none_when_topics_overrun_buffer() {
252 static BODY: [u8; 40] = [0u8; 40];
254 let raw = raw_event(3, &BODY);
255 let ev = EmittedEvent::from(&raw);
256 assert_eq!(ev.payload(), None);
257 assert_eq!(ev.topic(0), None);
259 }
260
261 #[test]
262 fn payload_is_none_when_num_topics_exceeds_cap() {
263 static BODY: [u8; 200] = [0u8; 200];
265 let raw = raw_event(4, &BODY);
266 let ev = EmittedEvent::from(&raw);
267 assert_eq!(ev.payload(), None);
268 }
269
270 #[test]
271 fn payload_is_some_for_well_formed_event() {
272 static BODY: [u8; 36] = {
274 let mut b = [0u8; 36];
275 b[32] = 1;
276 b[33] = 2;
277 b[34] = 3;
278 b[35] = 4;
279 b
280 };
281 let raw = raw_event(1, &BODY);
282 let ev = EmittedEvent::from(&raw);
283 assert_eq!(ev.payload(), Some(&[1u8, 2, 3, 4][..]));
284 assert!(ev.topic(0).is_some());
285 assert_eq!(ev.topic(1), None);
286 }
287
288 const SIG: Topic = [0xAB; 32];
289
290 struct SigEvent;
294
295 impl xdrs::WriteXDR for SigEvent {
296 fn write_xdr<F, W: xdrs::Writer<F>>(
297 &self,
298 _w: &mut W,
299 ) -> xdrs::WriteResult<()> {
300 Ok(())
301 }
302
303 unsafe fn write_xdr_unchecked<F, W: xdrs::WriterUnchecked<F>>(
304 &self,
305 _w: &mut W,
306 ) {
307 }
308 }
309
310 impl Event for SigEvent {
311 type Payload<'a> = u32;
312
313 fn num_topics() -> u32 {
314 1
315 }
316
317 fn signature() -> Option<&'static Topic> {
318 Some(&SIG)
319 }
320
321 fn decode_payload(bytes: &[u8]) -> Result<u32, xdrs::ReadError> {
322 match <[u8; 4]>::try_from(bytes) {
323 Ok(b) => Ok(u32::from_be_bytes(b)),
324 Err(_) => Err(xdrs::ReadError::source_empty(4, bytes.len())),
325 }
326 }
327 }
328
329 struct LenientEvent;
333
334 impl xdrs::WriteXDR for LenientEvent {
335 fn write_xdr<F, W: xdrs::Writer<F>>(
336 &self,
337 _w: &mut W,
338 ) -> xdrs::WriteResult<()> {
339 Ok(())
340 }
341
342 unsafe fn write_xdr_unchecked<F, W: xdrs::WriterUnchecked<F>>(
343 &self,
344 _w: &mut W,
345 ) {
346 }
347 }
348
349 impl Event for LenientEvent {
350 type Payload<'a> = usize;
351
352 fn num_topics() -> u32 {
353 1
354 }
355
356 fn signature() -> Option<&'static Topic> {
357 Some(&SIG)
358 }
359
360 fn decode_payload(bytes: &[u8]) -> Result<usize, xdrs::ReadError> {
361 Ok(bytes.len())
362 }
363 }
364
365 #[test]
366 fn try_new_decodes_matching_event() {
367 static BODY: [u8; 36] = {
369 let mut b = [0xABu8; 36];
370 b[32] = 0;
371 b[33] = 0;
372 b[34] = 0;
373 b[35] = 7;
374 b
375 };
376 let raw = raw_event(1, &BODY);
377 let typed =
378 TypedEvent::<SigEvent>::try_new(&raw).expect("should decode");
379 assert_eq!(*typed.payload(), 7);
380 assert_eq!(typed.topic(0), Some(&SIG));
381 }
382
383 #[test]
384 fn try_new_rejects_signature_mismatch() {
385 static BODY: [u8; 36] = [0xCD; 36];
387 let raw = raw_event(1, &BODY);
388 assert!(TypedEvent::<SigEvent>::try_new(&raw).is_none());
389 }
390
391 #[test]
392 fn try_new_rejects_malformed_event() {
393 static BODY: [u8; 40] = [0u8; 40];
395 let raw = raw_event(3, &BODY);
396 assert!(TypedEvent::<SigEvent>::try_new(&raw).is_none());
397 }
398
399 #[test]
400 fn try_new_rejects_undecodable_payload() {
401 static BODY: [u8; 35] = [0xAB; 35];
403 let raw = raw_event(1, &BODY);
404 assert!(TypedEvent::<SigEvent>::try_new(&raw).is_none());
405 }
406
407 #[test]
408 #[should_panic(expected = "event type mismatch")]
409 fn new_still_panics_on_signature_mismatch() {
410 static BODY: [u8; 36] = [0xCD; 36];
411 let raw = raw_event(1, &BODY);
412 let _ = TypedEvent::<SigEvent>::new(&raw);
413 }
414
415 #[test]
416 fn try_new_rejects_topic_count_mismatch() {
417 static BODY: [u8; 68] = [0xAB; 68];
421 let raw = raw_event(2, &BODY);
422 assert!(TypedEvent::<LenientEvent>::try_new(&raw).is_none());
423
424 static OK_BODY: [u8; 36] = [0xAB; 36];
426 let ok = raw_event(1, &OK_BODY);
427 assert!(TypedEvent::<LenientEvent>::try_new(&ok).is_some());
428 }
429
430 #[test]
431 #[should_panic(expected = "topic count mismatch")]
432 fn new_panics_on_topic_count_mismatch() {
433 static BODY: [u8; 68] = [0xAB; 68];
434 let raw = raw_event(2, &BODY);
435 let _ = TypedEvent::<LenientEvent>::new(&raw);
436 }
437}