1use alloc::string::String;
8use alloc::vec::Vec;
9
10use bytecheck::CheckBytes;
11use rkyv::{
12 ser::serializers::{BufferScratch, BufferSerializer, CompositeSerializer},
13 Archive, Deserialize, Serialize,
14};
15
16use crate::SCRATCH_BUF_BYTES;
17
18#[derive(
20 Debug,
21 Clone,
22 PartialEq,
23 Eq,
24 PartialOrd,
25 Ord,
26 Hash,
27 Archive,
28 Serialize,
29 Deserialize,
30)]
31#[archive_attr(derive(CheckBytes))]
32pub struct Event {
33 pub source: ContractId,
34 pub topic: String,
35 pub data: Vec<u8>,
36}
37
38pub type StandardBufSerializer<'a> = CompositeSerializer<
40 BufferSerializer<&'a mut [u8]>,
41 BufferScratch<&'a mut [u8; SCRATCH_BUF_BYTES]>,
42>;
43
44pub const CONTRACT_ID_BYTES: usize = 32;
46
47#[derive(
49 PartialEq,
50 Eq,
51 Archive,
52 Serialize,
53 CheckBytes,
54 Deserialize,
55 PartialOrd,
56 Ord,
57 Hash,
58 Clone,
59 Copy,
60)]
61#[archive(as = "Self")]
62#[repr(C)]
63pub struct ContractId([u8; CONTRACT_ID_BYTES]);
64
65impl ContractId {
66 pub const fn from_bytes(bytes: [u8; CONTRACT_ID_BYTES]) -> Self {
68 Self(bytes)
69 }
70
71 pub const fn to_bytes(self) -> [u8; CONTRACT_ID_BYTES] {
73 self.0
74 }
75
76 pub fn as_bytes(&self) -> &[u8] {
79 &self.0
80 }
81
82 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
85 &mut self.0
86 }
87}
88
89impl From<[u8; CONTRACT_ID_BYTES]> for ContractId {
90 fn from(bytes: [u8; CONTRACT_ID_BYTES]) -> Self {
91 Self::from_bytes(bytes)
92 }
93}
94
95impl AsRef<[u8]> for ContractId {
96 fn as_ref(&self) -> &[u8] {
97 self.as_bytes()
98 }
99}
100
101impl AsMut<[u8]> for ContractId {
102 fn as_mut(&mut self) -> &mut [u8] {
103 self.as_bytes_mut()
104 }
105}
106
107impl PartialEq<[u8; CONTRACT_ID_BYTES]> for ContractId {
108 fn eq(&self, other: &[u8; CONTRACT_ID_BYTES]) -> bool {
109 self.0.eq(other)
110 }
111}
112
113impl core::fmt::Debug for ContractId {
117 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
118 core::fmt::Display::fmt(self, f)
119 }
120}
121
122impl core::fmt::Display for ContractId {
128 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
129 if f.alternate() {
130 write!(f, "0x")?
131 }
132 for byte in self.0 {
133 write!(f, "{:02x}", &byte)?
134 }
135 Ok(())
136 }
137}
138
139impl TryFrom<String> for ContractId {
140 type Error = core::fmt::Error;
141
142 fn try_from(value: String) -> core::result::Result<Self, Self::Error> {
146 let value = value.trim_start_matches("0x");
147 let decoded = hex::decode(value).map_err(|_| core::fmt::Error)?;
148 let bytes: [u8; CONTRACT_ID_BYTES] =
149 decoded.try_into().map_err(|_| core::fmt::Error)?;
150
151 Ok(ContractId::from_bytes(bytes))
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use alloc::{format, string::ToString};
158
159 use rand::rngs::StdRng;
160 use rand::{Rng, SeedableRng};
161
162 use super::*;
163
164 const CONTRACT_ID_STR: &str =
165 "0000000000000000000000000000000000000000000000000000000000000000";
166 const CONTRACT_ID_STR_PRETTY: &str =
167 "0x0000000000000000000000000000000000000000000000000000000000000000";
168
169 #[test]
170 fn contract_id_display() {
171 let contract_id = ContractId::from_bytes([0u8; CONTRACT_ID_BYTES]);
172 assert_eq!(format!("{}", contract_id), CONTRACT_ID_STR);
173
174 let contract_id = ContractId::from_bytes([0u8; CONTRACT_ID_BYTES]);
175 assert_eq!(format!("{:#?}", contract_id), CONTRACT_ID_STR_PRETTY);
176 }
177
178 #[test]
179 fn contract_id_debug() {
180 let contract_id = ContractId::from_bytes([0u8; CONTRACT_ID_BYTES]);
181 assert_eq!(format!("{}", contract_id), CONTRACT_ID_STR);
182 }
183
184 #[test]
185 fn contract_id_to_from_string() {
186 let mut rng = StdRng::seed_from_u64(1618);
187 let contract_id = ContractId::from_bytes(rng.gen());
188
189 let string = contract_id.to_string();
190
191 assert_eq!(string.starts_with("0x"), false);
192 assert_eq!(string.len(), CONTRACT_ID_BYTES * 2);
193
194 let contract_id_from_string = ContractId::try_from(string).unwrap();
195
196 assert_eq!(contract_id, contract_id_from_string);
197 }
198
199 #[test]
200 fn contract_id_try_from_invalid_string() {
201 let too_short = ContractId::try_from("0x".to_string()).is_err();
202
203 let too_long =
204 ContractId::try_from(format!("{}0", CONTRACT_ID_STR)).is_err();
205
206 assert!(too_short);
207 assert!(too_long);
208 }
209}