1use std::sync::Arc;
5use std::sync::atomic::AtomicUsize;
6
7use mysten_common::ZipDebugEqIteratorExt;
8
9use futures::future::try_join_all;
10use futures::join;
11use indexmap::IndexMap;
12use sui_types::object::rpc_visitor as RV;
13
14use crate::v2::meter::Meter;
15use crate::v2::parser::Chain;
16use crate::v2::parser::Literal;
17use crate::v2::parser::Parser;
18use crate::v2::parser::Strand;
19mod error;
20mod interpreter;
21mod lexer;
22mod meter;
23mod parser;
24mod peek;
25mod value;
26mod visitor;
27mod writer;
28
29pub use crate::v2::error::Error;
30pub use crate::v2::error::FormatError;
31pub use crate::v2::interpreter::Interpreter;
32pub use crate::v2::meter::Limits;
33pub use crate::v2::value::OwnedSlice;
34pub use crate::v2::value::Store;
35pub use crate::v2::value::Value;
36
37pub struct Extract<'s>(Chain<'s>);
39
40pub struct Name<'s>(Literal<'s>);
42
43pub struct Format<'s>(Vec<Strand<'s>>);
45
46pub struct Display<'s> {
48 fields: Vec<Field<'s>>,
49}
50
51struct Field<'s> {
53 key: Sourced<'s, Vec<Strand<'s>>>,
54 val: Sourced<'s, Vec<Strand<'s>>>,
55}
56
57struct Sourced<'s, T> {
59 src: &'s str,
60 val: Result<T, FormatError>,
61}
62
63impl<'s> Extract<'s> {
64 pub fn parse(limits: Limits, src: &'s str) -> Result<Self, FormatError> {
69 let mut budget = limits.budget();
70 let mut meter = Meter::new(limits.max_depth, &mut budget);
71 let chain = Parser::chain(src, &mut meter)?;
72
73 Ok(Self(chain))
74 }
75
76 pub async fn extract<S: Store>(
82 &'s self,
83 interpreter: &'s Interpreter<S>,
84 ) -> Result<Option<Value<'s>>, FormatError> {
85 interpreter.eval_chain(&self.0).await
86 }
87}
88
89impl<'s> Name<'s> {
90 pub fn parse(limits: Limits, src: &'s str) -> Result<Self, FormatError> {
95 let mut budget = limits.budget();
96 let mut meter = Meter::new(limits.max_depth, &mut budget);
97 let literal = Parser::literal(src, &mut meter)?;
98
99 Ok(Self(literal))
100 }
101
102 pub async fn eval<S: Store>(
105 &'s self,
106 interpreter: &'s Interpreter<S>,
107 ) -> Result<Option<Value<'s>>, FormatError> {
108 interpreter.eval_literal(&self.0).await
109 }
110}
111
112impl<'s> Format<'s> {
113 pub fn parse(limits: Limits, src: &'s str) -> Result<Self, FormatError> {
118 let mut budget = limits.budget();
119 let mut meter = Meter::new(limits.max_depth, &mut budget);
120 let format = Parser::format(src, &mut meter)?;
121
122 Ok(Self(format))
123 }
124
125 pub async fn format<V: RV::Format>(
127 &'s self,
128 interpreter: &'s Interpreter<impl Store>,
129 max_depth: usize,
130 max_output_size: usize,
131 ) -> Result<V, FormatError> {
132 let used_size = AtomicUsize::new(0);
133 let mut meter = writer::Meter::new(&used_size, max_output_size, max_depth);
134 let Some(value) = interpreter.eval_strands(&self.0).await? else {
135 return Ok(V::null(&mut meter)?);
136 };
137
138 writer::write(meter, value)
139 }
140}
141
142impl<'s> Display<'s> {
143 pub fn parse(
152 limits: Limits,
153 display_fields: impl IntoIterator<Item = (&'s str, &'s str)>,
154 ) -> Result<Self, Error> {
155 let mut fields = Vec::new();
156 let mut budget = limits.budget();
157 let mut meter = Meter::new(limits.max_depth, &mut budget);
158
159 let mut parse = |src: &'s str| {
160 let val = match Parser::format(src, &mut meter) {
161 Err(FormatError::TooBig) => return Err(Error::TooBig),
162 Err(FormatError::TooManyLoads) => return Err(Error::TooManyLoads),
163 Err(e) => Err(e),
164 Ok(ast) => Ok(ast),
165 };
166
167 Ok(Sourced { src, val })
168 };
169
170 for (k, v) in display_fields.into_iter() {
171 let key = parse(k)?;
172 let val = parse(v)?;
173 fields.push(Field { key, val });
174 }
175
176 Ok(Self { fields })
177 }
178
179 pub async fn display<V: RV::Format>(
185 &'s self,
186 max_depth: usize,
187 max_output_size: usize,
188 interpreter: &'s Interpreter<impl Store>,
189 ) -> Result<IndexMap<String, Result<V, FormatError>>, Error> {
190 let used_size = Arc::new(AtomicUsize::new(0));
191 let mut output = IndexMap::new();
192
193 let names = try_join_all(self.fields.iter().map(|kvp| {
197 let used_size = used_size.clone();
198 async move {
199 let strands = match kvp.key.val.as_ref() {
200 Ok(strands) => strands,
201 Err(e) => return Ok(Err(e.clone())),
202 };
203
204 let mut meter = writer::Meter::new(&used_size, max_output_size, max_depth);
205 let evaluated = match interpreter.eval_strands(strands).await {
206 Ok(Some(v)) => v,
207 Ok(None) => match V::null(&mut meter) {
208 Ok(value) => return Ok(Ok(value)),
209 Err(err) => return Ok(Err(err.into())),
210 },
211 Err(e) => return Ok(Err(e)),
212 };
213
214 match writer::write(meter, evaluated) {
215 Err(FormatError::TooMuchOutput) => Err(Error::TooMuchOutput),
216 other => Ok(other),
217 }
218 }
219 }));
220
221 let values = try_join_all(self.fields.iter().map(|kvp| {
222 let used_size = used_size.clone();
223 async move {
224 let strands = match kvp.val.val.as_ref() {
225 Ok(strands) => strands,
226 Err(e) => return Ok(Err(e.clone())),
227 };
228
229 let mut meter = writer::Meter::new(&used_size, max_output_size, max_depth);
230 let evaluated = match interpreter.eval_strands(strands).await {
231 Ok(Some(v)) => v,
232 Ok(None) => match V::null(&mut meter) {
233 Ok(value) => return Ok(Ok(value)),
234 Err(err) => return Ok(Err(err.into())),
235 },
236 Err(e) => return Ok(Err(e)),
237 };
238
239 match writer::write(meter, evaluated) {
240 Err(FormatError::TooMuchOutput) => Err(Error::TooMuchOutput),
241 other => Ok(other),
242 }
243 }
244 }));
245
246 let (names, values) = join!(names, values);
247
248 let names = names?;
249 let values = values?;
250
251 for ((field, name), value) in self.fields.iter().zip_debug_eq(names).zip_debug_eq(values) {
252 use indexmap::map::Entry;
253
254 let src = field.key.src;
255
256 let n = match name {
257 Ok(v) if v.is_string() => v.as_string().unwrap().to_owned(),
258 Ok(v) if v.is_null() => return Err(Error::NameEmpty(src.to_owned())),
259 Ok(_) => return Err(Error::NameInvalid(src.to_owned())),
260 Err(e) => return Err(Error::NameEvaluation(src.to_owned(), e)),
261 };
262
263 match output.entry(n) {
264 Entry::Occupied(e) => return Err(Error::NameDuplicate(e.key().to_owned())),
265 Entry::Vacant(e) => {
266 e.insert(value);
267 }
268 }
269 }
270
271 Ok(output)
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use std::str::FromStr;
278 use std::sync::Arc;
279 use std::sync::atomic::AtomicUsize;
280
281 use async_trait::async_trait;
282 use base64::Engine as _;
283 use base64::engine::general_purpose::STANDARD;
284 use insta::assert_debug_snapshot;
285 use insta::assert_json_snapshot;
286 use move_core_types::account_address::AccountAddress;
287 use move_core_types::annotated_value::MoveTypeLayout;
288 use move_core_types::annotated_value::MoveTypeLayout as L;
289 use move_core_types::language_storage::TypeTag;
290 use move_core_types::u256::U256;
291 use serde::Serialize;
292 use sui_types::base_types::move_ascii_str_layout;
293 use sui_types::base_types::move_utf8_str_layout;
294 use sui_types::base_types::url_layout;
295 use sui_types::dynamic_field::DynamicFieldInfo;
296 use sui_types::dynamic_field::derive_dynamic_field_id;
297 use sui_types::id::ID;
298 use sui_types::id::UID;
299 use tokio::sync::Barrier;
300 use tokio::time::Duration;
301
302 use crate::v2::value::tests::MockStore;
303 use crate::v2::value::tests::enum_;
304 use crate::v2::value::tests::optional_;
305 use crate::v2::value::tests::struct_;
306 use crate::v2::value::tests::vec_map;
307 use crate::v2::value::tests::vector_;
308
309 use super::*;
310
311 const ONE_MB: usize = 1024 * 1024;
312
313 async fn extract(
315 store: MockStore,
316 bytes: Vec<u8>,
317 layout: MoveTypeLayout,
318 path: &str,
319 ) -> Result<Option<serde_json::Value>, FormatError> {
320 let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
321 let used = AtomicUsize::new(0);
322
323 let chain = Extract::parse(Limits::default(), path)?;
324 let Some(value) = chain.extract(&interpreter).await? else {
325 return Ok(None);
326 };
327
328 let meter = writer::Meter::new(&used, usize::MAX, usize::MAX);
329 Ok(Some(value.format_json(meter)?))
330 }
331
332 async fn extract_owned(
333 store: impl Store,
334 bytes: Vec<u8>,
335 layout: MoveTypeLayout,
336 path: &str,
337 ) -> Result<Option<OwnedSlice>, FormatError> {
338 let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
339 let chain = Extract::parse(Limits::default(), path)?;
340 let Some(value) = chain.extract(&interpreter).await? else {
341 return Ok(None);
342 };
343
344 Ok(value.into_owned_slice())
345 }
346
347 async fn dynamic_field_id(
348 store: MockStore,
349 bytes: Vec<u8>,
350 layout: MoveTypeLayout,
351 parent: AccountAddress,
352 literal: &str,
353 ) -> Result<Option<AccountAddress>, FormatError> {
354 let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
355 let name = Name::parse(Limits::default(), literal)?;
356 let Some(value) = name.eval(&interpreter).await? else {
357 return Ok(None);
358 };
359
360 Ok(Some(value.derive_dynamic_field_id(parent)?.into()))
361 }
362
363 async fn dynamic_object_field_id(
364 store: MockStore,
365 bytes: Vec<u8>,
366 layout: MoveTypeLayout,
367 parent: AccountAddress,
368 literal: &str,
369 ) -> Result<Option<AccountAddress>, FormatError> {
370 let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
371 let name = Name::parse(Limits::default(), literal)?;
372 let Some(value) = name.eval(&interpreter).await? else {
373 return Ok(None);
374 };
375
376 Ok(Some(value.derive_dynamic_object_field_id(parent)?.into()))
377 }
378
379 async fn derived_object_id(
380 store: MockStore,
381 bytes: Vec<u8>,
382 layout: MoveTypeLayout,
383 parent: AccountAddress,
384 literal: &str,
385 ) -> Result<Option<AccountAddress>, FormatError> {
386 let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
387 let name = Name::parse(Limits::default(), literal)?;
388 let Some(value) = name.eval(&interpreter).await? else {
389 return Ok(None);
390 };
391
392 Ok(Some(value.derive_object_id(parent)?.into()))
393 }
394
395 async fn format<'s>(
397 store: impl Store,
398 limits: Limits,
399 bytes: Vec<u8>,
400 layout: MoveTypeLayout,
401 max_depth: usize,
402 max_output_size: usize,
403 fields: impl IntoIterator<Item = (&'s str, &'s str)>,
404 ) -> Result<IndexMap<String, Result<serde_json::Value, FormatError>>, Error> {
405 let interpreter = Interpreter::new(OwnedSlice::new(layout, bytes), store);
406 Display::parse(limits, fields)?
407 .display(max_depth, max_output_size, &interpreter)
408 .await
409 }
410
411 #[tokio::test]
412 async fn test_extract_simple() {
413 let bytes = bcs::to_bytes(&(
414 AccountAddress::from_str("0x1234").unwrap(),
415 None::<bool>,
416 Some(true),
417 48u8,
418 vec![1u64, 2u64, 3u64],
419 vec![(4u32, 5u32), (6u32, 7u32), (8u32, 9u32)],
420 ))
421 .unwrap();
422
423 let layout = struct_(
424 "0x1::m::S",
425 vec![
426 ("addr", L::Address),
427 ("none", optional_(L::Bool)),
428 ("some", optional_(L::Bool)),
429 ("posn", struct_("0x1::m::P", vec![("pos0", L::U8)])),
430 ("nums", vector_(L::U64)),
431 ("kvps", vec_map(L::U32, L::U32)),
432 ],
433 );
434
435 let fields = [
436 "addr",
437 "none",
438 "some",
439 "posn.0",
440 "nums[1u64]",
441 "kvps[6u32]",
442 "i.dont.exist",
443 ];
444
445 let mut outputs = Vec::with_capacity(fields.len());
446 for field in fields {
447 outputs.push(
448 extract(MockStore::default(), bytes.clone(), layout.clone(), field)
449 .await
450 .unwrap(),
451 );
452 }
453
454 assert_json_snapshot!(outputs, @r###"
455 [
456 "0x0000000000000000000000000000000000000000000000000000000000001234",
457 null,
458 true,
459 48,
460 "2",
461 7,
462 null
463 ]
464 "###);
465 }
466
467 #[tokio::test]
468 async fn test_extract_with_dynamic_loads() {
469 let parent = AccountAddress::from_str("0x5000").unwrap();
470 let child = AccountAddress::from_str("0x5001").unwrap();
471 let bytes = bcs::to_bytes(&parent).unwrap();
472
473 let layout = struct_(
474 "0x1::m::Root",
475 vec![(
476 "parent",
477 struct_(
478 "0x1::m::Parent",
479 vec![("id", L::Struct(Box::new(UID::layout())))],
480 ),
481 )],
482 );
483
484 let store = MockStore::default()
487 .with_dynamic_field(
488 parent,
489 "df_key",
490 L::Struct(Box::new(move_utf8_str_layout())),
491 (10u64, 20u64),
492 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
493 )
494 .with_dynamic_object_field(
495 parent,
496 "dof_key",
497 L::Struct(Box::new(move_utf8_str_layout())),
498 (child, 100u64, 200u64),
499 struct_(
500 "0x1::m::Child",
501 vec![
502 ("id", L::Struct(Box::new(UID::layout()))),
503 ("x", L::U64),
504 ("y", L::U64),
505 ],
506 ),
507 );
508
509 let fields = [
510 "parent->['df_key'].x",
512 "parent->['df_key'].y",
513 "parent.id->['df_key'].x",
514 "parent=>['dof_key'].x",
516 "parent=>['dof_key'].y",
517 "parent.id=>['dof_key'].id",
518 "parent->['missing']",
520 "parent=>['missing']",
521 ];
522
523 let mut outputs = Vec::with_capacity(fields.len());
524 for field in fields {
525 outputs.push(
526 extract(store.clone(), bytes.clone(), layout.clone(), field)
527 .await
528 .unwrap(),
529 );
530 }
531
532 assert_json_snapshot!(outputs, @r###"
533 [
534 "10",
535 "20",
536 "10",
537 "100",
538 "200",
539 "0x0000000000000000000000000000000000000000000000000000000000005001",
540 null,
541 null
542 ]
543 "###);
544 }
545
546 #[tokio::test]
547 async fn test_extract_with_derived_object_loads() {
548 let parent = AccountAddress::from_str("0x5100").unwrap();
549 let child = AccountAddress::from_str("0x5101").unwrap();
550 let bytes = bcs::to_bytes(&parent).unwrap();
551
552 let layout = struct_(
553 "0x1::m::Root",
554 vec![(
555 "parent",
556 struct_(
557 "0x1::m::Parent",
558 vec![("id", L::Struct(Box::new(UID::layout())))],
559 ),
560 )],
561 );
562
563 let store = MockStore::default().with_derived_object(
564 parent,
565 "derived_key",
566 L::Struct(Box::new(move_utf8_str_layout())),
567 (child, 111u64, 222u64),
568 struct_(
569 "0x1::m::Child",
570 vec![
571 ("id", L::Struct(Box::new(UID::layout()))),
572 ("x", L::U64),
573 ("y", L::U64),
574 ],
575 ),
576 );
577
578 let fields = [
579 "parent~>['derived_key'].x",
580 "parent~>['derived_key'].y",
581 "parent.id~>['derived_key'].id",
582 "parent~>['missing']",
583 ];
584
585 let mut outputs = Vec::with_capacity(fields.len());
586 for field in fields {
587 outputs.push(
588 extract(store.clone(), bytes.clone(), layout.clone(), field)
589 .await
590 .unwrap(),
591 );
592 }
593
594 assert_json_snapshot!(outputs, @r###"
595 [
596 "111",
597 "222",
598 "0x0000000000000000000000000000000000000000000000000000000000005101",
599 null
600 ]
601 "###);
602 }
603
604 #[tokio::test]
605 async fn test_dynamic_field_names() {
606 let parent = AccountAddress::from_str("0x4242").unwrap();
607
608 let obj_bytes = bcs::to_bytes(&0u8).unwrap();
610 let obj_layout = L::U8;
611
612 let cases: Vec<(&str, &str, Vec<u8>)> = vec![
614 (
615 "'hello'",
616 "0x1::string::String",
617 bcs::to_bytes(&"hello").unwrap(),
618 ),
619 ("42u64", "u64", bcs::to_bytes(&42u64).unwrap()),
620 ("123u128", "u128", bcs::to_bytes(&123u128).unwrap()),
621 (
622 "@0xabc",
623 "address",
624 bcs::to_bytes(&AccountAddress::from_str("0xabc").unwrap()).unwrap(),
625 ),
626 (
627 "0x1::m::Key(99u32, 'test')",
628 "0x1::m::Key",
629 bcs::to_bytes(&(99u32, "test")).unwrap(),
630 ),
631 (
632 "0x1::m::Key<u32, 0x1::string::String>(99u32, 'test')",
633 "0x1::m::Key<u32, 0x1::string::String>",
634 bcs::to_bytes(&(99u32, "test")).unwrap(),
635 ),
636 (
637 "vector[1u8, 2u8, 3u8]",
638 "vector<u8>",
639 bcs::to_bytes(&vec![1u8, 2u8, 3u8]).unwrap(),
640 ),
641 ];
642
643 for (literal, type_, bytes) in cases {
644 let id = dynamic_field_id(
645 MockStore::default(),
646 obj_bytes.clone(),
647 obj_layout.clone(),
648 parent,
649 literal,
650 )
651 .await
652 .unwrap()
653 .unwrap();
654
655 let type_: TypeTag = type_.parse().unwrap();
656 let expected = derive_dynamic_field_id(parent, &type_, &bytes).unwrap();
657 assert_eq!(id, expected.into(), "mismatch for literal: {literal}");
658 }
659 }
660
661 #[tokio::test]
662 async fn test_dynamic_object_field_names() {
663 let parent = AccountAddress::from_str("0x4242").unwrap();
664
665 let obj_bytes = bcs::to_bytes(&0u8).unwrap();
667 let obj_layout = L::U8;
668
669 let cases: Vec<(&str, &str, Vec<u8>)> = vec![
671 (
672 "'hello'",
673 "0x1::string::String",
674 bcs::to_bytes(&"hello").unwrap(),
675 ),
676 ("42u64", "u64", bcs::to_bytes(&42u64).unwrap()),
677 ("123u128", "u128", bcs::to_bytes(&123u128).unwrap()),
678 (
679 "@0xabc",
680 "address",
681 bcs::to_bytes(&AccountAddress::from_str("0xabc").unwrap()).unwrap(),
682 ),
683 (
684 "0x1::m::Key(99u32, 'test')",
685 "0x1::m::Key",
686 bcs::to_bytes(&(99u32, "test")).unwrap(),
687 ),
688 (
689 "0x1::m::Key<u32, 0x1::string::String>(99u32, 'test')",
690 "0x1::m::Key<u32, 0x1::string::String>",
691 bcs::to_bytes(&(99u32, "test")).unwrap(),
692 ),
693 (
694 "vector[1u8, 2u8, 3u8]",
695 "vector<u8>",
696 bcs::to_bytes(&vec![1u8, 2u8, 3u8]).unwrap(),
697 ),
698 ];
699
700 for (literal, type_, bytes) in cases {
701 let id = dynamic_object_field_id(
702 MockStore::default(),
703 obj_bytes.clone(),
704 obj_layout.clone(),
705 parent,
706 literal,
707 )
708 .await
709 .unwrap()
710 .unwrap();
711
712 let type_: TypeTag = type_.parse().unwrap();
713 let wrapper_type = DynamicFieldInfo::dynamic_object_field_wrapper(type_);
714 let expected = derive_dynamic_field_id(parent, &wrapper_type.into(), &bytes).unwrap();
715 assert_eq!(id, expected.into(), "mismatch for literal: {literal}");
716 }
717 }
718
719 #[tokio::test]
720 async fn test_derived_object_names() {
721 let parent = AccountAddress::from_str("0x4242").unwrap();
722
723 let obj_bytes = bcs::to_bytes(&0u8).unwrap();
724 let obj_layout = L::U8;
725
726 let cases: Vec<(&str, &str, Vec<u8>)> = vec![
727 (
728 "'hello'",
729 "0x1::string::String",
730 bcs::to_bytes(&"hello").unwrap(),
731 ),
732 ("42u64", "u64", bcs::to_bytes(&42u64).unwrap()),
733 (
734 "0x1::m::Key(99u32, 'test')",
735 "0x1::m::Key",
736 bcs::to_bytes(&(99u32, "test")).unwrap(),
737 ),
738 ];
739
740 for (literal, type_, bytes) in cases {
741 let id = derived_object_id(
742 MockStore::default(),
743 obj_bytes.clone(),
744 obj_layout.clone(),
745 parent,
746 literal,
747 )
748 .await
749 .unwrap()
750 .unwrap();
751
752 let type_: TypeTag = type_.parse().unwrap();
753 let expected =
754 sui_types::derived_object::derive_object_id(parent, &type_, &bytes).unwrap();
755 assert_eq!(id, expected.into(), "mismatch for literal: {literal}");
756 }
757 }
758
759 #[test]
760 fn test_dynamic_field_name_parse_errors() {
761 let cases = [
762 "",
764 "foo",
766 "foo.bar",
767 "42",
769 "'hello",
771 "0x1::m::S(",
773 "0x1::m::S(42u64",
774 "vector[1u8, 2u8",
776 "@0xGGG",
778 ];
779
780 for literal in cases {
781 assert!(
782 Name::parse(Limits::default(), literal).is_err(),
783 "expected error for: {literal:?}"
784 );
785 }
786 }
787
788 #[tokio::test]
789 async fn test_format_fields_and_scalars() {
790 let bytes = bcs::to_bytes(&(
791 AccountAddress::from_str("0x4243").unwrap(),
792 AccountAddress::from_str("0x4445").unwrap(),
793 AccountAddress::from_str("0x4647").unwrap(),
794 true,
795 48u8,
796 49u16,
797 50u32,
798 51u64,
799 52u128,
800 U256::from(53u64),
801 "hello",
802 "world",
803 "https://example.com",
804 ))
805 .unwrap();
806
807 let fields = vec![
808 ("addr", L::Address),
809 ("id", L::Struct(Box::new(ID::layout()))),
810 ("uid", L::Struct(Box::new(UID::layout()))),
811 ("flag", L::Bool),
812 ("n8", L::U8),
813 ("n16", L::U16),
814 ("n32", L::U32),
815 ("n64", L::U64),
816 ("n128", L::U128),
817 ("n256", L::U256),
818 ("ascii", L::Struct(Box::new(move_ascii_str_layout()))),
819 ("utf8", L::Struct(Box::new(move_ascii_str_layout()))),
820 ("url", L::Struct(Box::new(url_layout()))),
821 ];
822
823 let formats = [
824 "{addr}, {id}, {uid}",
825 "{flag}",
826 "{n8}, {n16}, {n32}, {n64}, {n128}, {n256}",
827 "{ascii}, {utf8}, {url}",
828 "{ascii.bytes}, {utf8.bytes}, {url.url.bytes}",
829 "{@0x5455}",
830 "{false}",
831 "{56u8}, {57u16}, {58u32}, {59u64}, {60u128}, {61u256}",
832 "{'goodbye'}",
833 ];
834
835 let store = MockStore::default();
836 let root = OwnedSlice::new(struct_("0x1::m::S", fields), bytes);
837
838 let mut output: Vec<serde_json::Value> = Vec::with_capacity(formats.len());
839 let interpreter = Interpreter::new(root, store);
840 for s in formats {
841 let format = Format::parse(Limits::default(), s).unwrap();
842 output.push(
843 format
844 .format(&interpreter, usize::MAX, usize::MAX)
845 .await
846 .unwrap(),
847 );
848 }
849
850 assert_json_snapshot!(output, @r###"
851 [
852 "0x0000000000000000000000000000000000000000000000000000000000004243, 0x0000000000000000000000000000000000000000000000000000000000004445, 0x0000000000000000000000000000000000000000000000000000000000004647",
853 "true",
854 "48, 49, 50, 51, 52, 53",
855 "hello, world, https://example.com",
856 "hello, world, https://example.com",
857 "0x0000000000000000000000000000000000000000000000000000000000005455",
858 "false",
859 "56, 57, 58, 59, 60, 61",
860 "goodbye"
861 ]
862 "###);
863 }
864
865 #[tokio::test]
866 async fn test_display_fields_and_scalars() {
867 let bytes = bcs::to_bytes(&(
868 AccountAddress::from_str("0x4243").unwrap(),
869 AccountAddress::from_str("0x4445").unwrap(),
870 AccountAddress::from_str("0x4647").unwrap(),
871 true,
872 48u8,
873 49u16,
874 50u32,
875 51u64,
876 52u128,
877 U256::from(53u64),
878 "hello",
879 "world",
880 "https://example.com",
881 ))
882 .unwrap();
883
884 let fields = vec![
885 ("addr", L::Address),
886 ("id", L::Struct(Box::new(ID::layout()))),
887 ("uid", L::Struct(Box::new(UID::layout()))),
888 ("flag", L::Bool),
889 ("n8", L::U8),
890 ("n16", L::U16),
891 ("n32", L::U32),
892 ("n64", L::U64),
893 ("n128", L::U128),
894 ("n256", L::U256),
895 ("ascii", L::Struct(Box::new(move_ascii_str_layout()))),
896 ("utf8", L::Struct(Box::new(move_ascii_str_layout()))),
897 ("url", L::Struct(Box::new(url_layout()))),
898 ];
899
900 let formats = [
901 ("ser_ids", "{addr}, {id}, {uid}"),
902 ("ser_bool", "{flag}"),
903 ("ser_nums", "{n8}, {n16}, {n32}, {n64}, {n128}, {n256}"),
904 ("ser_strs", "{ascii}, {utf8}, {url}"),
905 ("ser_bytes", "{ascii.bytes}, {utf8.bytes}, {url.url.bytes}"),
906 ("lit_addr", "{@0x5455}"),
907 ("lit_bool", "{false}"),
908 (
909 "lit_nums",
910 "{56u8}, {57u16}, {58u32}, {59u64}, {60u128}, {61u256}",
911 ),
912 ("lit_str", "{'goodbye'}"),
913 ];
914
915 let output = format(
916 MockStore::default(),
917 Limits::default(),
918 bytes,
919 struct_("0x1::m::S", fields),
920 usize::MAX,
921 ONE_MB,
922 formats,
923 )
924 .await
925 .unwrap();
926
927 assert_debug_snapshot!(output, @r###"
928 {
929 "ser_ids": Ok(
930 String("0x0000000000000000000000000000000000000000000000000000000000004243, 0x0000000000000000000000000000000000000000000000000000000000004445, 0x0000000000000000000000000000000000000000000000000000000000004647"),
931 ),
932 "ser_bool": Ok(
933 String("true"),
934 ),
935 "ser_nums": Ok(
936 String("48, 49, 50, 51, 52, 53"),
937 ),
938 "ser_strs": Ok(
939 String("hello, world, https://example.com"),
940 ),
941 "ser_bytes": Ok(
942 String("hello, world, https://example.com"),
943 ),
944 "lit_addr": Ok(
945 String("0x0000000000000000000000000000000000000000000000000000000000005455"),
946 ),
947 "lit_bool": Ok(
948 String("false"),
949 ),
950 "lit_nums": Ok(
951 String("56, 57, 58, 59, 60, 61"),
952 ),
953 "lit_str": Ok(
954 String("goodbye"),
955 ),
956 }
957 "###);
958 }
959
960 #[tokio::test]
961 async fn test_display_vector_access() {
962 let bytes =
963 bcs::to_bytes(&(vec![2u64, 1u64, 0u64], vec!["first", "second", "third"])).unwrap();
964
965 let fields = vec![
966 ("ns", vector_(L::U64)),
967 ("ss", vector_(L::Struct(Box::new(move_ascii_str_layout())))),
968 ];
969
970 let formats = [
971 ("ns", "{{{ns[0u8]}, {ns[1u16]}, {ns[2u32]}}}"),
972 ("ss", "{{{ss[0u64]}, {ss[1u128]}, {ss[2u256]}}}"),
973 ("xs", "{{{ss[ns[0u64]]}, {ss[ns[1u64]]}, {ss[ns[2u64]]}}}"),
974 ];
975
976 let output = format(
977 MockStore::default(),
978 Limits::default(),
979 bytes,
980 struct_("0x1::m::S", fields),
981 usize::MAX,
982 ONE_MB,
983 formats,
984 )
985 .await
986 .unwrap();
987
988 assert_debug_snapshot!(output, @r###"
989 {
990 "ns": Ok(
991 String("{2, 1, 0}"),
992 ),
993 "ss": Ok(
994 String("{first, second, third}"),
995 ),
996 "xs": Ok(
997 String("{third, second, first}"),
998 ),
999 }
1000 "###);
1001 }
1002
1003 #[tokio::test]
1004 async fn test_display_enums() {
1005 #[derive(serde::Serialize)]
1006 enum Status<'s> {
1007 Pending(&'s str),
1008 Active(u32),
1009 Done(u128, u64),
1010 }
1011
1012 let layout = enum_(
1013 "0x1::m::Status",
1014 vec![
1015 (
1016 "Pending",
1017 vec![("message", L::Struct(Box::new(move_ascii_str_layout())))],
1018 ),
1019 ("Active", vec![("progress", L::U32)]),
1020 ("Done", vec![("count", L::U128), ("timestamp", L::U64)]),
1021 ],
1022 );
1023
1024 let formats = [
1025 ("pending", "message = {message}"),
1026 ("active", "progress = {progress}"),
1027 ("complete", "count = {count}, timestamp = {timestamp}"),
1028 ];
1029
1030 let mut outputs = vec![];
1031
1032 let pending = bcs::to_bytes(&Status::Pending("waiting")).unwrap();
1033 outputs.push(
1034 format(
1035 MockStore::default(),
1036 Limits::default(),
1037 pending,
1038 layout.clone(),
1039 usize::MAX,
1040 ONE_MB,
1041 formats,
1042 )
1043 .await
1044 .unwrap(),
1045 );
1046
1047 let active = bcs::to_bytes(&Status::Active(42)).unwrap();
1048 outputs.push(
1049 format(
1050 MockStore::default(),
1051 Limits::default(),
1052 active,
1053 layout.clone(),
1054 usize::MAX,
1055 ONE_MB,
1056 formats,
1057 )
1058 .await
1059 .unwrap(),
1060 );
1061
1062 let complete = bcs::to_bytes(&Status::Done(100, 999)).unwrap();
1063 outputs.push(
1064 format(
1065 MockStore::default(),
1066 Limits::default(),
1067 complete,
1068 layout,
1069 usize::MAX,
1070 ONE_MB,
1071 formats,
1072 )
1073 .await
1074 .unwrap(),
1075 );
1076
1077 assert_debug_snapshot!(outputs, @r###"
1078 [
1079 {
1080 "pending": Ok(
1081 String("message = waiting"),
1082 ),
1083 "active": Ok(
1084 Null,
1085 ),
1086 "complete": Ok(
1087 Null,
1088 ),
1089 },
1090 {
1091 "pending": Ok(
1092 Null,
1093 ),
1094 "active": Ok(
1095 String("progress = 42"),
1096 ),
1097 "complete": Ok(
1098 Null,
1099 ),
1100 },
1101 {
1102 "pending": Ok(
1103 Null,
1104 ),
1105 "active": Ok(
1106 Null,
1107 ),
1108 "complete": Ok(
1109 String("count = 100, timestamp = 999"),
1110 ),
1111 },
1112 ]
1113 "###);
1114 }
1115
1116 #[tokio::test]
1117 async fn test_display_nested_access() {
1118 let bytes = bcs::to_bytes(&(
1119 (42u64, "nested"),
1120 vec![(1u32, "first"), (2u32, "second")],
1121 vec![Some((100u64, 200u64, 300u64))],
1122 ))
1123 .unwrap();
1124
1125 let inner = struct_(
1126 "0x1::m::Inner",
1127 vec![
1128 ("value", L::U64),
1129 ("label", L::Struct(Box::new(move_ascii_str_layout()))),
1130 ],
1131 );
1132
1133 let item = struct_(
1134 "0x1::m::Item",
1135 vec![
1136 ("id", L::U32),
1137 ("name", L::Struct(Box::new(move_ascii_str_layout()))),
1138 ],
1139 );
1140
1141 let tuple = struct_(
1142 "0x1::m::Tuple",
1143 vec![("pos0", L::U64), ("pos1", L::U64), ("pos2", L::U64)],
1144 );
1145
1146 let option = enum_(
1147 "0x1::option::Option",
1148 vec![("None", vec![]), ("Some", vec![("pos0", tuple)])],
1149 );
1150
1151 let fields = vec![
1152 ("inner", inner),
1153 ("is", vector_(item)),
1154 ("ts", vector_(option)),
1155 ];
1156
1157 let formats = [
1158 ("inner", "{inner.value}/{inner.label}"),
1159 ("items", "{is[0u64].name}, {is[1u64].id}"),
1160 ("tuples", "({ts[0u64].0.0}, {ts[0u64].0.1}, {ts[0u64].0.2})"),
1161 ("litpos", "{0x2::m::S(is[1u64]).0.name}"),
1162 ("litnamed", "{0x2::m::T { id: is[0u64].id }.id}"),
1163 ];
1164
1165 let output = format(
1166 MockStore::default(),
1167 Limits::default(),
1168 bytes,
1169 struct_("0x1::m::S", fields),
1170 usize::MAX,
1171 ONE_MB,
1172 formats,
1173 )
1174 .await
1175 .unwrap();
1176
1177 assert_debug_snapshot!(output, @r###"
1178 {
1179 "inner": Ok(
1180 String("42/nested"),
1181 ),
1182 "items": Ok(
1183 String("first, 2"),
1184 ),
1185 "tuples": Ok(
1186 String("(100, 200, 300)"),
1187 ),
1188 "litpos": Ok(
1189 String("second"),
1190 ),
1191 "litnamed": Ok(
1192 String("1"),
1193 ),
1194 }
1195 "###);
1196 }
1197
1198 #[tokio::test]
1199 async fn test_display_string_bytes() {
1200 let bytes = bcs::to_bytes("ABC").unwrap();
1201 let layout = L::Struct(Box::new(move_ascii_str_layout()));
1202
1203 let formats = vec![
1204 ("serialized", "{bytes[0u64]}"),
1205 ("string_lit", "{'ABC'.bytes[1u64]}"),
1206 ("bytes_lit", "{b'ABC'[2u64]}"),
1207 ];
1208
1209 let output = format(
1210 MockStore::default(),
1211 Limits::default(),
1212 bytes,
1213 layout,
1214 usize::MAX,
1215 ONE_MB,
1216 formats,
1217 )
1218 .await
1219 .unwrap();
1220
1221 assert_debug_snapshot!(output, @r###"
1222 {
1223 "serialized": Ok(
1224 String("65"),
1225 ),
1226 "string_lit": Ok(
1227 String("66"),
1228 ),
1229 "bytes_lit": Ok(
1230 String("67"),
1231 ),
1232 }
1233 "###);
1234 }
1235
1236 #[tokio::test]
1237 async fn test_display_missing_fields() {
1238 let bytes = bcs::to_bytes(&(42u64, vec![10u64, 20u64, 30u64])).unwrap();
1239 let fields = vec![("num", L::U64), ("nums", vector_(L::U64))];
1240
1241 let formats = [
1242 ("scalar_ok", "{num}"),
1244 ("scalar_fail", "{num.field}"),
1245 ("field_fail", "{missing}"),
1247 ("index_ok", "{nums[1u64]}"),
1249 ("index_fail", "{numbers[10u64]}"),
1250 ("combined_ok", "{num}, {nums[0u64]}"),
1252 ("combined_fail", "{num}, {missing}, {nums[0u64]}"),
1254 ];
1255
1256 let output = format(
1257 MockStore::default(),
1258 Limits::default(),
1259 bytes,
1260 struct_("0x1::m::S", fields),
1261 usize::MAX,
1262 ONE_MB,
1263 formats,
1264 )
1265 .await
1266 .unwrap();
1267
1268 assert_debug_snapshot!(output, @r###"
1269 {
1270 "scalar_ok": Ok(
1271 String("42"),
1272 ),
1273 "scalar_fail": Ok(
1274 Null,
1275 ),
1276 "field_fail": Ok(
1277 Null,
1278 ),
1279 "index_ok": Ok(
1280 String("20"),
1281 ),
1282 "index_fail": Ok(
1283 Null,
1284 ),
1285 "combined_ok": Ok(
1286 String("42, 10"),
1287 ),
1288 "combined_fail": Ok(
1289 Null,
1290 ),
1291 }
1292 "###);
1293 }
1294
1295 #[tokio::test]
1296 async fn test_display_alternates() {
1297 let bytes = bcs::to_bytes(&42u64).unwrap();
1298 let layout = struct_("0x1::m::S", vec![("bar", L::U64)]);
1299
1300 let formats = [
1301 ("succeeds", "{bar | baz}"),
1302 ("eventually", "{foo | bar | baz}"),
1303 ("never", "{foo | baz | qux}"),
1304 ("fallback", "{foo | 'default'}"),
1305 ];
1306
1307 let output = format(
1308 MockStore::default(),
1309 Limits::default(),
1310 bytes,
1311 layout,
1312 usize::MAX,
1313 ONE_MB,
1314 formats,
1315 )
1316 .await
1317 .unwrap();
1318
1319 assert_debug_snapshot!(output, @r###"
1320 {
1321 "succeeds": Ok(
1322 String("42"),
1323 ),
1324 "eventually": Ok(
1325 String("42"),
1326 ),
1327 "never": Ok(
1328 Null,
1329 ),
1330 "fallback": Ok(
1331 String("default"),
1332 ),
1333 }
1334 "###);
1335 }
1336
1337 #[tokio::test]
1338 async fn test_display_alternate_optional() {
1339 let bytes = bcs::to_bytes(&(Some(100u64), None::<u64>)).unwrap();
1340 let layout = struct_(
1341 "0x1::m::S",
1342 vec![("a", optional_(L::U64)), ("b", optional_(L::U64))],
1343 );
1344
1345 let formats = [("some", "{a | 42u64}"), ("none", "{b | 43u64}")];
1346
1347 let output = format(
1348 MockStore::default(),
1349 Limits::default(),
1350 bytes,
1351 layout,
1352 usize::MAX,
1353 ONE_MB,
1354 formats,
1355 )
1356 .await
1357 .unwrap();
1358
1359 assert_debug_snapshot!(output, @r###"
1360 {
1361 "some": Ok(
1362 String("100"),
1363 ),
1364 "none": Ok(
1365 String("43"),
1366 ),
1367 }
1368 "###);
1369 }
1370
1371 #[tokio::test]
1372 async fn test_display_optional_auto_dereference() {
1373 let inner = struct_(
1374 "0x1::m::Inner",
1375 vec![("data", L::U64), ("optional_data", optional_(L::U64))],
1376 );
1377
1378 let layout = struct_(
1379 "0x1::m::Test",
1380 vec![
1381 ("some_inner", optional_(inner.clone())),
1382 ("none_inner", optional_(inner.clone())),
1383 ("partial_inner", optional_(inner)),
1384 ("some_value", optional_(L::U64)),
1385 ("none_value", optional_(L::U64)),
1386 ],
1387 );
1388
1389 let bytes = bcs::to_bytes(&(
1390 Some((100u64, Some(200u64))), None::<(u64, Option<u64>)>, Some((300u64, None::<u64>)), Some(42u64), None::<u64>, ))
1396 .unwrap();
1397
1398 let formats = [
1399 ("some_inner_data", "{some_inner.data}"),
1401 ("some_inner_optional", "{some_inner.optional_data}"),
1402 ("none_inner_data", "{none_inner.data}"),
1404 ("none_inner_optional", "{none_inner.optional_data}"),
1405 ("partial_inner_data", "{partial_inner.data}"),
1407 ("partial_inner_optional", "{partial_inner.optional_data}"),
1408 ("some_value", "{some_value}"),
1410 ("none_value", "{none_value}"),
1411 ];
1412
1413 let output = format(
1414 MockStore::default(),
1415 Limits::default(),
1416 bytes,
1417 layout,
1418 usize::MAX,
1419 ONE_MB,
1420 formats,
1421 )
1422 .await
1423 .unwrap();
1424
1425 assert_debug_snapshot!(output, @r###"
1426 {
1427 "some_inner_data": Ok(
1428 String("100"),
1429 ),
1430 "some_inner_optional": Ok(
1431 String("200"),
1432 ),
1433 "none_inner_data": Ok(
1434 Null,
1435 ),
1436 "none_inner_optional": Ok(
1437 Null,
1438 ),
1439 "partial_inner_data": Ok(
1440 String("300"),
1441 ),
1442 "partial_inner_optional": Ok(
1443 Null,
1444 ),
1445 "some_value": Ok(
1446 String("42"),
1447 ),
1448 "none_value": Ok(
1449 Null,
1450 ),
1451 }
1452 "###);
1453 }
1454
1455 #[tokio::test]
1456 async fn test_display_dynamic_fields() {
1457 let parent = AccountAddress::from_str("0x1000").unwrap();
1458 let bytes = bcs::to_bytes(&parent).unwrap();
1459 let layout = struct_(
1460 "0x1::m::Root",
1461 vec![(
1462 "parent",
1463 struct_(
1464 "0x1::m::Parent",
1465 vec![("id", L::Struct(Box::new(UID::layout())))],
1466 ),
1467 )],
1468 );
1469
1470 let store = MockStore::default().with_dynamic_field(
1472 parent,
1473 "key",
1474 L::Struct(Box::new(move_utf8_str_layout())),
1475 (42u64, 43u64),
1476 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1477 );
1478
1479 let formats = [
1480 ("via_obj", "{parent->['key'].x}"),
1481 ("via_uid", "{parent.id->['key'].y}"),
1482 ("via_id", "{parent.id.id->['key'].x}"),
1483 ("via_addr", "{parent.id.id.bytes->['key'].y}"),
1484 ("via_lit", "{@0x1000->['key'].x}"),
1485 ("missing", "{parent.id->['missing']}"),
1486 ];
1487
1488 let output = format(
1489 store,
1490 Limits::default(),
1491 bytes,
1492 layout,
1493 usize::MAX,
1494 ONE_MB,
1495 formats,
1496 )
1497 .await
1498 .unwrap();
1499
1500 assert_debug_snapshot!(output, @r###"
1501 {
1502 "via_obj": Ok(
1503 String("42"),
1504 ),
1505 "via_uid": Ok(
1506 String("43"),
1507 ),
1508 "via_id": Ok(
1509 String("42"),
1510 ),
1511 "via_addr": Ok(
1512 String("43"),
1513 ),
1514 "via_lit": Ok(
1515 String("42"),
1516 ),
1517 "missing": Ok(
1518 Null,
1519 ),
1520 }
1521 "###);
1522 }
1523
1524 #[tokio::test]
1525 async fn test_display_dynamic_field_lookup_with_self_key() {
1526 let registry = AccountAddress::from_str("0x1100").unwrap();
1527 let bytes = bcs::to_bytes(&(registry, 7u64)).unwrap();
1528 let layout = struct_(
1529 "0x1::m::Root",
1530 vec![("registry", L::Address), ("nonce", L::U64)],
1531 );
1532
1533 let store = MockStore::default().with_dynamic_field(
1534 registry,
1535 (registry, 7u64),
1536 layout.clone(),
1537 (123u64, 456u64),
1538 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1539 );
1540
1541 let formats = [
1542 ("hit", "{registry->[$self].x}"),
1543 ("miss", "{registry->[$self].z}"),
1544 ];
1545
1546 let output = format(
1547 store,
1548 Limits::default(),
1549 bytes,
1550 layout,
1551 usize::MAX,
1552 ONE_MB,
1553 formats,
1554 )
1555 .await
1556 .unwrap();
1557
1558 assert_debug_snapshot!(output, @r###"
1559 {
1560 "hit": Ok(
1561 String("123"),
1562 ),
1563 "miss": Ok(
1564 Null,
1565 ),
1566 }
1567 "###);
1568 }
1569
1570 #[tokio::test]
1571 async fn test_display_concurrent_dynamic_field_fetch() {
1572 #[derive(Clone)]
1575 struct BlockingStore {
1576 barrier: Arc<Barrier>,
1577 inner: MockStore,
1578 }
1579
1580 #[async_trait]
1581 impl Store for BlockingStore {
1582 async fn latest(
1583 &self,
1584 id: AccountAddress,
1585 ) -> anyhow::Result<Option<(MoveTypeLayout, Vec<u8>)>> {
1586 self.barrier.wait().await;
1587 self.inner.latest(id).await
1588 }
1589 }
1590
1591 let parent = AccountAddress::from_str("0x1200").unwrap();
1592 let bytes = bcs::to_bytes(&parent).unwrap();
1593 let layout = struct_(
1594 "0x1::m::Root",
1595 vec![("id", L::Struct(Box::new(UID::layout())))],
1596 );
1597
1598 let store = BlockingStore {
1599 barrier: Arc::new(Barrier::new(2)),
1600 inner: MockStore::default().with_dynamic_field(
1601 parent,
1602 "key",
1603 L::Struct(Box::new(move_utf8_str_layout())),
1604 42u64,
1605 L::U64,
1606 ),
1607 };
1608
1609 let rendered = tokio::time::timeout(
1610 Duration::from_secs(10),
1611 format(
1612 store,
1613 Limits::default(),
1614 bytes,
1615 layout,
1616 usize::MAX,
1617 ONE_MB,
1618 [("concurrent", "{id->['key']}{id->['key']}")],
1619 ),
1620 )
1621 .await
1622 .expect("back-to-back dynamic field expressions should not block")
1623 .unwrap();
1624
1625 assert_debug_snapshot!(rendered, @r###"
1626 {
1627 "concurrent": Ok(
1628 String("4242"),
1629 ),
1630 }
1631 "###);
1632 }
1633
1634 #[tokio::test]
1635 async fn test_display_dynamic_object_fields() {
1636 let parent = AccountAddress::from_str("0x2000").unwrap();
1637 let child = AccountAddress::from_str("0x2001").unwrap();
1638 let bytes = bcs::to_bytes(&parent).unwrap();
1639 let layout = struct_(
1640 "0x1::m::Root",
1641 vec![(
1642 "parent",
1643 struct_(
1644 "0x1::m::Parent",
1645 vec![("id", L::Struct(Box::new(UID::layout())))],
1646 ),
1647 )],
1648 );
1649
1650 let store = MockStore::default().with_dynamic_object_field(
1651 parent,
1652 "key",
1653 L::Struct(Box::new(move_utf8_str_layout())),
1654 (child, 100u64, 200u64),
1655 struct_(
1656 "0x1::m::Child",
1657 vec![
1658 ("id", L::Struct(Box::new(UID::layout()))),
1659 ("x", L::U64),
1660 ("y", L::U64),
1661 ],
1662 ),
1663 );
1664
1665 let formats = [
1666 ("via_obj", "{parent=>['key'].x}"),
1667 ("via_uid", "{parent.id=>['key'].y}"),
1668 ("via_id", "{parent.id.id=>['key'].x}"),
1669 ("via_addr", "{parent.id.id=>['key'].y}"),
1670 ("via_lit", "{@0x2000=>['key'].x}"),
1671 ("missing", "{parent.id=>['missing']}"),
1672 ];
1673
1674 let limits = Limits {
1675 max_loads: 20, ..Limits::default()
1677 };
1678
1679 let output = format(store, limits, bytes, layout, usize::MAX, ONE_MB, formats)
1680 .await
1681 .unwrap();
1682
1683 assert_debug_snapshot!(output, @r###"
1684 {
1685 "via_obj": Ok(
1686 String("100"),
1687 ),
1688 "via_uid": Ok(
1689 String("200"),
1690 ),
1691 "via_id": Ok(
1692 String("100"),
1693 ),
1694 "via_addr": Ok(
1695 String("200"),
1696 ),
1697 "via_lit": Ok(
1698 String("100"),
1699 ),
1700 "missing": Ok(
1701 Null,
1702 ),
1703 }
1704 "###);
1705 }
1706
1707 #[tokio::test]
1708 async fn test_display_nested_dynamic_fields() {
1709 let parent = AccountAddress::from_str("0x3000").unwrap();
1710 let child = AccountAddress::from_str("0x3001").unwrap();
1711 let bytes = bcs::to_bytes(&parent).unwrap();
1712 let layout = struct_(
1713 "0x1::m::Root",
1714 vec![(
1715 "parent",
1716 struct_(
1717 "0x1::m::Parent",
1718 vec![("id", L::Struct(Box::new(UID::layout())))],
1719 ),
1720 )],
1721 );
1722
1723 let store = MockStore::default()
1724 .with_dynamic_object_field(
1725 parent,
1726 "L1",
1727 L::Struct(Box::new(move_utf8_str_layout())),
1728 (child, 100u64),
1729 struct_(
1730 "0x1::m::Child",
1731 vec![("id", L::Struct(Box::new(UID::layout()))), ("data", L::U64)],
1732 ),
1733 )
1734 .with_dynamic_field(
1735 child,
1736 "L2",
1737 L::Struct(Box::new(move_utf8_str_layout())),
1738 (10u64, 20u64),
1739 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1740 );
1741
1742 let formats = [
1743 ("1_data", "{parent=>['L1'].data}"),
1744 ("1_2_x", "{parent=>['L1']->['L2'].x}"),
1745 ("1_2_y", "{parent=>['L1']->['L2'].y}"),
1746 ];
1747
1748 let limits = Limits {
1749 max_loads: 20,
1750 ..Limits::default()
1751 };
1752
1753 let output = format(store, limits, bytes, layout, usize::MAX, ONE_MB, formats)
1754 .await
1755 .unwrap();
1756
1757 assert_debug_snapshot!(output, @r###"
1758 {
1759 "1_data": Ok(
1760 String("100"),
1761 ),
1762 "1_2_x": Ok(
1763 String("10"),
1764 ),
1765 "1_2_y": Ok(
1766 String("20"),
1767 ),
1768 }
1769 "###);
1770 }
1771
1772 #[tokio::test]
1773 async fn test_extract_root_value_remains_scoped() {
1774 let parent = AccountAddress::from_str("0x4000").unwrap();
1775 let bytes = bcs::to_bytes(&parent).unwrap();
1776 let layout = struct_(
1777 "0x1::m::Root",
1778 vec![(
1779 "parent",
1780 struct_(
1781 "0x1::m::Parent",
1782 vec![("id", L::Struct(Box::new(UID::layout())))],
1783 ),
1784 )],
1785 );
1786
1787 let store = MockStore::default().with_dynamic_field(
1788 parent,
1789 "key",
1790 L::Struct(Box::new(move_utf8_str_layout())),
1791 (20u64, 21u64),
1792 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1793 );
1794
1795 let slice = extract_owned(store, bytes, layout, "parent.id->['key']")
1796 .await
1797 .unwrap()
1798 .unwrap();
1799
1800 assert!(slice.scoped);
1801 assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1802 }
1803
1804 #[tokio::test]
1805 async fn test_extract_literal_lookup_is_latest() {
1806 let parent = AccountAddress::from_str("0x4100").unwrap();
1807 let bytes = bcs::to_bytes(&false).unwrap();
1808 let layout = L::Bool;
1809
1810 let store = MockStore::default().with_dynamic_field(
1811 parent,
1812 "key",
1813 L::Struct(Box::new(move_utf8_str_layout())),
1814 (20u64, 21u64),
1815 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1816 );
1817
1818 let slice = extract_owned(store, bytes, layout, "@0x4100->['key']")
1819 .await
1820 .unwrap()
1821 .unwrap();
1822
1823 assert!(!slice.scoped);
1824 assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1825 }
1826
1827 #[tokio::test]
1828 async fn test_extract_root_value_via_literal_field_remains_scoped() {
1829 let parent = AccountAddress::from_str("0x4200").unwrap();
1830 let bytes = bcs::to_bytes(&parent).unwrap();
1831 let layout = struct_(
1832 "0x1::m::Root",
1833 vec![(
1834 "parent",
1835 struct_(
1836 "0x1::m::Parent",
1837 vec![("id", L::Struct(Box::new(UID::layout())))],
1838 ),
1839 )],
1840 );
1841
1842 let store = MockStore::default().with_dynamic_field(
1843 parent,
1844 "key",
1845 L::Struct(Box::new(move_utf8_str_layout())),
1846 (20u64, 21u64),
1847 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1848 );
1849
1850 let slice = extract_owned(
1851 store,
1852 bytes,
1853 layout,
1854 "0x1::m::Wrapper{p: parent.id.id}.p->['key']",
1855 )
1856 .await
1857 .unwrap()
1858 .unwrap();
1859
1860 assert!(slice.scoped);
1861 assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1862 }
1863
1864 #[tokio::test]
1865 async fn test_extract_derived_object_with_literal_parent_is_latest() {
1866 let literal_parent = AccountAddress::from_str("0x4300").unwrap();
1867 let bytes = bcs::to_bytes(&("derived_key",)).unwrap();
1868 let layout = struct_(
1869 "0x1::m::Root",
1870 vec![("key", L::Struct(Box::new(move_utf8_str_layout())))],
1871 );
1872
1873 let store = MockStore::default().with_derived_object(
1874 literal_parent,
1875 "derived_key",
1876 L::Struct(Box::new(move_utf8_str_layout())),
1877 (20u64, 21u64),
1878 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1879 );
1880
1881 let slice = extract_owned(store, bytes, layout, "@0x4300~>[key]")
1882 .await
1883 .unwrap()
1884 .unwrap();
1885
1886 assert!(!slice.scoped);
1887 assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1888 }
1889
1890 #[tokio::test]
1891 async fn test_extract_derived_object_with_root_parent_remains_scoped() {
1892 let parent = AccountAddress::from_str("0x4400").unwrap();
1893 let bytes = bcs::to_bytes(&parent).unwrap();
1894 let layout = struct_(
1895 "0x1::m::Root",
1896 vec![(
1897 "parent",
1898 struct_(
1899 "0x1::m::Parent",
1900 vec![("id", L::Struct(Box::new(UID::layout())))],
1901 ),
1902 )],
1903 );
1904
1905 let store = MockStore::default().with_derived_object(
1906 parent,
1907 "derived_key",
1908 L::Struct(Box::new(move_utf8_str_layout())),
1909 (20u64, 21u64),
1910 struct_("0x1::m::Inner", vec![("x", L::U64), ("y", L::U64)]),
1911 );
1912
1913 let slice = extract_owned(store, bytes, layout, "parent~>['derived_key']")
1914 .await
1915 .unwrap()
1916 .unwrap();
1917
1918 assert!(slice.scoped);
1919 assert_eq!(slice.bytes, bcs::to_bytes(&(20u64, 21u64)).unwrap());
1920 }
1921
1922 #[tokio::test]
1923 async fn test_display_vec_map() {
1924 let key = struct_(
1925 "0x42::m::Key",
1926 vec![
1927 ("id", L::U64),
1928 ("name", L::Struct(Box::new(move_ascii_str_layout()))),
1929 ],
1930 );
1931
1932 let val = struct_("0x42::m::Value", vec![("data", L::U32)]);
1933
1934 let bytes = bcs::to_bytes(&vec![
1936 (1u64, "first", 100u32),
1937 (2u64, "second", 200u32),
1938 (3u64, "third", 300u32),
1939 ])
1940 .unwrap();
1941
1942 let layout = struct_("0x1::m::Root", vec![("map", vec_map(key, val))]);
1943
1944 let formats = [
1945 ("1st", "{map[0x42::m::Key(1u64, 'first')].data}"),
1946 ("2nd", "{map[0x42::m::Key(2u64, 'second')].data}"),
1947 ("3rd", "{map[0x42::m::Key(3u64, 'third')].data}"),
1948 ("4th", "{map[0x42::m::Key(4u64, 'fourth')].data}"),
1950 ("err", "{map[0x42::m::Key(1u64, 'first')].data['empty']}"),
1952 ];
1953
1954 let output = format(
1955 MockStore::default(),
1956 Limits::default(),
1957 bytes,
1958 layout,
1959 usize::MAX,
1960 ONE_MB,
1961 formats,
1962 )
1963 .await
1964 .unwrap();
1965
1966 assert_debug_snapshot!(output, @r###"
1967 {
1968 "1st": Ok(
1969 String("100"),
1970 ),
1971 "2nd": Ok(
1972 String("200"),
1973 ),
1974 "3rd": Ok(
1975 String("300"),
1976 ),
1977 "4th": Ok(
1978 Null,
1979 ),
1980 "err": Ok(
1981 Null,
1982 ),
1983 }
1984 "###);
1985 }
1986
1987 #[tokio::test]
1988 async fn test_display_timestamp() {
1989 let bytes = bcs::to_bytes(&1681318800000u64).unwrap();
1990 let layout = struct_("0x1::m::S", vec![("timestamp", L::U64)]);
1991
1992 let formats = [
1993 ("epoch", "{0u64:ts}"),
1994 ("field", "{timestamp:ts}"),
1995 ("lit64", "{1683730800000u64:ts}"),
1996 ("lit128", "{1681318800000u128:ts}"),
1997 ("toobig", "{1681318800000000000u128:ts}"),
1998 ];
1999
2000 let output = format(
2001 MockStore::default(),
2002 Limits::default(),
2003 bytes,
2004 layout,
2005 usize::MAX,
2006 ONE_MB,
2007 formats,
2008 )
2009 .await
2010 .unwrap();
2011
2012 assert_debug_snapshot!(output, @r###"
2013 {
2014 "epoch": Ok(
2015 String("1970-01-01T00:00:00Z"),
2016 ),
2017 "field": Ok(
2018 String("2023-04-12T17:00:00Z"),
2019 ),
2020 "lit64": Ok(
2021 String("2023-05-10T15:00:00Z"),
2022 ),
2023 "lit128": Ok(
2024 String("2023-04-12T17:00:00Z"),
2025 ),
2026 "toobig": Err(
2027 TransformInvalid_ {
2028 offset: 0,
2029 reason: "expected unix timestamp in milliseconds",
2030 },
2031 ),
2032 }
2033 "###);
2034 }
2035
2036 #[tokio::test]
2037 async fn test_display_hex() {
2038 let bytes = bcs::to_bytes(&(
2039 0x42u8,
2040 0x4243u16,
2041 0x42434445u32,
2042 0x4243444546474849u64,
2043 0x42434445464748494a4b4c4d4e4f5051u128,
2044 U256::from_str_radix(
2045 "42434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061",
2046 16,
2047 )
2048 .unwrap(),
2049 AccountAddress::from_str(
2050 "0x41403f3e3d3c3b3a393837363534333231300f0e0d0c0b0a0908070605040302",
2051 )
2052 .unwrap(),
2053 vec![0x41u8, 0x40, 0x3a],
2054 "ABC",
2055 ))
2056 .unwrap();
2057
2058 let layout = struct_(
2059 "0x1::m::S",
2060 vec![
2061 ("n8", L::U8),
2062 ("n16", L::U16),
2063 ("n32", L::U32),
2064 ("n64", L::U64),
2065 ("n128", L::U128),
2066 ("n256", L::U256),
2067 ("addr", L::Address),
2068 ("bytes", vector_(L::U8)),
2069 ("str", L::Struct(Box::new(move_ascii_str_layout()))),
2070 ],
2071 );
2072
2073 let formats = [
2074 ("n8", "{n8:hex}"),
2075 ("n16", "{n16:hex}"),
2076 ("n32", "{n32:hex}"),
2077 ("n64", "{n64:hex}"),
2078 ("n128", "{n128:hex}"),
2079 ("n256", "{n256:hex}"),
2080 ("addr", "{addr:hex}"),
2081 ("bytes", "{bytes:hex}"),
2082 ("str", "{str:hex}"),
2083 ("str_bytes", "{str.bytes:hex}"),
2084 ];
2085
2086 let output = format(
2087 MockStore::default(),
2088 Limits::default(),
2089 bytes,
2090 layout,
2091 usize::MAX,
2092 ONE_MB,
2093 formats,
2094 )
2095 .await
2096 .unwrap();
2097
2098 assert_debug_snapshot!(output, @r###"
2099 {
2100 "n8": Ok(
2101 String("42"),
2102 ),
2103 "n16": Ok(
2104 String("4243"),
2105 ),
2106 "n32": Ok(
2107 String("42434445"),
2108 ),
2109 "n64": Ok(
2110 String("4243444546474849"),
2111 ),
2112 "n128": Ok(
2113 String("42434445464748494a4b4c4d4e4f5051"),
2114 ),
2115 "n256": Ok(
2116 String("42434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f6061"),
2117 ),
2118 "addr": Ok(
2119 String("41403f3e3d3c3b3a393837363534333231300f0e0d0c0b0a0908070605040302"),
2120 ),
2121 "bytes": Ok(
2122 String("41403a"),
2123 ),
2124 "str": Ok(
2125 String("414243"),
2126 ),
2127 "str_bytes": Ok(
2128 String("414243"),
2129 ),
2130 }
2131 "###);
2132 }
2133
2134 #[tokio::test]
2135 async fn test_display_url() {
2136 let bytes = bcs::to_bytes(&(
2137 1234u32,
2138 "hello/goodbye world",
2139 "🔥",
2140 vec![0x3eu8, 0x3f, 0x40, 0x41, 0x42, 0x43],
2141 ))
2142 .unwrap();
2143
2144 let layout = struct_(
2145 "0x1::m::S",
2146 vec![
2147 ("num", L::U32),
2148 ("str", L::Struct(Box::new(move_ascii_str_layout()))),
2149 ("emoji", L::Struct(Box::new(move_utf8_str_layout()))),
2150 ("bytes", L::Struct(Box::new(url_layout()))),
2151 ],
2152 );
2153
2154 let formats = [(
2155 "url",
2156 "https://example.com/?num={num:url}&str={str:url}&emoji={emoji:url}&data={bytes:url}",
2157 )];
2158
2159 let output = format(
2160 MockStore::default(),
2161 Limits::default(),
2162 bytes,
2163 layout,
2164 usize::MAX,
2165 ONE_MB,
2166 formats,
2167 )
2168 .await
2169 .unwrap();
2170
2171 assert_debug_snapshot!(output, @r###"
2172 {
2173 "url": Ok(
2174 String("https://example.com/?num=1234&str=hello%2Fgoodbye%20world&emoji=%F0%9F%94%A5&data=%3E%3F%40ABC"),
2175 ),
2176 }
2177 "###);
2178 }
2179
2180 #[tokio::test]
2181 async fn test_display_base64() {
2182 let bytes = bcs::to_bytes(&00u8).unwrap();
2183 let layout = struct_("0x1::m::S", vec![("dummy_field", L::Bool)]);
2184
2185 let formats = [
2186 ("byte", "{0u8:base64}"),
2187 ("byte_nopad", "{0u8:base64(nopad)}"),
2188 ("byte_url", "{0u8:base64(url)}"),
2189 ("byte_url_nopad", "{0u8:base64(url, nopad)}"),
2190 ("long", "{0xf8fbu64:base64}"),
2191 ("long_nopad", "{0xf8fbu64:base64(nopad)}"),
2192 ("long_url", "{0xf8fbu64:base64(url)}"),
2193 ("long_url_nopad", "{0xf8fbu64:base64(nopad, url)}"),
2194 ("str", "{'hello':base64}"),
2195 ("str_nopad", "{'hello':base64(nopad)}"),
2196 ("str_url", "{'hello':base64(url)}"),
2197 ("str_url_nopad", "{'hello':base64(url, nopad)}"),
2198 (
2199 "flatland",
2200 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(url)}",
2201 ),
2202 (
2203 "flatland_nopad",
2204 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(nopad)}",
2205 ),
2206 (
2207 "flatland_url",
2208 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(url)}",
2209 ),
2210 (
2211 "flatland_url_nopad",
2212 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:base64(url, nopad)}",
2213 ),
2214 ];
2215
2216 let output = format(
2217 MockStore::default(),
2218 Limits::default(),
2219 bytes,
2220 layout,
2221 usize::MAX,
2222 ONE_MB,
2223 formats,
2224 )
2225 .await
2226 .unwrap();
2227
2228 assert_debug_snapshot!(output, @r###"
2229 {
2230 "byte": Ok(
2231 String("AA=="),
2232 ),
2233 "byte_nopad": Ok(
2234 String("AA"),
2235 ),
2236 "byte_url": Ok(
2237 String("AA=="),
2238 ),
2239 "byte_url_nopad": Ok(
2240 String("AA"),
2241 ),
2242 "long": Ok(
2243 String("+/gAAAAAAAA="),
2244 ),
2245 "long_nopad": Ok(
2246 String("+/gAAAAAAAA"),
2247 ),
2248 "long_url": Ok(
2249 String("-_gAAAAAAAA="),
2250 ),
2251 "long_url_nopad": Ok(
2252 String("-_gAAAAAAAA"),
2253 ),
2254 "str": Ok(
2255 String("aGVsbG8="),
2256 ),
2257 "str_nopad": Ok(
2258 String("aGVsbG8"),
2259 ),
2260 "str_url": Ok(
2261 String("aGVsbG8="),
2262 ),
2263 "str_url_nopad": Ok(
2264 String("aGVsbG8"),
2265 ),
2266 "flatland": Ok(
2267 String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2268 ),
2269 "flatland_nopad": Ok(
2270 String("0tGFaqPKhfWCrycZHVcT6lgF7C+YIrMMzORXFwcsGmE"),
2271 ),
2272 "flatland_url": Ok(
2273 String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2274 ),
2275 "flatland_url_nopad": Ok(
2276 String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE"),
2277 ),
2278 }
2279 "###);
2280 }
2281
2282 #[tokio::test]
2283 async fn test_display_bcs() {
2284 let bytes = bcs::to_bytes(&(
2285 0x42u8,
2286 0x1234u16,
2287 0x12345678u32,
2288 0x123456789abcdef0u64,
2289 "hello",
2290 vec![1u8, 2, 3],
2291 ))
2292 .unwrap();
2293
2294 let layout = struct_(
2295 "0x1::m::S",
2296 vec![
2297 ("n8", L::U8),
2298 ("n16", L::U16),
2299 ("n32", L::U32),
2300 ("n64", L::U64),
2301 ("str", L::Struct(Box::new(move_utf8_str_layout()))),
2302 ("bytes", vector_(L::U8)),
2303 ],
2304 );
2305
2306 let formats = [
2307 ("s8", "{n8:bcs}"),
2308 ("l8", "{0x43u8:bcs}"),
2309 ("s16", "{n16:bcs}"),
2310 ("l16", "{0x1235u16:bcs}"),
2311 ("s32", "{n32:bcs}"),
2312 ("l32", "{0x12345679u32:bcs}"),
2313 ("s64", "{n64:bcs}"),
2314 ("l64", "{0x123456789abcdef1u64:bcs}"),
2315 ("sstr", "{str:bcs}"),
2316 ("lstr", "{'goodbye':bcs}"),
2317 ("sbytes", "{bytes:bcs}"),
2318 ("lbytes", "{x'010204':bcs}"),
2319 ("hbytes", "{vector[0x41u8, n8, 0x43u8]:bcs}"),
2320 ("lstruct", "{0x1::m::S(n8, n16):bcs}"),
2321 ("lempty", "{0x1::m::Empty():bcs}"),
2322 ("lnone", "{0x1::option::Option<u8>::None#0():bcs}"),
2323 ("lsome", "{0x1::option::Option<u8>::Some#1(0x44u8):bcs}"),
2324 ];
2325
2326 let output = format(
2327 MockStore::default(),
2328 Limits::default(),
2329 bytes,
2330 layout,
2331 usize::MAX,
2332 ONE_MB,
2333 formats,
2334 )
2335 .await
2336 .unwrap();
2337
2338 let actual = |f: &str| output.get(f).unwrap().as_ref().unwrap().as_str().unwrap();
2339 fn expect(x: impl Serialize) -> String {
2340 STANDARD.encode(bcs::to_bytes(&x).unwrap())
2341 }
2342
2343 assert_eq!(actual("s8"), expect(0x42u8));
2344 assert_eq!(actual("l8"), expect(0x43u8));
2345 assert_eq!(actual("s16"), expect(0x1234u16));
2346 assert_eq!(actual("l16"), expect(0x1235u16));
2347 assert_eq!(actual("s32"), expect(0x12345678u32));
2348 assert_eq!(actual("l32"), expect(0x12345679u32));
2349 assert_eq!(actual("s64"), expect(0x123456789abcdef0u64));
2350 assert_eq!(actual("l64"), expect(0x123456789abcdef1u64));
2351 assert_eq!(actual("sstr"), expect("hello"));
2352 assert_eq!(actual("lstr"), expect("goodbye"));
2353 assert_eq!(actual("sbytes"), expect(vec![1u8, 2, 3]));
2354 assert_eq!(actual("lbytes"), expect(vec![1u8, 2, 4]));
2355 assert_eq!(actual("hbytes"), expect(vec![0x41u8, 0x42, 0x43]));
2356 assert_eq!(actual("lstruct"), expect((0x42u8, 0x1234u16)));
2357 assert_eq!(actual("lempty"), expect(false));
2358 assert_eq!(actual("lnone"), expect(None::<u8>));
2359 assert_eq!(actual("lsome"), expect(Some(0x44u8)));
2360 }
2361
2362 #[tokio::test]
2363 async fn test_display_bcs_modifiers() {
2364 let bytes = bcs::to_bytes(&00u8).unwrap();
2365 let layout = struct_("0x1::m::S", vec![("dummy_field", L::Bool)]);
2366
2367 let formats = [
2368 ("byte", "{0u8:bcs}"),
2369 ("byte_nopad", "{0u8:bcs(nopad)}"),
2370 ("byte_url", "{0u8:bcs(url)}"),
2371 ("byte_url_nopad", "{0u8:bcs(url, nopad)}"),
2372 ("long", "{0xf8fbu64:bcs}"),
2373 ("long_nopad", "{0xf8fbu64:bcs(nopad)}"),
2374 ("long_url", "{0xf8fbu64:bcs(url)}"),
2375 ("long_url_nopad", "{0xf8fbu64:bcs(nopad, url)}"),
2376 ("str", "{'hello':bcs}"),
2377 ("str_nopad", "{'hello':bcs(nopad)}"),
2378 ("str_url", "{'hello':bcs(url)}"),
2379 ("str_url_nopad", "{'hello':bcs(url, nopad)}"),
2380 (
2381 "flatland",
2382 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(url)}",
2383 ),
2384 (
2385 "flatland_nopad",
2386 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(nopad)}",
2387 ),
2388 (
2389 "flatland_url",
2390 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(url)}",
2391 ),
2392 (
2393 "flatland_url_nopad",
2394 "{43920588204278303214855528440570972873796977361529388163322669436471087583698u256:bcs(url, nopad)}",
2395 ),
2396 ];
2397
2398 let output = format(
2399 MockStore::default(),
2400 Limits::default(),
2401 bytes,
2402 layout,
2403 usize::MAX,
2404 ONE_MB,
2405 formats,
2406 )
2407 .await
2408 .unwrap();
2409
2410 assert_debug_snapshot!(output, @r###"
2411 {
2412 "byte": Ok(
2413 String("AA=="),
2414 ),
2415 "byte_nopad": Ok(
2416 String("AA"),
2417 ),
2418 "byte_url": Ok(
2419 String("AA=="),
2420 ),
2421 "byte_url_nopad": Ok(
2422 String("AA"),
2423 ),
2424 "long": Ok(
2425 String("+/gAAAAAAAA="),
2426 ),
2427 "long_nopad": Ok(
2428 String("+/gAAAAAAAA"),
2429 ),
2430 "long_url": Ok(
2431 String("-_gAAAAAAAA="),
2432 ),
2433 "long_url_nopad": Ok(
2434 String("-_gAAAAAAAA"),
2435 ),
2436 "str": Ok(
2437 String("BWhlbGxv"),
2438 ),
2439 "str_nopad": Ok(
2440 String("BWhlbGxv"),
2441 ),
2442 "str_url": Ok(
2443 String("BWhlbGxv"),
2444 ),
2445 "str_url_nopad": Ok(
2446 String("BWhlbGxv"),
2447 ),
2448 "flatland": Ok(
2449 String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2450 ),
2451 "flatland_nopad": Ok(
2452 String("0tGFaqPKhfWCrycZHVcT6lgF7C+YIrMMzORXFwcsGmE"),
2453 ),
2454 "flatland_url": Ok(
2455 String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE="),
2456 ),
2457 "flatland_url_nopad": Ok(
2458 String("0tGFaqPKhfWCrycZHVcT6lgF7C-YIrMMzORXFwcsGmE"),
2459 ),
2460 }
2461 "###);
2462 }
2463
2464 #[tokio::test]
2465 async fn test_display_json() {
2466 let bytes = bcs::to_bytes(&(
2467 12u8,
2468 1234u16,
2469 12345678u32,
2470 123456781234567890u64,
2471 "hello",
2472 vec![1u8, 2, 3],
2473 None::<u8>,
2474 Some(vec![4u32, 5u32, 6u32]),
2475 (1u8, 5678u16),
2476 (9u32, 10u64),
2477 ))
2478 .unwrap();
2479
2480 let layout = struct_(
2481 "0x1::m::S",
2482 vec![
2483 ("n8", L::U8),
2484 ("n16", L::U16),
2485 ("n32", L::U32),
2486 ("n64", L::U64),
2487 ("str", L::Struct(Box::new(move_utf8_str_layout()))),
2488 ("bytes", vector_(L::U8)),
2489 (
2490 "none",
2491 struct_("0x1::option::Option<u8>", vec![("vec", vector_(L::U8))]),
2492 ),
2493 (
2494 "some",
2495 struct_(
2496 "0x1::option::Option<vector<u32>>",
2497 vec![("vec", vector_(vector_(L::U32)))],
2498 ),
2499 ),
2500 (
2501 "variant",
2502 enum_(
2503 "0x1::m::E",
2504 vec![("A", vec![("x", L::U8)]), ("B", vec![("y", L::U16)])],
2505 ),
2506 ),
2507 (
2508 "nested",
2509 struct_("0x1::m::N", vec![("a", L::U32), ("b", L::U64)]),
2510 ),
2511 ],
2512 );
2513
2514 let formats = [
2515 ("s8", "{n8:json}"),
2516 ("l8", "{34u8:json}"),
2517 ("s16", "{n16:json}"),
2518 ("l16", "{5678u16:json}"),
2519 ("s32", "{n32:json}"),
2520 ("l32", "{87654321u32:json}"),
2521 ("s64", "{n64:json}"),
2522 ("l64", "{9876543210987654321u64:json}"),
2523 ("sstr", "{str:json}"),
2524 ("lstr", "{'goodbye':json}"),
2525 ("sbytes", "{bytes:json}"),
2526 ("lbytes", "{x'040506':json}"),
2527 ("vbytes", "{vector[0x01u8, 0x02u8, 0x03u8]:json}"),
2528 ("snone", "{none:json}"),
2529 ("ssome", "{some:json}"),
2530 ("lvec", "{vector[7u64, 8u64, 9u64]:json}"),
2531 ("svariant", "{variant:json}"),
2532 ("lvariant", "{0x1::m::E::A#0(90u8):json}"),
2533 ("snested", "{nested:json}"),
2534 ("lstruct", "{0x1::m::S { c: n8, d: n16 }:json}"),
2535 ("lempty", "{0x1::m::Empty():json}"),
2536 ];
2537
2538 let output = format(
2539 MockStore::default(),
2540 Limits::default(),
2541 bytes,
2542 layout,
2543 usize::MAX,
2544 ONE_MB,
2545 formats,
2546 )
2547 .await
2548 .unwrap();
2549
2550 assert_debug_snapshot!(output, @r###"
2551 {
2552 "s8": Ok(
2553 Number(12),
2554 ),
2555 "l8": Ok(
2556 Number(34),
2557 ),
2558 "s16": Ok(
2559 Number(1234),
2560 ),
2561 "l16": Ok(
2562 Number(5678),
2563 ),
2564 "s32": Ok(
2565 Number(12345678),
2566 ),
2567 "l32": Ok(
2568 Number(87654321),
2569 ),
2570 "s64": Ok(
2571 String("123456781234567890"),
2572 ),
2573 "l64": Ok(
2574 String("9876543210987654321"),
2575 ),
2576 "sstr": Ok(
2577 String("hello"),
2578 ),
2579 "lstr": Ok(
2580 String("goodbye"),
2581 ),
2582 "sbytes": Ok(
2583 String("AQID"),
2584 ),
2585 "lbytes": Ok(
2586 String("BAUG"),
2587 ),
2588 "vbytes": Ok(
2589 Array [
2590 Number(1),
2591 Number(2),
2592 Number(3),
2593 ],
2594 ),
2595 "snone": Ok(
2596 Null,
2597 ),
2598 "ssome": Ok(
2599 Array [
2600 Number(4),
2601 Number(5),
2602 Number(6),
2603 ],
2604 ),
2605 "lvec": Ok(
2606 Array [
2607 String("7"),
2608 String("8"),
2609 String("9"),
2610 ],
2611 ),
2612 "svariant": Ok(
2613 Object {
2614 "@variant": String("B"),
2615 "y": Number(5678),
2616 },
2617 ),
2618 "lvariant": Ok(
2619 Object {
2620 "@variant": String("A"),
2621 "pos0": Number(90),
2622 },
2623 ),
2624 "snested": Ok(
2625 Object {
2626 "a": Number(9),
2627 "b": String("10"),
2628 },
2629 ),
2630 "lstruct": Ok(
2631 Object {
2632 "c": Number(12),
2633 "d": Number(1234),
2634 },
2635 ),
2636 "lempty": Ok(
2637 Object {},
2638 ),
2639 }
2640 "###);
2641 }
2642
2643 #[tokio::test]
2644 async fn test_display_string_hardening() {
2645 let bytes = bcs::to_bytes(&("ascii", "🔥", vec![0xC3u8])).unwrap();
2646 let layout = struct_(
2647 "0x1::m::S",
2648 vec![
2649 ("ascii", L::Struct(Box::new(move_utf8_str_layout()))),
2650 ("utf8", L::Struct(Box::new(move_utf8_str_layout()))),
2651 ("invalid", L::Struct(Box::new(move_utf8_str_layout()))),
2652 ],
2653 );
2654
2655 let formats = [
2656 ("ascii", "{ascii}"),
2657 ("utf8", "{utf8}"),
2658 ("invalid", "{invalid}"),
2659 ];
2660
2661 let output = format(
2662 MockStore::default(),
2663 Limits::default(),
2664 bytes,
2665 layout,
2666 usize::MAX,
2667 ONE_MB,
2668 formats,
2669 )
2670 .await
2671 .unwrap();
2672
2673 assert_debug_snapshot!(output, @r###"
2674 {
2675 "ascii": Ok(
2676 String("ascii"),
2677 ),
2678 "utf8": Ok(
2679 String("🔥"),
2680 ),
2681 "invalid": Err(
2682 TransformInvalid_ {
2683 offset: 0,
2684 reason: "expected utf8 bytes",
2685 },
2686 ),
2687 }
2688 "###);
2689 }
2690
2691 #[tokio::test]
2692 async fn test_format_single_bare_expression_falls_back_to_json() {
2693 #[derive(serde::Serialize)]
2694 enum Status<'s> {
2695 Pending(&'s str),
2696 }
2697
2698 let bytes = bcs::to_bytes(&(
2699 (42u64, "hello"),
2700 Status::Pending("ready"),
2701 vec![1u64, 2u64, 3u64],
2702 ))
2703 .unwrap();
2704
2705 let layout = struct_(
2706 "0x1::m::S",
2707 vec![
2708 (
2709 "st",
2710 struct_(
2711 "0x1::m::Inner",
2712 vec![
2713 ("count", L::U64),
2714 ("label", L::Struct(Box::new(move_ascii_str_layout()))),
2715 ],
2716 ),
2717 ),
2718 (
2719 "en",
2720 enum_(
2721 "0x1::m::Status",
2722 vec![(
2723 "Pending",
2724 vec![("message", L::Struct(Box::new(move_ascii_str_layout())))],
2725 )],
2726 ),
2727 ),
2728 ("vs", vector_(L::U64)),
2729 ],
2730 );
2731
2732 let store = MockStore::default();
2733 let root = OwnedSlice::new(layout, bytes);
2734 let interpreter = Interpreter::new(root, store);
2735
2736 let formats = ["{st}", "{en}", "{vs}"];
2737 let mut output = Vec::with_capacity(formats.len());
2738 for s in formats {
2739 let format = Format::parse(Limits::default(), s).unwrap();
2740 output.push(
2741 format
2742 .format::<serde_json::Value>(&interpreter, usize::MAX, usize::MAX)
2743 .await
2744 .unwrap(),
2745 );
2746 }
2747
2748 assert_json_snapshot!(output, @r###"
2749 [
2750 {
2751 "count": "42",
2752 "label": "hello"
2753 },
2754 {
2755 "@variant": "Pending",
2756 "message": "ready"
2757 },
2758 [
2759 "1",
2760 "2",
2761 "3"
2762 ]
2763 ]
2764 "###);
2765 }
2766
2767 #[tokio::test]
2768 async fn test_display_field_errors() {
2769 let bytes = bcs::to_bytes(&0u8).unwrap();
2770 let layout = struct_("0x1::m::S", vec![("byte", L::U8)]);
2771
2772 let formats = [
2773 ("parsing_error", "{42"),
2774 ("bad_transform", "{byte:invalid}"),
2775 ("too_deep", "{a[b[c[d[e[f]]]]]}"),
2776 ];
2777
2778 let limits = Limits {
2779 max_depth: 5,
2780 ..Limits::default()
2781 };
2782
2783 let output = format(
2784 MockStore::default(),
2785 limits,
2786 bytes,
2787 layout,
2788 usize::MAX,
2789 ONE_MB,
2790 formats,
2791 )
2792 .await
2793 .unwrap();
2794
2795 assert_debug_snapshot!(output, @r###"
2796 {
2797 "parsing_error": Err(
2798 UnexpectedEos {
2799 expect: ExpectedSet {
2800 prev: [],
2801 tried: [
2802 Literal(
2803 "u8",
2804 ),
2805 Literal(
2806 "u16",
2807 ),
2808 Literal(
2809 "u32",
2810 ),
2811 Literal(
2812 "u64",
2813 ),
2814 Literal(
2815 "u128",
2816 ),
2817 Literal(
2818 "u256",
2819 ),
2820 ],
2821 },
2822 },
2823 ),
2824 "bad_transform": Err(
2825 UnexpectedToken {
2826 actual: OwnedLexeme(
2827 false,
2828 Ident,
2829 6,
2830 "invalid",
2831 ),
2832 expect: ExpectedSet {
2833 prev: [],
2834 tried: [
2835 Literal(
2836 "base64",
2837 ),
2838 Literal(
2839 "bcs",
2840 ),
2841 Literal(
2842 "hex",
2843 ),
2844 Literal(
2845 "json",
2846 ),
2847 Literal(
2848 "str",
2849 ),
2850 Literal(
2851 "ts",
2852 ),
2853 Literal(
2854 "url",
2855 ),
2856 ],
2857 },
2858 },
2859 ),
2860 "too_deep": Err(
2861 TooDeep,
2862 ),
2863 }
2864 "###);
2865 }
2866
2867 #[tokio::test]
2868 async fn test_display_vector_literal_type_mismatch() {
2869 let bytes = bcs::to_bytes(&0u8).unwrap();
2870 let layout = struct_("0x1::m::S", vec![("byte", L::U8)]);
2871
2872 let formats = [
2873 ("between_literals", "{vector[42u8, 42u64]:bcs}"),
2874 ("between_field_and_literal", "{vector[42u64, byte]:bcs}"),
2875 ("between_annotation_and_element", "{vector<u64>[byte]:bcs}"),
2876 ];
2877
2878 let output = format(
2879 MockStore::default(),
2880 Limits::default(),
2881 bytes,
2882 layout,
2883 usize::MAX,
2884 ONE_MB,
2885 formats,
2886 )
2887 .await
2888 .unwrap();
2889
2890 assert_debug_snapshot!(output, @r###"
2891 {
2892 "between_literals": Err(
2893 VectorTypeMismatch {
2894 offset: 1,
2895 this: U8,
2896 that: U64,
2897 },
2898 ),
2899 "between_field_and_literal": Err(
2900 VectorTypeMismatch {
2901 offset: 1,
2902 this: U64,
2903 that: U8,
2904 },
2905 ),
2906 "between_annotation_and_element": Err(
2907 VectorTypeMismatch {
2908 offset: 1,
2909 this: U64,
2910 that: U8,
2911 },
2912 ),
2913 }
2914 "###);
2915 }
2916
2917 #[tokio::test]
2918 async fn test_display_output_node_limits() {
2919 let bytes = bcs::to_bytes(&42u64).unwrap();
2920
2921 let limits = Limits {
2922 max_nodes: 10,
2923 ..Limits::default()
2924 };
2925
2926 let big_field = [("f", "{a | b | c | d | e | f | g | h | i | j}")];
2928 let two_fields = [("f", "{a | b | c | d | e}"), ("g", "{f | g | h | i | j}")];
2929
2930 let res = format(
2931 MockStore::default(),
2932 limits.clone(),
2933 bytes.clone(),
2934 L::U64,
2935 usize::MAX,
2936 ONE_MB,
2937 big_field,
2938 )
2939 .await;
2940 assert!(matches!(res, Err(Error::TooBig)));
2941
2942 let res = format(
2943 MockStore::default(),
2944 limits,
2945 bytes,
2946 L::U64,
2947 usize::MAX,
2948 ONE_MB,
2949 two_fields,
2950 )
2951 .await;
2952 assert!(matches!(res, Err(Error::TooBig)));
2953 }
2954
2955 #[tokio::test]
2956 async fn test_display_output_size_limits() {
2957 let bytes = bcs::to_bytes(&42u64).unwrap();
2958 let formats = [("x", "012345"), ("y", "67890"), ("z", "ABCDE")];
2959
2960 let res = format(
2961 MockStore::default(),
2962 Limits::default(),
2963 bytes,
2964 L::U64,
2965 usize::MAX,
2966 10,
2967 formats,
2968 )
2969 .await;
2970 assert!(matches!(res, Err(Error::TooMuchOutput)));
2971 }
2972
2973 #[tokio::test]
2974 async fn test_display_move_value_depth_limit() {
2975 let bytes = bcs::to_bytes(&42u64).unwrap();
2976
2977 let formats = [
2978 ("leaf", "{42u64:json}"),
2979 ("shallow", "{0x1::m::S(43u128):json}"),
2980 (
2981 "deep",
2982 "{0x1::m::S(vector[vector[0x1::m::T(44u256)]]):json}",
2983 ),
2984 ];
2985
2986 let output = format(
2987 MockStore::default(),
2988 Limits::default(),
2989 bytes,
2990 L::U64,
2991 3,
2992 ONE_MB,
2993 formats,
2994 )
2995 .await
2996 .unwrap();
2997
2998 assert_debug_snapshot!(output, @r###"
2999 {
3000 "leaf": Ok(
3001 String("42"),
3002 ),
3003 "shallow": Ok(
3004 Object {
3005 "pos0": String("43"),
3006 },
3007 ),
3008 "deep": Err(
3009 TooDeep,
3010 ),
3011 }
3012 "###);
3013 }
3014
3015 #[tokio::test]
3016 async fn test_display_too_many_loads() {
3017 let bytes = bcs::to_bytes(&42u64).unwrap();
3018
3019 let limits = Limits {
3020 max_loads: 3,
3021 ..Limits::default()
3022 };
3023
3024 let big_field = [("f", "{a->[b]->[c]->[d]->[e]}")];
3027 let two_fields = [("f1", "{a->[b]}"), ("f2", "{c->[d]}"), ("f3", "{e=>[f]}")];
3028
3029 let res = format(
3030 MockStore::default(),
3031 limits.clone(),
3032 bytes.clone(),
3033 L::U64,
3034 usize::MAX,
3035 ONE_MB,
3036 big_field,
3037 )
3038 .await;
3039 assert!(matches!(res, Err(Error::TooManyLoads)));
3040
3041 let res = format(
3042 MockStore::default(),
3043 limits,
3044 bytes,
3045 L::U64,
3046 usize::MAX,
3047 ONE_MB,
3048 two_fields,
3049 )
3050 .await;
3051 assert!(matches!(res, Err(Error::TooManyLoads)));
3052 }
3053
3054 #[tokio::test]
3055 async fn test_display_name_empty() {
3056 let bytes = bcs::to_bytes(&42u64).unwrap();
3057
3058 let formats = [("name {missing}", "value")];
3060 let res = format(
3061 MockStore::default(),
3062 Limits::default(),
3063 bytes,
3064 L::U64,
3065 usize::MAX,
3066 ONE_MB,
3067 formats,
3068 )
3069 .await;
3070 assert!(matches!(res, Err(Error::NameEmpty(_))), "{res:?}");
3071 }
3072
3073 #[tokio::test]
3074 async fn test_display_duplicate_name() {
3075 let layout = struct_("0x1::m::S", vec![("a", L::U64), ("b", L::U64)]);
3076
3077 let formats = [("field", "value1"), ("field", "value2")];
3079 let bytes = bcs::to_bytes(&(42u64, 43u64)).unwrap();
3080 let res = format(
3081 MockStore::default(),
3082 Limits::default(),
3083 bytes,
3084 layout.clone(),
3085 usize::MAX,
3086 ONE_MB,
3087 formats,
3088 )
3089 .await;
3090 assert!(matches!(res, Err(Error::NameDuplicate(_))));
3091
3092 let formats = [("{a}", "value1"), ("{b}", "value2")];
3094 let bytes = bcs::to_bytes(&(42u64, 42u64)).unwrap();
3095 let res = format(
3096 MockStore::default(),
3097 Limits::default(),
3098 bytes,
3099 layout.clone(),
3100 usize::MAX,
3101 ONE_MB,
3102 formats,
3103 )
3104 .await;
3105 assert!(matches!(res, Err(Error::NameDuplicate(_))));
3106
3107 let formats = [("f42", "value1"), ("f{a}", "value2")];
3109 let bytes = bcs::to_bytes(&(42u64, 43u64)).unwrap();
3110 let res = format(
3111 MockStore::default(),
3112 Limits::default(),
3113 bytes,
3114 layout.clone(),
3115 usize::MAX,
3116 ONE_MB,
3117 formats,
3118 )
3119 .await;
3120 assert!(matches!(res, Err(Error::NameDuplicate(_))));
3121 }
3122}