ironfish_primitives/
memo.rs

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