stellar_base/
memo.rs

1//! Transaction memo.
2use std::io::{Read, Write};
3
4use crate::error::{Error, Result};
5use crate::xdr;
6
7/// Maximum length of text memo.
8pub const MAX_MEMO_TEXT_LEN: usize = 28;
9
10/// Maximum length of hash and return memo.
11pub const MAX_HASH_LEN: usize = 32;
12
13/// Memo attached to transactions.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum Memo {
16    /// No memo
17    None,
18    /// Text Memo
19    Text(String),
20    /// Id Memo
21    Id(u64),
22    /// Hash Memo
23    Hash([u8; 32]),
24    /// Return Memo
25    Return([u8; 32]),
26}
27
28impl Memo {
29    /// Create new empty memo.
30    pub fn new_none() -> Memo {
31        Memo::None
32    }
33
34    /// Create new id memo.
35    pub fn new_id(id: u64) -> Memo {
36        Memo::Id(id)
37    }
38
39    /// Create new text memo. `text` must be shorter than 28 bytes.
40    pub fn new_text<S: Into<String>>(text: S) -> Result<Memo> {
41        let text = text.into();
42        if text.len() > MAX_MEMO_TEXT_LEN {
43            Err(Error::InvalidMemoText)
44        } else {
45            Ok(Memo::Text(text))
46        }
47    }
48
49    /// Create new hash memo.
50    pub fn new_hash(hash: &[u8]) -> Result<Memo> {
51        if hash.len() > MAX_HASH_LEN {
52            Err(Error::InvalidMemoHash)
53        } else {
54            let mut memo_hash: [u8; 32] = Default::default();
55            memo_hash[..hash.len()].copy_from_slice(hash);
56            Ok(Memo::Hash(memo_hash))
57        }
58    }
59
60    /// Creates new return memo.
61    pub fn new_return(ret: &[u8]) -> Result<Memo> {
62        if ret.len() > MAX_HASH_LEN {
63            Err(Error::InvalidMemoReturn)
64        } else {
65            let mut memo_ret: [u8; 32] = Default::default();
66            memo_ret[..ret.len()].copy_from_slice(ret);
67            Ok(Memo::Return(memo_ret))
68        }
69    }
70
71    /// Returns true if memo is None. Returns false otherwise.
72    pub fn is_none(&self) -> bool {
73        matches!(self, Memo::None)
74    }
75
76    /// If the memo is an Id, returns its value. Returns None otherwise.
77    pub fn as_id(&self) -> Option<&u64> {
78        match *self {
79            Memo::Id(ref id) => Some(id),
80            _ => None,
81        }
82    }
83
84    /// If the memo is an Id, returns its mutable value. Returns None otherwise.
85    pub fn as_id_mut(&mut self) -> Option<&mut u64> {
86        match *self {
87            Memo::Id(ref mut id) => Some(id),
88            _ => None,
89        }
90    }
91
92    /// Returns true if memo is Id. Returns false otherwise.
93    pub fn is_id(&self) -> bool {
94        self.as_id().is_some()
95    }
96
97    /// If the memo is a Text, returns its value. Returns None otherwise.
98    pub fn as_text(&self) -> Option<&str> {
99        match *self {
100            Memo::Text(ref text) => Some(text),
101            _ => None,
102        }
103    }
104
105    /// If the memo is a Text, returns its value. Returns None otherwise.
106    pub fn as_text_mut(&mut self) -> Option<&mut str> {
107        match *self {
108            Memo::Text(ref mut text) => Some(text),
109            _ => None,
110        }
111    }
112
113    /// Returns true if memo is Text. Returns false otherwise.
114    pub fn is_text(&self) -> bool {
115        self.as_text().is_some()
116    }
117
118    /// If the memo is a Hash, returns its value. Returns None otherwise.
119    pub fn as_hash(&self) -> Option<&[u8; 32]> {
120        match *self {
121            Memo::Hash(ref hash) => Some(hash),
122            _ => None,
123        }
124    }
125
126    /// If the memo is a Hash, returns its mutable value. Returns None otherwise.
127    pub fn as_hash_mut(&mut self) -> Option<&mut [u8; 32]> {
128        match *self {
129            Memo::Hash(ref mut hash) => Some(hash),
130            _ => None,
131        }
132    }
133
134    /// Returns true if memo is a Hash.
135    pub fn is_hash(&self) -> bool {
136        self.as_hash().is_some()
137    }
138
139    /// If the memo is a Return, returns its value. Returns None otherwise.
140    pub fn as_return(&self) -> Option<&[u8; 32]> {
141        match *self {
142            Memo::Return(ref hash) => Some(hash),
143            _ => None,
144        }
145    }
146
147    /// If the memo is a Return, returns its mutable value. Returns None otherwise.
148    pub fn as_return_mut(&mut self) -> Option<&mut [u8; 32]> {
149        match *self {
150            Memo::Return(ref mut hash) => Some(hash),
151            _ => None,
152        }
153    }
154
155    /// Returns true if memo is a Return.
156    pub fn is_return(&self) -> bool {
157        self.as_return().is_some()
158    }
159
160    /// Returns the memo xdr object.
161    pub fn to_xdr(&self) -> Result<xdr::Memo> {
162        match self {
163            Memo::None => Ok(xdr::Memo::None),
164            Memo::Text(text) => Ok(xdr::Memo::Text(
165                text.try_into().map_err(|_| Error::InvalidMemoText)?,
166            )),
167            Memo::Id(id) => Ok(xdr::Memo::Id(*id)),
168            Memo::Hash(hash) => Ok(xdr::Memo::Hash(hash.clone().into())),
169            Memo::Return(ret) => Ok(xdr::Memo::Return(ret.clone().into())),
170        }
171    }
172
173    /// Creates a new memo from the xdr object.
174    pub fn from_xdr(x: &xdr::Memo) -> Result<Memo> {
175        match x {
176            xdr::Memo::None => Ok(Memo::new_none()),
177            xdr::Memo::Text(text) => {
178                let string: String = text.try_into().map_err(|_| Error::InvalidMemoText)?;
179                Memo::new_text(string)
180            }
181            xdr::Memo::Id(id) => Ok(Memo::new_id(*id)),
182            xdr::Memo::Hash(hash) => Memo::new_hash(&hash.0),
183            xdr::Memo::Return(ret) => Memo::new_return(&ret.0),
184        }
185    }
186}
187
188impl Default for Memo {
189    fn default() -> Memo {
190        Memo::new_none()
191    }
192}
193
194impl xdr::WriteXdr for Memo {
195    fn write_xdr<W: Write>(&self, w: &mut xdr::Limited<W>) -> xdr::Result<()> {
196        let xdr_memo = self.to_xdr().map_err(|_| xdr::Error::Invalid)?;
197        xdr_memo.write_xdr(w)
198    }
199}
200
201impl xdr::ReadXdr for Memo {
202    fn read_xdr<R: Read>(r: &mut xdr::Limited<R>) -> xdr::Result<Self> {
203        let xdr_result = xdr::Memo::read_xdr(r)?;
204        Self::from_xdr(&xdr_result).map_err(|_| xdr::Error::Invalid)
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use super::Memo;
211    use crate::xdr::{XDRDeserialize, XDRSerialize};
212
213    #[test]
214    fn test_defaults_to_none() {
215        let memo: Memo = Default::default();
216        assert!(memo.is_none());
217    }
218
219    #[test]
220    fn test_memo_none() {
221        let memo = Memo::new_none();
222        assert!(memo.is_none());
223        assert!(!memo.is_id());
224        assert!(!memo.is_text());
225        assert!(!memo.is_hash());
226        assert!(!memo.is_return());
227
228        assert_eq!(None, memo.as_id());
229        assert_eq!(None, memo.as_text());
230        assert_eq!(None, memo.as_hash());
231        assert_eq!(None, memo.as_return());
232    }
233
234    #[test]
235    fn test_memo_id() {
236        let mut memo = Memo::new_id(1234);
237        assert!(!memo.is_none());
238        assert!(memo.is_id());
239        assert!(!memo.is_text());
240        assert!(!memo.is_hash());
241        assert!(!memo.is_return());
242
243        assert_eq!(Some(&1234), memo.as_id());
244        assert_eq!(None, memo.as_text());
245        assert_eq!(None, memo.as_hash());
246        assert_eq!(None, memo.as_return());
247
248        *memo.as_id_mut().unwrap() = 456;
249        assert_eq!(Some(&456), memo.as_id());
250    }
251
252    #[test]
253    fn test_memo_text() {
254        let memo = Memo::new_text("Short text memo").unwrap();
255        assert!(!memo.is_none());
256        assert!(!memo.is_id());
257        assert!(memo.is_text());
258        assert!(!memo.is_hash());
259        assert!(!memo.is_return());
260
261        assert_eq!(None, memo.as_id());
262        assert_eq!("Short text memo", memo.as_text().unwrap());
263        assert_eq!(None, memo.as_hash());
264        assert_eq!(None, memo.as_return());
265    }
266
267    #[test]
268    fn test_memo_hash() {
269        let mut memo = Memo::new_hash(&[1, 2, 3, 4, 5]).unwrap();
270        assert!(!memo.is_none());
271        assert!(!memo.is_id());
272        assert!(!memo.is_text());
273        assert!(memo.is_hash());
274        assert!(!memo.is_return());
275
276        assert_eq!(None, memo.as_id());
277        assert_eq!(None, memo.as_text());
278        assert_eq!(
279            vec![
280                1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
281                0, 0, 0, 0
282            ],
283            memo.as_hash().unwrap()
284        );
285        assert_eq!(None, memo.as_return());
286
287        memo.as_hash_mut().unwrap()[super::MAX_HASH_LEN - 1] = 9;
288
289        assert_eq!(
290            vec![
291                1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
292                0, 0, 0, 9
293            ],
294            memo.as_hash().unwrap()
295        );
296    }
297
298    #[test]
299    fn test_memo_return() {
300        let mut memo = Memo::new_return(&[1, 2, 3, 4, 5]).unwrap();
301        assert!(!memo.is_none());
302        assert!(!memo.is_id());
303        assert!(!memo.is_text());
304        assert!(!memo.is_hash());
305        assert!(memo.is_return());
306
307        assert_eq!(None, memo.as_id());
308        assert_eq!(None, memo.as_text());
309        assert_eq!(None, memo.as_hash());
310        assert_eq!(
311            vec![
312                1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
313                0, 0, 0, 0
314            ],
315            memo.as_return().unwrap()
316        );
317
318        memo.as_return_mut().unwrap()[super::MAX_HASH_LEN - 1] = 9;
319        assert_eq!(
320            vec![
321                1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
322                0, 0, 0, 9
323            ],
324            memo.as_return().unwrap()
325        );
326    }
327
328    #[test]
329    fn test_memo_text_too_long() {
330        let result =
331            Memo::new_text("This is a very long text that will not fit in the memo 100% sure.");
332        assert!(result.is_err());
333    }
334
335    #[test]
336    fn test_memo_hash_too_long() {
337        let mut hash = Vec::new();
338        hash.resize(33, b'1');
339        let result = Memo::new_hash(&hash);
340        assert!(result.is_err());
341    }
342
343    #[test]
344    fn test_memo_return_too_long() {
345        let mut hash = Vec::new();
346        hash.resize(33, b'1');
347        let result = Memo::new_return(&hash);
348        assert!(result.is_err());
349    }
350
351    #[test]
352    fn test_memo_none_xdr_ser_de() {
353        let original = Memo::new_none();
354        let xdr = original.xdr_base64().unwrap();
355        assert_eq!("AAAAAA==", xdr);
356        let back = Memo::from_xdr_base64(&xdr).unwrap();
357        assert_eq!(original, back);
358    }
359
360    #[test]
361    fn test_memo_id_xdr_ser_de() {
362        let original = Memo::new_id(u64::MAX);
363        let xdr = original.xdr_base64().unwrap();
364        assert_eq!("AAAAAv//////////", xdr);
365        let back = Memo::from_xdr_base64(&xdr).unwrap();
366        assert_eq!(original, back);
367    }
368
369    #[test]
370    fn test_memo_hash_xdr_ser_de() {
371        let mut hash = Vec::new();
372        for i in 0..32 {
373            hash.push(i as u8);
374        }
375        let original = Memo::new_hash(&hash).unwrap();
376        let xdr = original.xdr_base64().unwrap();
377        assert_eq!("AAAAAwABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4f", xdr);
378        let back = Memo::from_xdr_base64(&xdr).unwrap();
379        assert_eq!(original, back);
380    }
381
382    #[test]
383    fn test_return_hash_xdr_ser_de() {
384        let mut hash = Vec::new();
385        for i in 0..32 {
386            hash.push(i as u8);
387        }
388        let original = Memo::new_return(&hash).unwrap();
389        let xdr = original.xdr_base64().unwrap();
390        assert_eq!("AAAABAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4f", xdr);
391        let back = Memo::from_xdr_base64(&xdr).unwrap();
392        assert_eq!(original, back);
393    }
394}