1use std::str::FromStr;
2
3use crate::xdr;
4use num_traits::ToPrimitive;
5
6const MEMO_NONE: &str = "none";
7const MEMO_ID: &str = "id";
8const MEMO_TEXT: &str = "text";
9const MEMO_HASH: &str = "hash";
10const MEMO_RETURN: &str = "return";
11
12pub enum MemoValue {
13 NoneValue,
14 IdValue(String),
15 TextValue(Vec<u8>),
16 HashValue(Vec<u8>),
17 ReturnValue(Vec<u8>),
18}
19
20#[derive(Debug)]
21pub struct Memo {
22 memo_type: String,
23 value: Option<String>,
24}
25
26pub trait MemoBehavior {
28 fn new(memo_type: &str, value: Option<&str>) -> Result<Self, Box<dyn std::error::Error>>
29 where
30 Self: Sized;
31 fn id(input: &str) -> Self
32 where
33 Self: Sized;
34 fn text(input: &str) -> Self
35 where
36 Self: Sized;
37 fn text_buffer(input: Vec<u8>) -> Self
38 where
39 Self: Sized;
40 fn hash_buffer(input: Vec<u8>) -> Self
41 where
42 Self: Sized;
43 fn return_hash(input: Vec<u8>) -> Self
44 where
45 Self: Sized;
46 fn none() -> Self
47 where
48 Self: Sized;
49 fn value(&self) -> Result<MemoValue, &'static str>;
50 fn from_xdr_object(object: xdr::Memo) -> Result<Self, &'static str>
51 where
52 Self: Sized;
53 fn to_xdr_object(&self) -> Option<xdr::Memo>;
54 fn _validate_id_value(value: &str) -> Result<(), String>;
55 fn _validate_text_value(value: &str);
56 fn _validate_hash_value(value: &[u8]);
57}
58
59impl MemoBehavior for Memo {
60 fn new(memo_type: &str, value: Option<&str>) -> Result<Self, Box<dyn std::error::Error>> {
61 let mut value_buf = None;
62 match memo_type {
63 MEMO_NONE => {}
64 MEMO_ID => {
65 Self::_validate_id_value(value.expect("Expected a value for MEMO_ID"));
66 if let Some(v) = value {
67 unsafe {
68 value_buf = Some(String::from_utf8_unchecked(v.into()));
69 }
70 }
71 }
72 MEMO_TEXT => {
73 Self::_validate_text_value(value.expect("Expected a value for MEMO_TEXT"));
74 if let Some(v) = value {
75 unsafe {
76 value_buf = Some(String::from_utf8_unchecked(v.into()));
77 }
78 }
79 }
80 MEMO_HASH | MEMO_RETURN => {
81 Self::_validate_hash_value(unsafe {
82 String::from_utf8_unchecked(value.unwrap().as_bytes().to_vec()).as_bytes()
83 });
84 if let Some(v) = value {
85 value_buf = Some(v.into());
86 }
87 }
88 _ => return Err("Invalid memo type".into()),
89 }
90
91 Ok(Memo {
92 memo_type: memo_type.to_string(),
93 value: value_buf,
94 })
95 }
96
97 fn _validate_id_value(value: &str) -> Result<(), String> {
98 let error = format!("Expects an int64 as a string. Got {}", value);
99
100 let number = match value.parse::<i64>() {
101 Ok(num) => num,
102 Err(_) => return Err(error.clone()),
103 };
104
105 Ok(())
106 }
107
108 fn _validate_text_value(value: &str) {
109 assert!(value.len() <= 28, "String is longer than 28 bytes");
110 let _ = xdr::Memo::Text(value.try_into().unwrap());
111 }
112
113 fn id(input: &str) -> Self {
114 unsafe {
115 Memo {
116 memo_type: MEMO_ID.to_string(),
117 value: Some(String::from_utf8_unchecked(input.into())),
118 }
119 }
120 }
121
122 fn text(input: &str) -> Self {
123 assert!(input.len() <= 28, "String is longer than 28 bytes");
124
125 unsafe {
126 Memo {
127 memo_type: MEMO_TEXT.to_string(),
128 value: Some(String::from_utf8_unchecked(input.into())),
129 }
130 }
131 }
132
133 fn text_buffer(input: Vec<u8>) -> Self {
134 unsafe {
135 Memo {
136 memo_type: MEMO_TEXT.to_string(),
137 value: Some(String::from_utf8_unchecked(input)),
138 }
139 }
140 }
141
142 fn hash_buffer(input: Vec<u8>) -> Self {
143 Self::_validate_hash_value(unsafe {
144 String::from_utf8_unchecked(input.clone()).as_bytes()
145 });
146
147 unsafe {
148 Memo {
149 memo_type: MEMO_HASH.to_string(),
150 value: Some(String::from_utf8_unchecked(input)),
151 }
152 }
153 }
154
155 fn return_hash(input: Vec<u8>) -> Self {
156 Self::_validate_hash_value(unsafe {
157 String::from_utf8_unchecked(input.clone()).as_bytes()
158 });
159
160 unsafe {
161 Memo {
162 memo_type: MEMO_RETURN.to_string(),
163 value: Some(String::from_utf8_unchecked(input)),
164 }
165 }
166 }
167
168 fn _validate_hash_value(value: &[u8]) {
169 if value.len() == 64 {
170 let hex_str = match std::str::from_utf8(value) {
172 Ok(s) => s,
173 Err(_) => panic!("Expects a 32 byte hash value or hex encoded string"),
174 };
175
176 if hex::decode(hex_str).is_err() {
177 panic!("Expects a 32 byte hash value or hex encoded string");
178 }
179 let decoded = match hex::decode(hex_str) {
180 Ok(d) => d,
181 Err(_) => panic!("Failed to decode hex string: {}", hex_str),
182 };
183 if decoded.len() != 32 {
184 panic!("Expects a 32 byte hash value or hex encoded string");
185 }
186 } else if value.len() != 32 {
187 let s = std::str::from_utf8(value).unwrap_or("<non-UTF8 data>");
188 panic!("Expects a 32 byte hash value or hex encoded string");
189 }
190 }
191
192 fn none() -> Self {
193 Self {
194 memo_type: MEMO_NONE.to_owned(),
195 value: None,
196 }
197 }
198
199 fn value(&self) -> Result<MemoValue, &'static str> {
200 match self.memo_type.as_str() {
201 MEMO_NONE => Ok(MemoValue::NoneValue),
202 MEMO_ID => Ok(MemoValue::IdValue(self.value.clone().unwrap())),
203 MEMO_TEXT => Ok(MemoValue::TextValue(
204 self.value.clone().unwrap().as_bytes().to_vec(),
205 )),
206 MEMO_HASH | MEMO_RETURN => Ok(MemoValue::HashValue(
207 self.value.clone().unwrap().as_bytes().to_vec(),
208 )),
209 _ => Err("Invalid memo type"),
210 }
211 }
212
213 fn from_xdr_object(object: xdr::Memo) -> Result<Self, &'static str> {
214 unsafe {
215 match object {
216 xdr::Memo::None => Ok(Memo {
217 memo_type: MEMO_NONE.to_owned(),
218 value: None,
219 }),
220 xdr::Memo::Text(x) => Ok(Memo {
221 memo_type: MEMO_TEXT.to_owned(),
222 value: Some(String::from_utf8_unchecked(x.to_vec())),
223 }),
224 xdr::Memo::Id(x) => Ok(Memo {
225 memo_type: MEMO_ID.to_owned(),
226 value: Some(x.to_string()),
227 }),
228 xdr::Memo::Hash(x) => Ok(Memo {
229 memo_type: MEMO_HASH.to_owned(),
230 value: Some(String::from_utf8_unchecked(x.0.to_vec())),
231 }),
232 xdr::Memo::Return(x) => Ok(Memo {
233 memo_type: MEMO_RETURN.to_owned(),
234 value: Some(String::from_utf8_unchecked(x.0.to_vec())),
235 }),
236 }
237 }
238 }
239
240 fn to_xdr_object(&self) -> Option<xdr::Memo> {
241 match self.memo_type.as_str() {
242 MEMO_NONE => Some(xdr::Memo::None),
243 MEMO_ID => Some(xdr::Memo::Id(
244 u64::from_str(self.value.clone().unwrap().as_str()).unwrap(),
245 )),
246 MEMO_TEXT => Some(xdr::Memo::Text(
247 self.value.clone().unwrap().as_str().try_into().unwrap(),
248 )),
249 MEMO_HASH => Some(xdr::Memo::Hash(
250 xdr::Hash::from_str(&hex::encode(self.value.clone().unwrap().as_str())).unwrap(),
251 )),
252 MEMO_RETURN => Some(xdr::Memo::Return(
254 xdr::Hash::from_str(&hex::encode(self.value.clone().unwrap().as_str())).unwrap(),
255 )),
256 _ => None,
257 }
258 }
259}
260
261fn assert_panic<F: FnOnce(), S: AsRef<str>>(f: F, expected_msg: S) {
262 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
263 match result {
264 Ok(_) => panic!("Function did not panic as expected"),
265 Err(err) => {
266 if let Some(s) = err.downcast_ref::<&str>() {
267 assert!(
268 s.contains(expected_msg.as_ref()),
269 "Unexpected panic message. Got: {}",
270 s
271 );
272 } else {
273 panic!("Unexpected panic type");
274 }
275 }
276 }
277}
278
279#[cfg(test)]
280mod tests {
281 use crate::memo::MemoBehavior;
282 use crate::xdr;
283 use crate::xdr::WriteXdr;
284 use core::panic;
285
286 use crate::memo::{MEMO_HASH, MEMO_NONE, MEMO_RETURN};
287
288 use super::{assert_panic, Memo, MEMO_ID, MEMO_TEXT};
289
290 #[test]
291 fn constructor_throws_error_when_type_is_invalid() {
292 let result = Memo::new("test", None);
293 assert!(result.is_err());
294 let err_msg = format!("{:?}", result.err().unwrap());
295 assert!(err_msg.contains("Invalid memo type"));
296 }
297
298 #[test]
299 fn memo_none_converts_to_from_xdr() {
300 let memo = Memo::none().to_xdr_object().unwrap();
301 let base_memo = Memo::from_xdr_object(memo).unwrap();
302 assert_eq!(base_memo.memo_type, MEMO_NONE);
303 assert!(base_memo.value.is_none());
304 }
305
306 #[test]
307 fn memo_text_returns_value_for_correct_argument() {
308 let _ = Memo::new(MEMO_TEXT, Some("test"));
309
310 let memo_utf8 = Memo::new(MEMO_TEXT, Some("三代之時")).unwrap();
311 let val = match memo_utf8.to_xdr_object().unwrap() {
312 xdr::Memo::Text(x) => x.to_utf8_string().unwrap(),
313
314 _ => panic!("Invalid Type"),
315 };
316 let b = String::from("三代之時");
317 print!("xx {}", val);
318
319 assert_eq!(val, b, "Memo text value does not match expected value");
320 }
321
322 #[test]
323 fn returns_value_for_correct_argument_utf8() {
324 let vec2: Vec<u8> = vec![0xd1];
325 let expected: Vec<u8> = vec![
326 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xd1, 0x00, 0x00, 0x00,
330 ];
331 let memo_text = Memo::text_buffer(vec2.clone())
333 .to_xdr_object()
334 .unwrap()
335 .to_xdr(xdr::Limits::none())
336 .unwrap();
337
338 unsafe {
339 let memo_text_2 =
340 Memo::new(MEMO_TEXT, Some(&String::from_utf8_unchecked(vec2.clone())))
341 .unwrap()
342 .to_xdr_object()
343 .unwrap()
344 .to_xdr(xdr::Limits::none())
345 .unwrap();
346 assert_eq!(memo_text_2, expected);
347 }
348 assert_eq!(memo_text, expected);
349 }
350
351 #[test]
352 fn converts_to_from_xdr_object() {
353 let memo = Memo::text("test").to_xdr_object().unwrap();
354
355 let val = match memo.clone() {
356 xdr::Memo::Text(x) => x.to_string(),
357 _ => panic!("Invalid Type"),
358 };
359
360 assert_eq!(val, "test");
361
362 let base_memo = Memo::from_xdr_object(memo.clone()).unwrap();
363 assert_eq!(base_memo.memo_type, MEMO_TEXT);
364 assert_eq!(base_memo.value.unwrap(), "test");
365 }
366
367 #[test]
368 fn converts_to_from_xdr_object_buffer() {
369 let buf = vec![0xd1];
370 let memo = Memo::text_buffer(buf.clone()).to_xdr_object().unwrap();
372 let val = match memo.clone() {
374 xdr::Memo::Text(x) => x,
375 _ => panic!("Invalid Type"),
376 };
377
378 unsafe {
379 assert_eq!(val.to_vec(), buf);
380 }
381
382 let base_memo = Memo::from_xdr_object(memo).unwrap();
383 assert_eq!(base_memo.memo_type, MEMO_TEXT);
384
385 let val = match base_memo.value().unwrap() {
386 crate::memo::MemoValue::TextValue(x) => x,
387 _ => panic!("Bad"),
388 };
389 unsafe {
390 assert_eq!(val.to_vec(), buf);
391 }
392 }
393
394 #[test]
395 fn errors_when_string_longer_than_28_bytes() {
396 let long_string = "12345678901234567890123456789";
397 let scenario_1 = || {
398 Memo::text(long_string);
399 };
400 assert_panic(scenario_1, "String is longer than 28 bytes");
401
402 let scenario_2 = || {
403 let long_utf8_string = "三代之時三代之時三代之時";
404 Memo::text(long_utf8_string);
405 };
406 assert_panic(scenario_2, "String is longer than 28 bytes");
407 }
408
409 fn memo_id_handles_correct_argument() {
410 Memo::new(MEMO_ID, Some("1000"));
411 Memo::new(MEMO_ID, Some("0"));
412 }
413
414 #[test]
415 fn converts_to_from_xdr_object_if() {
416 let memo = Memo::id("1000").to_xdr_object().unwrap();
417
418 let val = match memo {
419 xdr::Memo::Id(x) => x,
420 _ => panic!("Invalid Type"),
421 };
422
423 assert_eq!(val.to_string(), "1000");
424
425 let base_memo = Memo::from_xdr_object(memo).unwrap();
426
427 match base_memo.memo_type.as_str() {
428 MEMO_ID => (),
429 _ => panic!("Invalid"),
430 }
431
432 assert_eq!(base_memo.value.unwrap(), "1000");
433 }
434
435 #[test]
436 fn hash_converts_to_from_xdr_object() {
437 let buffer = vec![10u8; 32];
439
440 let memo = Memo::hash_buffer(buffer.clone()).to_xdr_object().unwrap();
441
442 let val = match memo.clone() {
443 xdr::Memo::Hash(x) => x,
444 _ => panic!("Invalid"),
445 };
446 assert_eq!(val.0.len(), 32);
447 unsafe {
448 assert_eq!(
449 val.to_string(),
450 String::from_utf8_unchecked(hex::encode(buffer.clone()).into())
451 );
452 }
453 let base_memo = Memo::from_xdr_object(memo).unwrap();
454
455 match base_memo.memo_type.as_str() {
456 MEMO_HASH => (),
457 _ => panic!("Invalid"),
458 }
459 assert_eq!(base_memo.value.clone().unwrap().len(), 32);
460
461 let base_memo_hex = hex::encode(base_memo.value.unwrap());
462 let buffer_hex = hex::encode(buffer.clone());
463
464 assert_eq!(base_memo_hex, buffer_hex);
465 }
466
467 #[test]
468 fn return_converts_to_from_xdr_object() {
469 let buffer = vec![10u8; 32];
470
471 let buffer_hex: String = hex::encode(&buffer);
473
474 let memo = Memo::return_hash(unsafe { buffer.clone() })
476 .to_xdr_object()
477 .unwrap();
478
479 let val = match memo.clone() {
480 xdr::Memo::Return(x) => x,
481 _ => panic!("Invalid"),
482 };
483
484 assert_eq!(val.0.len(), 32);
485 unsafe {
486 assert_eq!(
487 val.to_string(),
488 String::from_utf8_unchecked(hex::encode(buffer.clone()).into())
489 );
490 }
491
492 let base_memo = Memo::from_xdr_object(memo).unwrap();
493
494 match base_memo.memo_type.as_str() {
495 MEMO_RETURN => (),
496 _ => panic!("Invalid"),
497 };
498
499 assert_eq!(base_memo.value.clone().unwrap().len(), 32);
500 let base_memo_hex = hex::encode(base_memo.value.unwrap());
501 let buffer_hex = hex::encode(buffer.clone());
502 assert_eq!(base_memo_hex, buffer_hex);
503 }
504
505 #[test]
506 fn returns_value_for_correct_argument() {
507 let methods = [Memo::hash_buffer, Memo::return_hash];
508
509 for method in &methods {
510 let _ = method(vec![0u8; 32]);
511
512 let hex_str = "0000000000000000000000000000000000000000000000000000000000000000";
513 let _ = method(hex::decode(hex_str).expect("Failed to decode hex"));
514 }
515
516 let binding_1 =
517 hex::decode("00000000000000000000000000000000000000000000000000000000000000").unwrap();
518 let binding_2 =
519 hex::decode("000000000000000000000000000000000000000000000000000000000000000000")
520 .unwrap();
521 let binding_3 = &vec![0u8; 33][..];
522
523 let invalid_inputs = vec![
524 &[] as &[u8], &b"test"[..], &[0, 10, 20],
527 binding_3, &binding_1[..], &binding_2[..], ];
532
533 for method in &methods {
534 for input in &invalid_inputs {
535 let scenario_1 = || {
536 method(input.to_vec());
537 };
538 assert_panic(
539 scenario_1,
540 "Expects a 32 byte hash value or hex encoded string",
541 );
542 }
543 }
544 }
545}