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(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
22#[serde(rename_all = "camelCase")]
23/// Request payload for JS-driven public-event ABI encoding.
24pub 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}