casper_types/contract_messages/
topics.rs

1use crate::{
2    bytesrepr::{self, FromBytes, ToBytes},
3    checksummed_hex, BlockTime,
4};
5
6use core::convert::TryFrom;
7
8use alloc::{string::String, vec::Vec};
9use core::fmt::{Debug, Display, Formatter};
10
11#[cfg(feature = "datasize")]
12use datasize::DataSize;
13#[cfg(any(feature = "testing", test))]
14use rand::{
15    distributions::{Distribution, Standard},
16    Rng,
17};
18#[cfg(feature = "json-schema")]
19use schemars::JsonSchema;
20use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
21
22use super::error::FromStrError;
23
24/// The length in bytes of a topic name hash.
25pub const TOPIC_NAME_HASH_LENGTH: usize = 32;
26
27/// The hash of the name of the message topic.
28#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
29#[cfg_attr(feature = "datasize", derive(DataSize))]
30#[cfg_attr(
31    feature = "json-schema",
32    derive(JsonSchema),
33    schemars(description = "The hash of the name of the message topic.")
34)]
35pub struct TopicNameHash(
36    #[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))]
37    pub  [u8; TOPIC_NAME_HASH_LENGTH],
38);
39
40impl TopicNameHash {
41    /// Returns a new [`TopicNameHash`] based on the specified value.
42    pub const fn new(topic_name_hash: [u8; TOPIC_NAME_HASH_LENGTH]) -> TopicNameHash {
43        TopicNameHash(topic_name_hash)
44    }
45
46    /// Returns inner value of the topic hash.
47    pub fn value(&self) -> [u8; TOPIC_NAME_HASH_LENGTH] {
48        self.0
49    }
50
51    /// Formats the [`TopicNameHash`] as a prefixed, hex-encoded string.
52    pub fn to_formatted_string(self) -> String {
53        base16::encode_lower(&self.0)
54    }
55
56    /// Parses a string formatted as per `Self::to_formatted_string()` into a [`TopicNameHash`].
57    pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
58        let bytes =
59            <[u8; TOPIC_NAME_HASH_LENGTH]>::try_from(checksummed_hex::decode(input)?.as_ref())?;
60        Ok(TopicNameHash(bytes))
61    }
62}
63
64impl ToBytes for TopicNameHash {
65    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
66        let mut buffer = bytesrepr::allocate_buffer(self)?;
67        buffer.append(&mut self.0.to_bytes()?);
68        Ok(buffer)
69    }
70
71    fn serialized_length(&self) -> usize {
72        self.0.serialized_length()
73    }
74}
75
76impl FromBytes for TopicNameHash {
77    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
78        let (hash, rem) = FromBytes::from_bytes(bytes)?;
79        Ok((TopicNameHash(hash), rem))
80    }
81}
82
83impl Serialize for TopicNameHash {
84    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
85        if serializer.is_human_readable() {
86            self.to_formatted_string().serialize(serializer)
87        } else {
88            self.0.serialize(serializer)
89        }
90    }
91}
92
93impl<'de> Deserialize<'de> for TopicNameHash {
94    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
95        if deserializer.is_human_readable() {
96            let formatted_string = String::deserialize(deserializer)?;
97            TopicNameHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
98        } else {
99            let bytes = <[u8; TOPIC_NAME_HASH_LENGTH]>::deserialize(deserializer)?;
100            Ok(TopicNameHash(bytes))
101        }
102    }
103}
104
105impl Display for TopicNameHash {
106    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
107        write!(f, "{}", base16::encode_lower(&self.0))
108    }
109}
110
111impl Debug for TopicNameHash {
112    fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
113        write!(f, "MessageTopicHash({})", base16::encode_lower(&self.0))
114    }
115}
116
117#[cfg(any(feature = "testing", test))]
118impl Distribution<TopicNameHash> for Standard {
119    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TopicNameHash {
120        TopicNameHash(rng.gen())
121    }
122}
123
124impl From<[u8; TOPIC_NAME_HASH_LENGTH]> for TopicNameHash {
125    fn from(value: [u8; TOPIC_NAME_HASH_LENGTH]) -> Self {
126        TopicNameHash(value)
127    }
128}
129
130/// Summary of a message topic that will be stored in global state.
131#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
132#[cfg_attr(feature = "datasize", derive(DataSize))]
133#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
134pub struct MessageTopicSummary {
135    /// Number of messages in this topic.
136    pub(crate) message_count: u32,
137    /// Block timestamp in which these messages were emitted.
138    pub(crate) blocktime: BlockTime,
139    /// Name of the topic.
140    pub(crate) topic_name: String,
141}
142
143impl MessageTopicSummary {
144    /// Creates a new topic summary.
145    pub fn new(message_count: u32, blocktime: BlockTime, topic_name: String) -> Self {
146        Self {
147            message_count,
148            blocktime,
149            topic_name,
150        }
151    }
152
153    /// Returns the number of messages that were sent on this topic.
154    pub fn message_count(&self) -> u32 {
155        self.message_count
156    }
157
158    /// Returns the block time.
159    pub fn blocktime(&self) -> BlockTime {
160        self.blocktime
161    }
162
163    /// Returns the topic name.
164    pub fn topic_name(&self) -> &str {
165        &self.topic_name
166    }
167}
168
169impl ToBytes for MessageTopicSummary {
170    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
171        let mut buffer = bytesrepr::allocate_buffer(self)?;
172        buffer.append(&mut self.message_count.to_bytes()?);
173        buffer.append(&mut self.blocktime.to_bytes()?);
174        buffer.append(&mut self.topic_name.to_bytes()?);
175        Ok(buffer)
176    }
177
178    fn serialized_length(&self) -> usize {
179        self.message_count.serialized_length()
180            + self.blocktime.serialized_length()
181            + self.topic_name.serialized_length()
182    }
183}
184
185impl FromBytes for MessageTopicSummary {
186    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
187        let (message_count, rem) = FromBytes::from_bytes(bytes)?;
188        let (blocktime, rem) = FromBytes::from_bytes(rem)?;
189        let (topic_name, rem) = FromBytes::from_bytes(rem)?;
190        Ok((
191            MessageTopicSummary {
192                message_count,
193                blocktime,
194                topic_name,
195            },
196            rem,
197        ))
198    }
199}
200
201const TOPIC_OPERATION_ADD_TAG: u8 = 0;
202const OPERATION_MAX_SERIALIZED_LEN: usize = 1;
203
204/// Operations that can be performed on message topics.
205#[derive(Debug, PartialEq)]
206pub enum MessageTopicOperation {
207    /// Add a new message topic.
208    Add,
209}
210
211impl MessageTopicOperation {
212    /// Maximum serialized length of a message topic operation.
213    pub const fn max_serialized_len() -> usize {
214        OPERATION_MAX_SERIALIZED_LEN
215    }
216}
217
218impl ToBytes for MessageTopicOperation {
219    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
220        let mut buffer = bytesrepr::allocate_buffer(self)?;
221        match self {
222            MessageTopicOperation::Add => buffer.push(TOPIC_OPERATION_ADD_TAG),
223        }
224        Ok(buffer)
225    }
226
227    fn serialized_length(&self) -> usize {
228        match self {
229            MessageTopicOperation::Add => 1,
230        }
231    }
232}
233
234impl FromBytes for MessageTopicOperation {
235    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
236        let (tag, remainder): (u8, &[u8]) = FromBytes::from_bytes(bytes)?;
237        match tag {
238            TOPIC_OPERATION_ADD_TAG => Ok((MessageTopicOperation::Add, remainder)),
239            _ => Err(bytesrepr::Error::Formatting),
240        }
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use crate::bytesrepr;
247
248    use super::*;
249
250    #[test]
251    fn serialization_roundtrip() {
252        let topic_name_hash = TopicNameHash::new([0x4du8; TOPIC_NAME_HASH_LENGTH]);
253        bytesrepr::test_serialization_roundtrip(&topic_name_hash);
254
255        let topic_summary =
256            MessageTopicSummary::new(10, BlockTime::new(100), "topic_name".to_string());
257        bytesrepr::test_serialization_roundtrip(&topic_summary);
258
259        let topic_operation = MessageTopicOperation::Add;
260        bytesrepr::test_serialization_roundtrip(&topic_operation);
261    }
262
263    #[test]
264    fn json_roundtrip() {
265        let topic_name_hash = TopicNameHash::new([0x4du8; TOPIC_NAME_HASH_LENGTH]);
266        let json_string = serde_json::to_string_pretty(&topic_name_hash).unwrap();
267        let decoded: TopicNameHash = serde_json::from_str(&json_string).unwrap();
268        assert_eq!(decoded, topic_name_hash);
269
270        let topic_summary =
271            MessageTopicSummary::new(10, BlockTime::new(100), "topic_name".to_string());
272        let json_string = serde_json::to_string_pretty(&topic_summary).unwrap();
273        let decoded: MessageTopicSummary = serde_json::from_str(&json_string).unwrap();
274        assert_eq!(decoded, topic_summary);
275    }
276}