1use alloy_sol_types::SolType;
2use cosmwasm_std::Binary;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::error::Error;
6use std::fmt::{Display, Formatter};
7
8#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
9#[serde(rename_all = "camelCase")]
10pub struct PublicEvent {
12 pub source: String,
13 pub event_type: String,
14 pub data: Binary,
15 pub block_number: u64,
16 pub transaction_hash: Binary,
17 pub transaction_index: u32,
18 pub log_index: u32,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub enum PublicEventError {
23 UnexpectedEventType {
24 expected: &'static str,
25 actual: String,
26 },
27 AbiDecode(String),
28}
29
30impl Display for PublicEventError {
31 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
32 match self {
33 PublicEventError::UnexpectedEventType { expected, actual } => {
34 write!(
35 f,
36 "unexpected public event type: expected '{expected}', got '{actual}'"
37 )
38 }
39 PublicEventError::AbiDecode(message) => {
40 write!(f, "failed to decode ABI payload: {message}")
41 }
42 }
43 }
44}
45
46impl Error for PublicEventError {}
47
48pub fn require_event_type(
49 event: &PublicEvent,
50 expected: &'static str,
51) -> Result<(), PublicEventError> {
52 if event.event_type == expected {
53 Ok(())
54 } else {
55 Err(PublicEventError::UnexpectedEventType {
56 expected,
57 actual: event.event_type.clone(),
58 })
59 }
60}
61
62pub fn decode_abi<T: SolType>(data: &[u8]) -> Result<T::RustType, PublicEventError> {
63 T::abi_decode(data, true).map_err(|err| PublicEventError::AbiDecode(err.to_string()))
64}
65
66pub fn decode_abi_for<T: SolType>(
67 event: &PublicEvent,
68 expected_event_type: &'static str,
69) -> Result<T::RustType, PublicEventError> {
70 require_event_type(event, expected_event_type)?;
71 decode_abi::<T>(event.data.as_slice())
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use alloy_sol_types::{sol, SolValue};
78
79 #[test]
80 fn public_event_cbor_round_trip_preserves_binary_fields() {
81 let event = PublicEvent {
82 source: "0xabc".to_string(),
83 event_type: "consume".to_string(),
84 data: Binary::from(vec![0xde, 0xad, 0xbe, 0xef]),
85 block_number: 42,
86 transaction_hash: Binary::from(vec![0xaa, 0xbb, 0xcc]),
87 transaction_index: 2,
88 log_index: 7,
89 };
90
91 let encoded = crate::abi::cbor_to_vec(&event).expect("serialize public event");
92 let decoded: PublicEvent =
93 crate::abi::cbor_from_slice(&encoded).expect("deserialize public event");
94
95 assert_eq!(decoded, event);
96 }
97
98 #[test]
99 fn require_event_type_accepts_matching_type() {
100 let event = PublicEvent {
101 source: "0xabc".to_string(),
102 event_type: "consume".to_string(),
103 data: Binary::default(),
104 block_number: 0,
105 transaction_hash: Binary::default(),
106 transaction_index: 0,
107 log_index: 0,
108 };
109
110 assert_eq!(require_event_type(&event, "consume"), Ok(()));
111 }
112
113 #[test]
114 fn require_event_type_rejects_wrong_type() {
115 let event = PublicEvent {
116 source: "0xabc".to_string(),
117 event_type: "redeem".to_string(),
118 data: Binary::default(),
119 block_number: 0,
120 transaction_hash: Binary::default(),
121 transaction_index: 0,
122 log_index: 0,
123 };
124
125 assert_eq!(
126 require_event_type(&event, "consume"),
127 Err(PublicEventError::UnexpectedEventType {
128 expected: "consume",
129 actual: "redeem".to_string(),
130 })
131 );
132 }
133
134 #[test]
135 fn decode_abi_decodes_typed_payload() {
136 type ConsumeEvent = sol!((uint32,bool));
137
138 let expected = (123u32, true);
139 let encoded = expected.abi_encode();
140
141 let decoded: <ConsumeEvent as SolType>::RustType =
142 decode_abi::<ConsumeEvent>(&encoded).expect("decode payload");
143
144 assert_eq!(decoded, expected);
145 }
146
147 #[test]
148 fn decode_abi_for_checks_event_type_before_decoding() {
149 type ConsumeEvent = sol!((uint32,bool));
150
151 let event = PublicEvent {
152 source: "0xabc".to_string(),
153 event_type: "redeem".to_string(),
154 data: Binary::from(vec![0x00, 0x01]),
155 block_number: 0,
156 transaction_hash: Binary::default(),
157 transaction_index: 0,
158 log_index: 0,
159 };
160
161 let err =
162 decode_abi_for::<ConsumeEvent>(&event, "consume").expect_err("must reject wrong event type");
163 assert_eq!(
164 err,
165 PublicEventError::UnexpectedEventType {
166 expected: "consume",
167 actual: "redeem".to_string(),
168 }
169 );
170 }
171
172 #[test]
173 fn decode_abi_rejects_invalid_bytes() {
174 type ConsumeEvent = sol!((uint32,bool));
175
176 let err = decode_abi::<ConsumeEvent>(&[0x01, 0x02]).expect_err("must reject invalid ABI");
177 assert!(matches!(err, PublicEventError::AbiDecode(_)));
178 }
179}