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(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
22#[serde(rename_all = "camelCase")]
23pub struct EncodePublicEventRequest {
25 pub event_type: String,
26 pub data: Binary,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum PublicEventError {
31 UnexpectedEventType {
32 expected: &'static str,
33 actual: String,
34 },
35 AbiDecode(String),
36}
37
38impl Display for PublicEventError {
39 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40 match self {
41 PublicEventError::UnexpectedEventType { expected, actual } => {
42 write!(
43 f,
44 "unexpected public event type: expected '{expected}', got '{actual}'"
45 )
46 }
47 PublicEventError::AbiDecode(message) => {
48 write!(f, "failed to decode ABI payload: {message}")
49 }
50 }
51 }
52}
53
54impl Error for PublicEventError {}
55
56pub fn require_event_type(
57 event: &PublicEvent,
58 expected: &'static str,
59) -> Result<(), PublicEventError> {
60 if event.event_type == expected {
61 Ok(())
62 } else {
63 Err(PublicEventError::UnexpectedEventType {
64 expected,
65 actual: event.event_type.clone(),
66 })
67 }
68}
69
70pub fn decode_abi<T: SolType>(data: &[u8]) -> Result<T::RustType, PublicEventError> {
71 T::abi_decode(data, true).map_err(|err| PublicEventError::AbiDecode(err.to_string()))
72}
73
74pub fn encode_abi<T: SolType>(value: &T::RustType) -> Vec<u8> {
75 T::abi_encode(value)
76}
77
78pub fn decode_abi_for<T: SolType>(
79 event: &PublicEvent,
80 expected_event_type: &'static str,
81) -> Result<T::RustType, PublicEventError> {
82 require_event_type(event, expected_event_type)?;
83 decode_abi::<T>(event.data.as_slice())
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use alloy_sol_types::sol;
90
91 #[test]
92 fn public_event_cbor_round_trip_preserves_binary_fields() {
93 let event = PublicEvent {
94 source: "0xabc".to_string(),
95 event_type: "consume".to_string(),
96 data: Binary::from(vec![0xde, 0xad, 0xbe, 0xef]),
97 block_number: 42,
98 transaction_hash: Binary::from(vec![0xaa, 0xbb, 0xcc]),
99 transaction_index: 2,
100 log_index: 7,
101 };
102
103 let encoded = crate::abi::cbor_to_vec(&event).expect("serialize public event");
104 let decoded: PublicEvent =
105 crate::abi::cbor_from_slice(&encoded).expect("deserialize public event");
106
107 assert_eq!(decoded, event);
108 }
109
110 #[test]
111 fn encode_public_event_request_cbor_round_trip_preserves_fields() {
112 let request = EncodePublicEventRequest {
113 event_type: "consume".to_string(),
114 data: Binary::from(vec![0xa1, 0x66, b'a', b'm', b'o', b'u', b'n', b't']),
115 };
116
117 let encoded = crate::abi::cbor_to_vec(&request).expect("serialize encode request");
118 let decoded: EncodePublicEventRequest =
119 crate::abi::cbor_from_slice(&encoded).expect("deserialize encode request");
120
121 assert_eq!(decoded, request);
122 }
123
124 #[test]
125 fn require_event_type_accepts_matching_type() {
126 let event = PublicEvent {
127 source: "0xabc".to_string(),
128 event_type: "consume".to_string(),
129 data: Binary::default(),
130 block_number: 0,
131 transaction_hash: Binary::default(),
132 transaction_index: 0,
133 log_index: 0,
134 };
135
136 assert_eq!(require_event_type(&event, "consume"), Ok(()));
137 }
138
139 #[test]
140 fn require_event_type_rejects_wrong_type() {
141 let event = PublicEvent {
142 source: "0xabc".to_string(),
143 event_type: "redeem".to_string(),
144 data: Binary::default(),
145 block_number: 0,
146 transaction_hash: Binary::default(),
147 transaction_index: 0,
148 log_index: 0,
149 };
150
151 assert_eq!(
152 require_event_type(&event, "consume"),
153 Err(PublicEventError::UnexpectedEventType {
154 expected: "consume",
155 actual: "redeem".to_string(),
156 })
157 );
158 }
159
160 #[test]
161 fn decode_abi_decodes_typed_payload() {
162 type ConsumeEvent = sol!((uint32,bool));
163
164 let expected = (123u32, true);
165 let encoded = encode_abi::<ConsumeEvent>(&expected);
166
167 let decoded: <ConsumeEvent as SolType>::RustType =
168 decode_abi::<ConsumeEvent>(&encoded).expect("decode payload");
169
170 assert_eq!(decoded, expected);
171 }
172
173 #[test]
174 fn encode_abi_round_trips_through_decode() {
175 type ConsumeEvent = sol!((uint32,bool));
176
177 let expected = (456u32, false);
178 let encoded = encode_abi::<ConsumeEvent>(&expected);
179 let decoded = decode_abi::<ConsumeEvent>(&encoded).expect("decode encoded payload");
180
181 assert_eq!(decoded, expected);
182 }
183
184 #[test]
185 fn decode_abi_for_checks_event_type_before_decoding() {
186 type ConsumeEvent = sol!((uint32,bool));
187
188 let event = PublicEvent {
189 source: "0xabc".to_string(),
190 event_type: "redeem".to_string(),
191 data: Binary::from(vec![0x00, 0x01]),
192 block_number: 0,
193 transaction_hash: Binary::default(),
194 transaction_index: 0,
195 log_index: 0,
196 };
197
198 let err =
199 decode_abi_for::<ConsumeEvent>(&event, "consume").expect_err("must reject wrong event type");
200 assert_eq!(
201 err,
202 PublicEventError::UnexpectedEventType {
203 expected: "consume",
204 actual: "redeem".to_string(),
205 }
206 );
207 }
208
209 #[test]
210 fn decode_abi_rejects_invalid_bytes() {
211 type ConsumeEvent = sol!((uint32,bool));
212
213 let err = decode_abi::<ConsumeEvent>(&[0x01, 0x02]).expect_err("must reject invalid ABI");
214 assert!(matches!(err, PublicEventError::AbiDecode(_)));
215 }
216}