casper_types/
contract_messages.rs

1//! Data types for interacting with contract level messages.
2
3mod error;
4mod messages;
5mod topics;
6
7pub use error::FromStrError;
8pub use messages::{Message, MessageChecksum, MessagePayload, Messages};
9pub use topics::{
10    MessageTopicOperation, MessageTopicSummary, TopicNameHash, TOPIC_NAME_HASH_LENGTH,
11};
12
13use crate::{
14    bytesrepr::{self, FromBytes, ToBytes},
15    EntityAddr,
16};
17
18use alloc::{string::String, vec::Vec};
19use core::fmt::{Debug, Display, Formatter};
20
21#[cfg(feature = "datasize")]
22use datasize::DataSize;
23#[cfg(any(feature = "testing", test))]
24use rand::{
25    distributions::{Distribution, Standard},
26    Rng,
27};
28#[cfg(feature = "json-schema")]
29use schemars::JsonSchema;
30use serde::{Deserialize, Serialize};
31
32const TOPIC_FORMATTED_STRING_PREFIX: &str = "topic-";
33const MESSAGE_ADDR_PREFIX: &str = "message-";
34
35/// MessageTopicAddr
36#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, Debug)]
37#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
38#[cfg_attr(feature = "datasize", derive(DataSize))]
39pub struct MessageAddr {
40    /// The entity addr.
41    entity_addr: EntityAddr,
42    /// The hash of the name of the message topic.
43    topic_name_hash: TopicNameHash,
44    /// The message index.
45    message_index: Option<u32>,
46}
47
48impl MessageAddr {
49    /// Constructs a new topic address based on the addressable entity addr and the hash of the
50    /// message topic name.
51    pub const fn new_topic_addr(entity_addr: EntityAddr, topic_name_hash: TopicNameHash) -> Self {
52        Self {
53            entity_addr,
54            topic_name_hash,
55            message_index: None,
56        }
57    }
58
59    /// Constructs a new message address based on the addressable entity addr, the hash of the
60    /// message topic name and the message index in the topic.
61    pub const fn new_message_addr(
62        entity_addr: EntityAddr,
63        topic_name_hash: TopicNameHash,
64        message_index: u32,
65    ) -> Self {
66        Self {
67            entity_addr,
68            topic_name_hash,
69            message_index: Some(message_index),
70        }
71    }
72
73    /// Formats the [`MessageAddr`] as a prefixed, hex-encoded string.
74    pub fn to_formatted_string(self) -> String {
75        match self.message_index {
76            Some(index) => {
77                format!(
78                    "{}{}-{}-{:x}",
79                    MESSAGE_ADDR_PREFIX,
80                    self.entity_addr,
81                    self.topic_name_hash.to_formatted_string(),
82                    index,
83                )
84            }
85            None => {
86                format!(
87                    "{}{}{}-{}",
88                    MESSAGE_ADDR_PREFIX,
89                    TOPIC_FORMATTED_STRING_PREFIX,
90                    self.entity_addr,
91                    self.topic_name_hash.to_formatted_string(),
92                )
93            }
94        }
95    }
96
97    /// Parses a formatted string into a [`MessageAddr`].
98    pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
99        let remainder = input
100            .strip_prefix(MESSAGE_ADDR_PREFIX)
101            .ok_or(FromStrError::InvalidPrefix)?;
102
103        let (remainder, message_index) = match remainder.strip_prefix(TOPIC_FORMATTED_STRING_PREFIX)
104        {
105            Some(topic_string) => (topic_string, None),
106            None => {
107                let (remainder, message_index_str) = remainder
108                    .rsplit_once('-')
109                    .ok_or(FromStrError::MissingMessageIndex)?;
110                (remainder, Some(u32::from_str_radix(message_index_str, 16)?))
111            }
112        };
113
114        let (entity_addr_str, topic_name_hash_str) = remainder
115            .rsplit_once('-')
116            .ok_or(FromStrError::MissingMessageIndex)?;
117
118        let entity_addr = EntityAddr::from_formatted_str(entity_addr_str)
119            .map_err(FromStrError::EntityAddrParseError)?;
120
121        let topic_name_hash = TopicNameHash::from_formatted_str(topic_name_hash_str)?;
122        Ok(MessageAddr {
123            entity_addr,
124            topic_name_hash,
125            message_index,
126        })
127    }
128
129    /// Returns the entity addr of this message topic.
130    pub fn entity_addr(&self) -> EntityAddr {
131        self.entity_addr
132    }
133
134    /// Returns the topic name hash of this message topic.
135    pub fn topic_name_hash(&self) -> TopicNameHash {
136        self.topic_name_hash
137    }
138
139    /// Returns None in the case of the key for a message topic summary,
140    /// else Some with the sequential index of the underlying message within the topic.
141    pub fn message_index(&self) -> Option<u32> {
142        self.message_index
143    }
144}
145
146impl Display for MessageAddr {
147    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
148        match self.message_index {
149            Some(index) => {
150                write!(
151                    f,
152                    "{}-{}-{:x}",
153                    self.entity_addr, self.topic_name_hash, index,
154                )
155            }
156            None => {
157                write!(f, "{}-{}", self.entity_addr, self.topic_name_hash)
158            }
159        }
160    }
161}
162
163impl ToBytes for MessageAddr {
164    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
165        let mut buffer = bytesrepr::allocate_buffer(self)?;
166        buffer.append(&mut self.entity_addr.to_bytes()?);
167        buffer.append(&mut self.topic_name_hash.to_bytes()?);
168        buffer.append(&mut self.message_index.to_bytes()?);
169        Ok(buffer)
170    }
171
172    fn serialized_length(&self) -> usize {
173        self.entity_addr.serialized_length()
174            + self.topic_name_hash.serialized_length()
175            + self.message_index.serialized_length()
176    }
177}
178
179impl FromBytes for MessageAddr {
180    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
181        let (entity_addr, rem) = FromBytes::from_bytes(bytes)?;
182        let (topic_hash, rem) = FromBytes::from_bytes(rem)?;
183        let (message_index, rem) = FromBytes::from_bytes(rem)?;
184        Ok((
185            MessageAddr {
186                entity_addr,
187                topic_name_hash: topic_hash,
188                message_index,
189            },
190            rem,
191        ))
192    }
193}
194
195#[cfg(any(feature = "testing", test))]
196impl Distribution<MessageAddr> for Standard {
197    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> MessageAddr {
198        MessageAddr {
199            entity_addr: rng.gen(),
200            topic_name_hash: rng.gen(),
201            message_index: rng.gen(),
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use crate::{bytesrepr, KEY_HASH_LENGTH};
209
210    use super::{topics::TOPIC_NAME_HASH_LENGTH, *};
211
212    #[test]
213    fn serialization_roundtrip() {
214        let topic_addr = MessageAddr::new_topic_addr(
215            EntityAddr::SmartContract([1; KEY_HASH_LENGTH]),
216            [2; TOPIC_NAME_HASH_LENGTH].into(),
217        );
218        bytesrepr::test_serialization_roundtrip(&topic_addr);
219
220        let message_addr = MessageAddr::new_message_addr(
221            EntityAddr::SmartContract([1; KEY_HASH_LENGTH]),
222            [2; TOPIC_NAME_HASH_LENGTH].into(),
223            3,
224        );
225        bytesrepr::test_serialization_roundtrip(&message_addr);
226    }
227}