zcash_protocol/
memo.rs

1//! Structs for handling encrypted memos.
2
3use alloc::borrow::ToOwned;
4use alloc::boxed::Box;
5use alloc::string::String;
6use core::cmp::Ordering;
7use core::fmt;
8use core::ops::Deref;
9use core::str;
10
11#[cfg(feature = "std")]
12use std::error;
13
14/// Format a byte array as a colon-delimited hex string.
15///
16/// - Source: <https://github.com/tendermint/signatory>
17/// - License: MIT / Apache 2.0
18fn fmt_colon_delimited_hex<B>(f: &mut fmt::Formatter<'_>, bytes: B) -> fmt::Result
19where
20    B: AsRef<[u8]>,
21{
22    let len = bytes.as_ref().len();
23
24    for (i, byte) in bytes.as_ref().iter().enumerate() {
25        write!(f, "{:02x}", byte)?;
26
27        if i != len - 1 {
28            write!(f, ":")?;
29        }
30    }
31
32    Ok(())
33}
34
35/// Errors that may result from attempting to construct an invalid memo.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum Error {
38    InvalidUtf8(core::str::Utf8Error),
39    TooLong(usize),
40}
41
42impl fmt::Display for Error {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Error::InvalidUtf8(e) => write!(f, "Invalid UTF-8: {}", e),
46            Error::TooLong(n) => write!(f, "Memo length {} is larger than maximum of 512", n),
47        }
48    }
49}
50
51#[cfg(feature = "std")]
52impl error::Error for Error {}
53
54/// The unencrypted memo bytes received alongside a shielded note in a Zcash transaction.
55#[derive(Clone)]
56pub struct MemoBytes(pub(crate) Box<[u8; 512]>);
57
58impl fmt::Debug for MemoBytes {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "MemoBytes(")?;
61        fmt_colon_delimited_hex(f, &self.0[..])?;
62        write!(f, ")")
63    }
64}
65
66impl PartialEq for MemoBytes {
67    fn eq(&self, rhs: &MemoBytes) -> bool {
68        self.0[..] == rhs.0[..]
69    }
70}
71
72impl Eq for MemoBytes {}
73
74impl PartialOrd for MemoBytes {
75    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
76        Some(self.cmp(other))
77    }
78}
79
80impl Ord for MemoBytes {
81    fn cmp(&self, rhs: &Self) -> Ordering {
82        self.0[..].cmp(&rhs.0[..])
83    }
84}
85
86impl MemoBytes {
87    /// Creates a `MemoBytes` indicating that no memo is present.
88    pub fn empty() -> Self {
89        let mut bytes = [0u8; 512];
90        bytes[0] = 0xF6;
91        MemoBytes(Box::new(bytes))
92    }
93
94    /// Creates a `MemoBytes` from a slice, exactly as provided.
95    ///
96    /// Returns an error if the provided slice is longer than 512 bytes. Slices shorter
97    /// than 512 bytes are padded with null bytes.
98    ///
99    /// Note that passing an empty slice to this API (or an all-zeroes slice) will result
100    /// in a memo representing an empty string. What you almost certainly want in this
101    /// case is [`MemoBytes::empty`], which uses a specific encoding to indicate that no
102    /// memo is present.
103    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
104        if bytes.len() > 512 {
105            return Err(Error::TooLong(bytes.len()));
106        }
107
108        let mut memo = [0u8; 512];
109        memo[..bytes.len()].copy_from_slice(bytes);
110        Ok(MemoBytes(Box::new(memo)))
111    }
112
113    /// Returns the raw byte array containing the memo bytes, including null padding.
114    pub fn as_array(&self) -> &[u8; 512] {
115        &self.0
116    }
117
118    /// Consumes this `MemoBytes` value and returns the underlying byte array.
119    pub fn into_bytes(self) -> [u8; 512] {
120        *self.0
121    }
122
123    /// Returns a slice of the raw bytes, excluding null padding.
124    pub fn as_slice(&self) -> &[u8] {
125        let first_null = self
126            .0
127            .iter()
128            .enumerate()
129            .rev()
130            .find(|(_, &b)| b != 0)
131            .map(|(i, _)| i + 1)
132            .unwrap_or_default();
133
134        &self.0[..first_null]
135    }
136}
137
138/// Type-safe wrapper around String to enforce memo length requirements.
139#[derive(Clone, PartialEq, Eq)]
140pub struct TextMemo(String);
141
142impl From<TextMemo> for String {
143    fn from(memo: TextMemo) -> String {
144        memo.0
145    }
146}
147
148impl Deref for TextMemo {
149    type Target = str;
150
151    #[inline]
152    fn deref(&self) -> &str {
153        self.0.deref()
154    }
155}
156
157/// An unencrypted memo received alongside a shielded note in a Zcash transaction.
158#[derive(Clone, Default)]
159pub enum Memo {
160    /// An empty memo field.
161    #[default]
162    Empty,
163    /// A memo field containing a UTF-8 string.
164    Text(TextMemo),
165    /// Some unknown memo format from ✨*the future*✨ that we can't parse.
166    Future(MemoBytes),
167    /// A memo field containing arbitrary bytes.
168    Arbitrary(Box<[u8; 511]>),
169}
170
171impl fmt::Debug for Memo {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        match self {
174            Memo::Empty => write!(f, "Memo::Empty"),
175            Memo::Text(memo) => write!(f, "Memo::Text(\"{}\")", memo.0),
176            Memo::Future(bytes) => write!(f, "Memo::Future({:0x})", bytes.0[0]),
177            Memo::Arbitrary(bytes) => {
178                write!(f, "Memo::Arbitrary(")?;
179                fmt_colon_delimited_hex(f, &bytes[..])?;
180                write!(f, ")")
181            }
182        }
183    }
184}
185
186impl PartialEq for Memo {
187    fn eq(&self, rhs: &Memo) -> bool {
188        match (self, rhs) {
189            (Memo::Empty, Memo::Empty) => true,
190            (Memo::Text(a), Memo::Text(b)) => a == b,
191            (Memo::Future(a), Memo::Future(b)) => a.0[..] == b.0[..],
192            (Memo::Arbitrary(a), Memo::Arbitrary(b)) => a[..] == b[..],
193            _ => false,
194        }
195    }
196}
197
198impl TryFrom<MemoBytes> for Memo {
199    type Error = Error;
200
201    /// Parses a `Memo` from its ZIP 302 serialization.
202    ///
203    /// Returns an error if the provided slice does not represent a valid `Memo` (for
204    /// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical).
205    fn try_from(bytes: MemoBytes) -> Result<Self, Self::Error> {
206        Self::try_from(&bytes)
207    }
208}
209
210impl TryFrom<&MemoBytes> for Memo {
211    type Error = Error;
212
213    /// Parses a `Memo` from its ZIP 302 serialization.
214    ///
215    /// Returns an error if the provided slice does not represent a valid `Memo` (for
216    /// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical).
217    fn try_from(bytes: &MemoBytes) -> Result<Self, Self::Error> {
218        match bytes.0[0] {
219            0xF6 if bytes.0.iter().skip(1).all(|&b| b == 0) => Ok(Memo::Empty),
220            0xFF => Ok(Memo::Arbitrary(Box::new(bytes.0[1..].try_into().unwrap()))),
221            b if b <= 0xF4 => str::from_utf8(bytes.as_slice())
222                .map(|r| Memo::Text(TextMemo(r.to_owned())))
223                .map_err(Error::InvalidUtf8),
224            _ => Ok(Memo::Future(bytes.clone())),
225        }
226    }
227}
228
229impl From<Memo> for MemoBytes {
230    /// Serializes the `Memo` per ZIP 302.
231    fn from(memo: Memo) -> Self {
232        match memo {
233            // Small optimisation to avoid a clone
234            Memo::Future(memo) => memo,
235            memo => (&memo).into(),
236        }
237    }
238}
239
240impl From<&Memo> for MemoBytes {
241    /// Serializes the `Memo` per ZIP 302.
242    fn from(memo: &Memo) -> Self {
243        match memo {
244            Memo::Empty => MemoBytes::empty(),
245            Memo::Text(s) => {
246                let mut bytes = [0u8; 512];
247                let s_bytes = s.0.as_bytes();
248                // s_bytes.len() is guaranteed to be <= 512
249                bytes[..s_bytes.len()].copy_from_slice(s_bytes);
250                MemoBytes(Box::new(bytes))
251            }
252            Memo::Future(memo) => memo.clone(),
253            Memo::Arbitrary(arb) => {
254                let mut bytes = [0u8; 512];
255                bytes[0] = 0xFF;
256                bytes[1..].copy_from_slice(arb.as_ref());
257                MemoBytes(Box::new(bytes))
258            }
259        }
260    }
261}
262
263impl Memo {
264    /// Parses a `Memo` from its ZIP 302 serialization.
265    ///
266    /// Returns an error if the provided slice does not represent a valid `Memo` (for
267    /// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical).
268    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
269        MemoBytes::from_bytes(bytes).and_then(TryFrom::try_from)
270    }
271
272    /// Serializes the `Memo` per ZIP 302.
273    pub fn encode(&self) -> MemoBytes {
274        self.into()
275    }
276}
277
278impl str::FromStr for Memo {
279    type Err = Error;
280
281    /// Returns a `Memo` containing the given string, or an error if the string is too long.
282    fn from_str(memo: &str) -> Result<Self, Self::Err> {
283        if memo.is_empty() {
284            Ok(Memo::Empty)
285        } else if memo.len() <= 512 {
286            Ok(Memo::Text(TextMemo(memo.to_owned())))
287        } else {
288            Err(Error::TooLong(memo.len()))
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use alloc::boxed::Box;
296    use alloc::str::FromStr;
297
298    use super::{Error, Memo, MemoBytes};
299
300    #[test]
301    fn memo_from_str() {
302        assert_eq!(
303            Memo::from_str("").unwrap().encode(),
304            MemoBytes(Box::new([
305                0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
306                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
307                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
308                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
309                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
310                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
311                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
312                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
313                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
314                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
315                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
316                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
317                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
318                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
319                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
320                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
321                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
322                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
323                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
324                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
325                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
326                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
327                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
328                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
329                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
330                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
331                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
332                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
333                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
334                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
335                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
336                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
337                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
338                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
339                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
340                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
341                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
342            ]))
343        );
344        assert_eq!(
345            Memo::from_str(
346                "thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
347                 iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
348                 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
349                 veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
350                 looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
351                 meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
352                 but it's just short enough"
353            )
354            .unwrap()
355            .encode(),
356            MemoBytes(Box::new([
357                0x74, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
358                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
359                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
360                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
361                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
362                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x69, 0x69, 0x69,
363                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
364                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
365                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
366                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
367                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
368                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
369                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
370                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
371                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
372                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
373                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
374                0x61, 0x61, 0x61, 0x61, 0x20, 0x76, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
375                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
376                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
377                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
378                0x65, 0x65, 0x72, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
379                0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
380                0x79, 0x20, 0x6c, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
381                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
382                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
383                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
384                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
385                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6e, 0x67, 0x20, 0x6d,
386                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
387                0x65, 0x65, 0x65, 0x65, 0x65, 0x6d, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
388                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
389                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
390                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
391                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x20, 0x62, 0x75, 0x74, 0x20,
392                0x69, 0x74, 0x27, 0x73, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x72,
393                0x74, 0x20, 0x65, 0x6e, 0x6f, 0x75, 0x67, 0x68
394            ]))
395        );
396        assert_eq!(
397            Memo::from_str(
398                "thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
399                 iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
400                 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
401                 veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
402                 looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
403                 meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
404                 but it's now a bit too long"
405            ),
406            Err(Error::TooLong(513))
407        );
408    }
409
410    #[test]
411    fn future_memo() {
412        let bytes = [0xFE; 512];
413        assert_eq!(
414            MemoBytes::from_bytes(&bytes).unwrap().try_into(),
415            Ok(Memo::Future(MemoBytes(Box::new(bytes))))
416        );
417    }
418
419    #[test]
420    fn arbitrary_memo() {
421        let bytes = [42; 511];
422        let memo = Memo::Arbitrary(Box::new(bytes));
423        let raw = memo.encode();
424        let encoded = raw.as_array();
425        assert_eq!(encoded[0], 0xFF);
426        assert_eq!(encoded[1..], bytes[..]);
427        assert_eq!(MemoBytes::from_bytes(encoded).unwrap().try_into(), Ok(memo));
428    }
429}