Skip to main content

ownable_std/
register.rs

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")]
10/// Canonical contract-facing payload for public events routed through Anchor.
11pub 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}