iota_graphql_rpc/types/
move_value.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use async_graphql::*;
6use iota_types::{
7    base_types::{StructTag, TypeTag},
8    iota_sdk_types_conversions::{struct_tag_core_to_sdk, type_tag_core_to_sdk},
9    object::bounded_visitor::BoundedVisitor,
10};
11use move_core_types::{
12    account_address::AccountAddress,
13    annotated_value as A, ident_str,
14    identifier::{IdentStr, Identifier},
15};
16use serde::{Deserialize, Serialize};
17
18use crate::{
19    data::package_resolver::PackageResolver,
20    error::Error,
21    types::{
22        base64::Base64,
23        big_int::BigInt,
24        iota_address::IotaAddress,
25        json::Json,
26        move_type::{MoveType, unexpected_signer_error},
27    },
28};
29
30const STD: AccountAddress = AccountAddress::ONE;
31const IOTA: AccountAddress = AccountAddress::TWO;
32
33const MOD_ASCII: &IdentStr = ident_str!("ascii");
34const MOD_OBJECT: &IdentStr = ident_str!("object");
35const MOD_OPTION: &IdentStr = ident_str!("option");
36const MOD_STRING: &IdentStr = ident_str!("string");
37
38const TYPE_ID: &IdentStr = ident_str!("ID");
39const TYPE_OPTION: &IdentStr = ident_str!("Option");
40const TYPE_STRING: &IdentStr = ident_str!("String");
41const TYPE_UID: &IdentStr = ident_str!("UID");
42
43#[derive(SimpleObject)]
44#[graphql(complex)]
45pub(crate) struct MoveValue {
46    /// The value's Move type.
47    #[graphql(name = "type", complexity = 0)]
48    type_: MoveType,
49    /// The BCS representation of this value, Base64 encoded.
50    #[graphql(complexity = 0)]
51    bcs: Base64,
52}
53
54scalar!(
55    MoveData,
56    "MoveData",
57    "The contents of a Move Value, corresponding to the following recursive type:
58
59type MoveData =
60    { Address: IotaAddress }
61  | { UID:     IotaAddress }
62  | { ID:      IotaAddress }
63  | { Bool:    bool }
64  | { Number:  BigInt }
65  | { String:  string }
66  | { Vector:  [MoveData] }
67  | { Option:   MoveData? }
68  | { Struct:  [{ name: string , value: MoveData }] }
69  | { Variant: {
70      name: string,
71      fields: [{ name: string, value: MoveData }],
72  }"
73);
74
75#[derive(Serialize, Deserialize, Debug, Clone)]
76pub(crate) enum MoveData {
77    Address(IotaAddress),
78    #[serde(rename = "UID")]
79    Uid(IotaAddress),
80    #[serde(rename = "ID")]
81    Id(IotaAddress),
82    Bool(bool),
83    Number(BigInt),
84    String(String),
85    Vector(Vec<MoveData>),
86    Option(Option<Box<MoveData>>),
87    Struct(Vec<MoveField>),
88    Variant(MoveVariant),
89}
90
91#[derive(Serialize, Deserialize, Debug, Clone)]
92pub(crate) struct MoveVariant {
93    name: String,
94    fields: Vec<MoveField>,
95}
96
97#[derive(Serialize, Deserialize, Debug, Clone)]
98pub(crate) struct MoveField {
99    name: String,
100    value: MoveData,
101}
102
103/// An instance of a Move type.
104#[ComplexObject]
105impl MoveValue {
106    /// Structured contents of a Move value.
107    #[graphql(complexity = 0)]
108    async fn data(&self, ctx: &Context<'_>) -> Result<MoveData> {
109        let resolver: &PackageResolver = ctx
110            .data()
111            .map_err(|_| Error::Internal("Unable to fetch Package Cache.".to_string()))
112            .extend()?;
113
114        let Some(layout) = self.type_.layout_impl(resolver).await.extend()? else {
115            return Err(Error::Internal(
116                "Move value must have valid layout".to_string(),
117            ))
118            .extend();
119        };
120
121        // Factor out into its own non-GraphQL, non-async function for better
122        // testability
123        self.data_impl(layout).extend()
124    }
125
126    /// Representation of a Move value in JSON, where:
127    ///
128    /// - Addresses, IDs, and UIDs are represented in canonical form, as JSON
129    ///   strings.
130    /// - Bools are represented by JSON boolean literals.
131    /// - u8, u16, and u32 are represented as JSON numbers.
132    /// - u64, u128, and u256 are represented as JSON strings.
133    /// - Vectors are represented by JSON arrays.
134    /// - Structs are represented by JSON objects.
135    /// - Empty optional values are represented by `null`.
136    ///
137    /// This form is offered as a less verbose convenience in cases where the
138    /// layout of the type is known by the client.
139    #[graphql(complexity = 0)]
140    async fn json(&self, ctx: &Context<'_>) -> Result<Json> {
141        let resolver: &PackageResolver = ctx
142            .data()
143            .map_err(|_| Error::Internal("Unable to fetch Package Cache.".to_string()))
144            .extend()?;
145
146        let Some(layout) = self.type_.layout_impl(resolver).await.extend()? else {
147            return Err(Error::Internal(
148                "Move value must have valid layout".to_string(),
149            ))
150            .extend();
151        };
152
153        // Factor out into its own non-GraphQL, non-async function for better
154        // testability
155        self.json_impl(layout).extend()
156    }
157}
158
159impl MoveValue {
160    pub fn new(tag: TypeTag, bcs: Base64) -> Self {
161        let type_ = MoveType::from(tag);
162        Self { type_, bcs }
163    }
164
165    fn value_impl(&self, layout: A::MoveTypeLayout) -> Result<A::MoveValue, Error> {
166        // TODO (annotated-visitor): deserializing directly using a custom visitor.
167        BoundedVisitor::deserialize_value(&self.bcs.0[..], &layout).map_err(|_| {
168            let type_tag: TypeTag = type_tag_core_to_sdk(&(&layout).into());
169            Error::Internal(format!(
170                "Failed to deserialize Move value for type: {type_tag}"
171            ))
172        })
173    }
174
175    fn data_impl(&self, layout: A::MoveTypeLayout) -> Result<MoveData, Error> {
176        MoveData::try_from(self.value_impl(layout)?)
177    }
178
179    fn json_impl(&self, layout: A::MoveTypeLayout) -> Result<Json, Error> {
180        Ok(try_to_json_value(self.value_impl(layout)?)?.into())
181    }
182}
183
184impl TryFrom<A::MoveValue> for MoveData {
185    type Error = Error;
186
187    fn try_from(value: A::MoveValue) -> Result<Self, Error> {
188        use A::MoveValue as V;
189
190        Ok(match value {
191            V::U8(n) => Self::Number(BigInt::from(n)),
192            V::U16(n) => Self::Number(BigInt::from(n)),
193            V::U32(n) => Self::Number(BigInt::from(n)),
194            V::U64(n) => Self::Number(BigInt::from(n)),
195            V::U128(n) => Self::Number(BigInt::from(n)),
196            V::U256(n) => Self::Number(BigInt::from(n)),
197
198            V::Bool(b) => Self::Bool(b),
199            V::Address(a) => Self::Address(a.into()),
200
201            V::Vector(v) => Self::Vector(
202                v.into_iter()
203                    .map(MoveData::try_from)
204                    .collect::<Result<Vec<_>, _>>()?,
205            ),
206
207            V::Struct(s) => {
208                let A::MoveStruct { type_, fields } = s;
209                let type_ = struct_tag_core_to_sdk(&type_);
210                if is_type(&type_, &STD, MOD_OPTION, TYPE_OPTION) {
211                    // 0x1::option::Option
212                    Self::Option(match extract_option(&type_, fields)? {
213                        Some(value) => Some(Box::new(MoveData::try_from(value)?)),
214                        None => None,
215                    })
216                } else if is_type(&type_, &STD, MOD_ASCII, TYPE_STRING)
217                    || is_type(&type_, &STD, MOD_STRING, TYPE_STRING)
218                {
219                    // 0x1::ascii::String, 0x1::string::String
220                    Self::String(extract_string(&type_, fields)?)
221                } else if is_type(&type_, &IOTA, MOD_OBJECT, TYPE_UID) {
222                    // 0x2::object::UID
223                    Self::Uid(extract_uid(&type_, fields)?.into())
224                } else if is_type(&type_, &IOTA, MOD_OBJECT, TYPE_ID) {
225                    // 0x2::object::ID
226                    Self::Id(extract_id(&type_, fields)?.into())
227                } else {
228                    // Arbitrary structs
229                    let fields: Result<Vec<_>, _> =
230                        fields.into_iter().map(MoveField::try_from).collect();
231                    Self::Struct(fields?)
232                }
233            }
234
235            V::Variant(A::MoveVariant {
236                type_: _,
237                variant_name,
238                tag: _,
239                fields,
240            }) => {
241                let fields = fields
242                    .into_iter()
243                    .map(MoveField::try_from)
244                    .collect::<Result<_, _>>()?;
245                Self::Variant(MoveVariant {
246                    name: variant_name.to_string(),
247                    fields,
248                })
249            }
250
251            // IOTA does not support `signer` as a type.
252            V::Signer(_) => return Err(unexpected_signer_error()),
253        })
254    }
255}
256
257impl TryFrom<(Identifier, A::MoveValue)> for MoveField {
258    type Error = Error;
259
260    fn try_from((ident, value): (Identifier, A::MoveValue)) -> Result<Self, Error> {
261        Ok(MoveField {
262            name: ident.to_string(),
263            value: MoveData::try_from(value)?,
264        })
265    }
266}
267
268fn try_to_json_value(value: A::MoveValue) -> Result<Value, Error> {
269    use A::MoveValue as V;
270    Ok(match value {
271        V::U8(n) => Value::Number(n.into()),
272        V::U16(n) => Value::Number(n.into()),
273        V::U32(n) => Value::Number(n.into()),
274        V::U64(n) => Value::String(n.to_string()),
275        V::U128(n) => Value::String(n.to_string()),
276        V::U256(n) => Value::String(n.to_string()),
277
278        V::Bool(b) => Value::Boolean(b),
279        V::Address(a) => Value::String(a.to_canonical_string(/* with_prefix */ true)),
280
281        V::Vector(xs) => Value::List(
282            xs.into_iter()
283                .map(try_to_json_value)
284                .collect::<Result<_, _>>()?,
285        ),
286
287        V::Struct(s) => {
288            let A::MoveStruct { type_, fields } = s;
289            let type_ = struct_tag_core_to_sdk(&type_);
290            if is_type(&type_, &STD, MOD_OPTION, TYPE_OPTION) {
291                // 0x1::option::Option
292                match extract_option(&type_, fields)? {
293                    Some(value) => try_to_json_value(value)?,
294                    None => Value::Null,
295                }
296            } else if is_type(&type_, &STD, MOD_ASCII, TYPE_STRING)
297                || is_type(&type_, &STD, MOD_STRING, TYPE_STRING)
298            {
299                // 0x1::ascii::String, 0x1::string::String
300                Value::String(extract_string(&type_, fields)?)
301            } else if is_type(&type_, &IOTA, MOD_OBJECT, TYPE_UID) {
302                // 0x2::object::UID
303                Value::String(
304                    extract_uid(&type_, fields)?.to_canonical_string(/* with_prefix */ true),
305                )
306            } else if is_type(&type_, &IOTA, MOD_OBJECT, TYPE_ID) {
307                // 0x2::object::ID
308                Value::String(
309                    extract_id(&type_, fields)?.to_canonical_string(/* with_prefix */ true),
310                )
311            } else {
312                // Arbitrary structs
313                Value::Object(
314                    fields
315                        .into_iter()
316                        .map(|(name, value)| {
317                            Ok((Name::new(name.to_string()), try_to_json_value(value)?))
318                        })
319                        .collect::<Result<_, Error>>()?,
320                )
321            }
322        }
323
324        V::Variant(A::MoveVariant {
325            type_: _,
326            variant_name,
327            tag: _,
328            fields,
329        }) => {
330            let fields = fields
331                .into_iter()
332                .map(|(name, value)| Ok((Name::new(name.to_string()), try_to_json_value(value)?)))
333                .collect::<Result<_, Error>>()?;
334            Value::Object(
335                vec![(Name::new(variant_name.to_string()), Value::Object(fields))]
336                    .into_iter()
337                    .collect(),
338            )
339        }
340        // IOTA does not support `signer` as a type.
341        V::Signer(_) => return Err(unexpected_signer_error()),
342    })
343}
344
345fn is_type(tag: &StructTag, address: &AccountAddress, module: &IdentStr, name: &IdentStr) -> bool {
346    tag.address().as_bytes() == address.as_ref()
347        && tag.module().as_str() == module.as_str()
348        && tag.name().as_str() == name.as_str()
349}
350
351macro_rules! extract_field {
352    ($type:expr, $fields:expr, $name:ident) => {{
353        let _name = ident_str!(stringify!($name));
354        let _type = $type;
355        if let Some(value) = ($fields)
356            .into_iter()
357            .find_map(|(name, value)| (&*name == _name).then_some(value))
358        {
359            value
360        } else {
361            return Err(Error::Internal(format!(
362                "Couldn't find expected field '{_name}' of {_type}."
363            )));
364        }
365    }};
366}
367
368/// Extracts a vector of bytes from `value`, assuming it's a `MoveValue::Vector`
369/// where all the values are `MoveValue::U8`s.
370fn extract_bytes(value: A::MoveValue) -> Result<Vec<u8>, Error> {
371    use A::MoveValue as V;
372    let V::Vector(elements) = value else {
373        return Err(Error::Internal("Expected a vector.".to_string()));
374    };
375
376    let mut bytes = Vec::with_capacity(elements.len());
377    for element in elements {
378        let V::U8(byte) = element else {
379            return Err(Error::Internal("Expected a byte.".to_string()));
380        };
381        bytes.push(byte)
382    }
383
384    Ok(bytes)
385}
386
387/// Extracts a Rust String from the contents of a Move Struct assuming that
388/// struct matches the contents of Move String:
389///
390/// ```notrust
391///     { bytes: vector<u8> }
392/// ```
393///
394/// Which is conformed to by both `std::ascii::String` and
395/// `std::string::String`.
396fn extract_string(
397    type_: &StructTag,
398    fields: Vec<(Identifier, A::MoveValue)>,
399) -> Result<String, Error> {
400    let bytes = extract_bytes(extract_field!(type_, fields, bytes))?;
401    String::from_utf8(bytes).map_err(|e| {
402        const PREFIX: usize = 30;
403        let bytes = e.as_bytes();
404
405        // Provide a sample of the string in question.
406        let sample = if bytes.len() < PREFIX {
407            String::from_utf8_lossy(bytes)
408        } else {
409            String::from_utf8_lossy(&bytes[..PREFIX - 3]) + "..."
410        };
411
412        Error::Internal(format!("{e} in {sample:?}"))
413    })
414}
415
416/// Extracts an address from the contents of a Move Struct, assuming the struct
417/// matches the following shape:
418///
419/// ```notrust
420///     { bytes: address }
421/// ```
422///
423/// Which matches `0x2::object::ID`.
424fn extract_id(
425    type_: &StructTag,
426    fields: Vec<(Identifier, A::MoveValue)>,
427) -> Result<AccountAddress, Error> {
428    use A::MoveValue as V;
429    let V::Address(addr) = extract_field!(type_, fields, bytes) else {
430        return Err(Error::Internal(
431            "Expected ID.bytes to have type address.".to_string(),
432        ));
433    };
434
435    Ok(addr)
436}
437
438/// Extracts an address from the contents of a Move Struct, assuming the struct
439/// matches the following shape:
440///
441/// ```notrust
442///     { id: 0x2::object::ID { bytes: address } }
443/// ```
444///
445/// Which matches `0x2::object::UID`.
446fn extract_uid(
447    type_: &StructTag,
448    fields: Vec<(Identifier, A::MoveValue)>,
449) -> Result<AccountAddress, Error> {
450    use A::MoveValue as V;
451    let V::Struct(s) = extract_field!(type_, fields, id) else {
452        return Err(Error::Internal(
453            "Expected UID.id to be a struct".to_string(),
454        ));
455    };
456
457    let A::MoveStruct { type_, fields } = s;
458    let type_ = struct_tag_core_to_sdk(&type_);
459    if !is_type(&type_, &IOTA, MOD_OBJECT, TYPE_ID) {
460        return Err(Error::Internal(
461            "Expected UID.id to have type ID.".to_string(),
462        ));
463    }
464
465    extract_id(&type_, fields)
466}
467
468/// Extracts a value from the contents of a Move Struct, assuming the struct
469/// matches the following shape:
470///
471/// ```notrust
472///     { vec: vector<T> }
473/// ```
474///
475/// Where `vec` contains at most one element.  This matches the shape of
476/// `0x1::option::Option<T>`.
477fn extract_option(
478    type_: &StructTag,
479    fields: Vec<(Identifier, A::MoveValue)>,
480) -> Result<Option<A::MoveValue>, Error> {
481    let A::MoveValue::Vector(mut elements) = extract_field!(type_, fields, vec) else {
482        return Err(Error::Internal(
483            "Expected Option.vec to be a vector.".to_string(),
484        ));
485    };
486
487    if elements.len() > 1 {
488        return Err(Error::Internal(
489            "Expected Option.vec to contain at most one element.".to_string(),
490        ));
491    };
492
493    Ok(elements.pop())
494}
495
496#[cfg(test)]
497mod tests {
498    use std::str::FromStr;
499
500    use expect_test::expect;
501    use move_core_types::{
502        annotated_value::{self as A, MoveFieldLayout, MoveStructLayout as S, MoveTypeLayout as L},
503        u256::U256,
504    };
505
506    use super::*;
507
508    macro_rules! struct_layout {
509        ($type:literal { $($name:literal : $layout:expr),* $(,)?}) => {
510            A::MoveTypeLayout::Struct(Box::new(S {
511                type_: move_core_types::language_storage::StructTag::from_str($type).expect("failed to parse struct"),
512                fields: vec![$(MoveFieldLayout {
513                    name: ident_str!($name).to_owned(),
514                    layout: $layout,
515                }),*]
516            }))
517        }
518    }
519
520    macro_rules! vector_layout {
521        ($inner:expr) => {
522            A::MoveTypeLayout::Vector(Box::new($inner))
523        };
524    }
525
526    fn address(a: &str) -> IotaAddress {
527        IotaAddress::from_str(a).unwrap()
528    }
529
530    fn data<T: Serialize>(layout: A::MoveTypeLayout, data: T) -> Result<MoveData, Error> {
531        let tag: TypeTag = type_tag_core_to_sdk(&(&layout).into());
532
533        // The format for type from its `Display` impl does not technically match the
534        // format that the RPC expects from the data layer (where a type's
535        // package should be canonicalized), but it will suffice.
536        data_with_tag(format!("{tag}"), layout, data)
537    }
538
539    fn data_with_tag<T: Serialize>(
540        tag: impl Into<String>,
541        layout: A::MoveTypeLayout,
542        data: T,
543    ) -> Result<MoveData, Error> {
544        let tag = TypeTag::from_str(tag.into().as_str()).unwrap();
545        let type_ = MoveType::from(tag);
546        let bcs = Base64(bcs::to_bytes(&data).unwrap());
547        MoveValue { type_, bcs }.data_impl(layout)
548    }
549
550    fn json<T: Serialize>(layout: A::MoveTypeLayout, data: T) -> Result<Json, Error> {
551        let tag: TypeTag = type_tag_core_to_sdk(&(&layout).into());
552        let type_ = MoveType::from(tag);
553        let bcs = Base64(bcs::to_bytes(&data).unwrap());
554        MoveValue { type_, bcs }.json_impl(layout)
555    }
556
557    #[test]
558    fn bool_data() {
559        let v = data(L::Bool, true);
560        let expect = expect!["Ok(Bool(true))"];
561        expect.assert_eq(&format!("{v:?}"));
562    }
563
564    #[test]
565    fn bool_json() {
566        let v = json(L::Bool, true).unwrap();
567        let expect = expect!["true"];
568        expect.assert_eq(&format!("{v}"));
569    }
570
571    #[test]
572    fn u8_data() {
573        let v = data(L::U8, 42u8);
574        let expect = expect![[r#"Ok(Number(BigInt("42")))"#]];
575        expect.assert_eq(&format!("{v:?}"));
576    }
577
578    #[test]
579    fn u8_json() {
580        let v = json(L::U8, 42u8).unwrap();
581        let expect = expect!["42"];
582        expect.assert_eq(&format!("{v}"));
583    }
584
585    #[test]
586    fn u16_data() {
587        let v = data(L::U16, 424u16);
588        let expect = expect![[r#"Ok(Number(BigInt("424")))"#]];
589        expect.assert_eq(&format!("{v:?}"));
590    }
591
592    #[test]
593    fn u16_json() {
594        let v = json(L::U16, 424u16).unwrap();
595        let expect = expect!["424"];
596        expect.assert_eq(&format!("{v}"));
597    }
598
599    #[test]
600    fn u32_data() {
601        let v = data(L::U32, 424_242u32);
602        let expect = expect![[r#"Ok(Number(BigInt("424242")))"#]];
603        expect.assert_eq(&format!("{v:?}"));
604    }
605
606    #[test]
607    fn u32_json() {
608        let v = json(L::U32, 424_242u32).unwrap();
609        let expect = expect!["424242"];
610        expect.assert_eq(&format!("{v}"));
611    }
612
613    #[test]
614    fn u64_data() {
615        let v = data(L::U64, 42_424_242_424u64);
616        let expect = expect![[r#"Ok(Number(BigInt("42424242424")))"#]];
617        expect.assert_eq(&format!("{v:?}"));
618    }
619
620    #[test]
621    fn u64_json() {
622        let v = json(L::U64, 42_424_242_424u64).unwrap();
623        let expect = expect![[r#""42424242424""#]];
624        expect.assert_eq(&format!("{v}"));
625    }
626
627    #[test]
628    fn u128_data() {
629        let v = data(L::U128, 424_242_424_242_424_242_424u128);
630        let expect = expect![[r#"Ok(Number(BigInt("424242424242424242424")))"#]];
631        expect.assert_eq(&format!("{v:?}"));
632    }
633
634    #[test]
635    fn u128_json() {
636        let v = json(L::U128, 424_242_424_242_424_242_424u128).unwrap();
637        let expect = expect![[r#""424242424242424242424""#]];
638        expect.assert_eq(&format!("{v}"));
639    }
640
641    #[test]
642    fn u256_data() {
643        let v = data(
644            L::U256,
645            U256::from_str("42424242424242424242424242424242424242424").unwrap(),
646        );
647        let expect =
648            expect![[r#"Ok(Number(BigInt("42424242424242424242424242424242424242424")))"#]];
649        expect.assert_eq(&format!("{v:?}"));
650    }
651
652    #[test]
653    fn u256_json() {
654        let v = json(
655            L::U256,
656            U256::from_str("42424242424242424242424242424242424242424").unwrap(),
657        )
658        .unwrap();
659        let expect = expect![[r#""42424242424242424242424242424242424242424""#]];
660        expect.assert_eq(&format!("{v}"));
661    }
662
663    #[test]
664    fn ascii_string_data() {
665        let l = struct_layout!("0x1::ascii::String" {
666            "bytes": vector_layout!(L::U8)
667        });
668
669        let v = data(l, "The quick brown fox");
670        let expect = expect![[r#"Ok(String("The quick brown fox"))"#]];
671        expect.assert_eq(&format!("{v:?}"));
672    }
673
674    #[test]
675    fn ascii_string_json() {
676        let l = struct_layout!("0x1::ascii::String" {
677            "bytes": vector_layout!(L::U8)
678        });
679
680        let v = json(l, "The quick brown fox").unwrap();
681        let expect = expect![[r#""The quick brown fox""#]];
682        expect.assert_eq(&format!("{v}"));
683    }
684
685    #[test]
686    fn utf8_string_data() {
687        let l = struct_layout!("0x1::string::String" {
688            "bytes": vector_layout!(L::U8)
689        });
690
691        let v = data(l, "jumped over the lazy dog.");
692        let expect = expect![[r#"Ok(String("jumped over the lazy dog."))"#]];
693        expect.assert_eq(&format!("{v:?}"));
694    }
695
696    #[test]
697    fn utf8_string_json() {
698        let l = struct_layout!("0x1::string::String" {
699            "bytes": vector_layout!(L::U8)
700        });
701
702        let v = json(l, "jumped over the lazy dog.").unwrap();
703        let expect = expect![[r#""jumped over the lazy dog.""#]];
704        expect.assert_eq(&format!("{v}"));
705    }
706
707    #[test]
708    fn string_encoding_error() {
709        let l = struct_layout!("0x1::string::String" {
710            "bytes": vector_layout!(L::U8)
711        });
712
713        let mut bytes = "Lorem ipsum dolor sit amet consectetur".as_bytes().to_vec();
714        bytes[5] = 0xff;
715
716        let v = data(l, bytes);
717        let expect = expect![[r#"
718            Err(
719                Internal(
720                    "invalid utf-8 sequence of 1 bytes from index 5 in \"Lorem�ipsum dolor sit amet ...\"",
721                ),
722            )"#]];
723        expect.assert_eq(&format!("{v:#?}"));
724    }
725
726    #[test]
727    fn address_data() {
728        let v = data(L::Address, address("0x42"));
729        let expect = expect![
730            "Ok(Address(IotaAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66])))"
731        ];
732        expect.assert_eq(&format!("{v:?}"));
733    }
734
735    #[test]
736    fn address_json() {
737        let v = json(L::Address, address("0x42")).unwrap();
738        let expect =
739            expect![[r#""0x0000000000000000000000000000000000000000000000000000000000000042""#]];
740        expect.assert_eq(&format!("{v}"));
741    }
742
743    #[test]
744    fn uid_data() {
745        let l = struct_layout!("0x2::object::UID" {
746            "id": struct_layout!("0x2::object::ID" {
747                "bytes": L::Address,
748            })
749        });
750
751        let v = data(l, address("0x42"));
752        let expect = expect![
753            "Ok(Uid(IotaAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66])))"
754        ];
755        expect.assert_eq(&format!("{v:?}"));
756    }
757
758    #[test]
759    fn uid_json() {
760        let l = struct_layout!("0x2::object::UID" {
761            "id": struct_layout!("0x2::object::ID" {
762                "bytes": L::Address,
763            })
764        });
765
766        let v = json(l, address("0x42")).unwrap();
767        let expect =
768            expect![[r#""0x0000000000000000000000000000000000000000000000000000000000000042""#]];
769        expect.assert_eq(&format!("{v}"));
770    }
771
772    #[test]
773    fn compound_data() {
774        let l = struct_layout!("0x42::foo::Bar" {
775            "baz": struct_layout!("0x1::option::Option" { "vec": vector_layout!(L::U8) }),
776            "qux": vector_layout!(struct_layout!("0x43::xy::Zzy" {
777                "quy": L::U16,
778                "quz": struct_layout!("0x1::option::Option" {
779                    "vec": vector_layout!(struct_layout!("0x1::ascii::String" {
780                        "bytes": vector_layout!(L::U8),
781                    }))
782                }),
783                "frob": L::Address,
784            })),
785        });
786
787        let v = data(
788            l,
789            (
790                vec![] as Vec<Vec<u8>>,
791                vec![
792                    (44u16, vec!["Hello, world!"], address("0x45")),
793                    (46u16, vec![], address("0x47")),
794                ],
795            ),
796        );
797
798        let expect = expect![[r#"
799            Ok(
800                Struct(
801                    [
802                        MoveField {
803                            name: "baz",
804                            value: Option(
805                                None,
806                            ),
807                        },
808                        MoveField {
809                            name: "qux",
810                            value: Vector(
811                                [
812                                    Struct(
813                                        [
814                                            MoveField {
815                                                name: "quy",
816                                                value: Number(
817                                                    BigInt(
818                                                        "44",
819                                                    ),
820                                                ),
821                                            },
822                                            MoveField {
823                                                name: "quz",
824                                                value: Option(
825                                                    Some(
826                                                        String(
827                                                            "Hello, world!",
828                                                        ),
829                                                    ),
830                                                ),
831                                            },
832                                            MoveField {
833                                                name: "frob",
834                                                value: Address(
835                                                    IotaAddress(
836                                                        [
837                                                            0,
838                                                            0,
839                                                            0,
840                                                            0,
841                                                            0,
842                                                            0,
843                                                            0,
844                                                            0,
845                                                            0,
846                                                            0,
847                                                            0,
848                                                            0,
849                                                            0,
850                                                            0,
851                                                            0,
852                                                            0,
853                                                            0,
854                                                            0,
855                                                            0,
856                                                            0,
857                                                            0,
858                                                            0,
859                                                            0,
860                                                            0,
861                                                            0,
862                                                            0,
863                                                            0,
864                                                            0,
865                                                            0,
866                                                            0,
867                                                            0,
868                                                            69,
869                                                        ],
870                                                    ),
871                                                ),
872                                            },
873                                        ],
874                                    ),
875                                    Struct(
876                                        [
877                                            MoveField {
878                                                name: "quy",
879                                                value: Number(
880                                                    BigInt(
881                                                        "46",
882                                                    ),
883                                                ),
884                                            },
885                                            MoveField {
886                                                name: "quz",
887                                                value: Option(
888                                                    None,
889                                                ),
890                                            },
891                                            MoveField {
892                                                name: "frob",
893                                                value: Address(
894                                                    IotaAddress(
895                                                        [
896                                                            0,
897                                                            0,
898                                                            0,
899                                                            0,
900                                                            0,
901                                                            0,
902                                                            0,
903                                                            0,
904                                                            0,
905                                                            0,
906                                                            0,
907                                                            0,
908                                                            0,
909                                                            0,
910                                                            0,
911                                                            0,
912                                                            0,
913                                                            0,
914                                                            0,
915                                                            0,
916                                                            0,
917                                                            0,
918                                                            0,
919                                                            0,
920                                                            0,
921                                                            0,
922                                                            0,
923                                                            0,
924                                                            0,
925                                                            0,
926                                                            0,
927                                                            71,
928                                                        ],
929                                                    ),
930                                                ),
931                                            },
932                                        ],
933                                    ),
934                                ],
935                            ),
936                        },
937                    ],
938                ),
939            )"#]];
940        expect.assert_eq(&format!("{v:#?}"));
941    }
942
943    #[test]
944    fn compound_json() {
945        let l = struct_layout!("0x42::foo::Bar" {
946            "baz": struct_layout!("0x1::option::Option" { "vec": vector_layout!(L::U8) }),
947            "qux": vector_layout!(struct_layout!("0x43::xy::Zzy" {
948                "quy": L::U16,
949                "quz": struct_layout!("0x1::option::Option" {
950                    "vec": vector_layout!(struct_layout!("0x1::ascii::String" {
951                        "bytes": vector_layout!(L::U8),
952                    }))
953                }),
954                "frob": L::Address,
955            })),
956        });
957
958        let v = json(
959            l,
960            (
961                vec![] as Vec<Vec<u8>>,
962                vec![
963                    (44u16, vec!["Hello, world!"], address("0x45")),
964                    (46u16, vec![], address("0x47")),
965                ],
966            ),
967        )
968        .unwrap();
969
970        let expect = expect![[
971            r#"{baz: null, qux: [{quy: 44, quz: "Hello, world!", frob: "0x0000000000000000000000000000000000000000000000000000000000000045"}, {quy: 46, quz: null, frob: "0x0000000000000000000000000000000000000000000000000000000000000047"}]}"#
972        ]];
973        expect.assert_eq(&format!("{v}"));
974    }
975
976    #[test]
977    fn signer_value() {
978        let v = data(L::Signer, address("0x42"));
979        let expect = expect![[r#"
980            Err(
981                Internal(
982                    "Unexpected value of type: signer.",
983                ),
984            )"#]];
985        expect.assert_eq(&format!("{v:#?}"));
986    }
987
988    #[test]
989    fn signer_json() {
990        let err = json(L::Signer, address("0x42")).unwrap_err();
991        let expect = expect![[r#"Internal("Unexpected value of type: signer.")"#]];
992        expect.assert_eq(&format!("{err:?}"));
993    }
994
995    #[test]
996    fn signer_nested_data() {
997        let v = data(
998            vector_layout!(L::Signer),
999            vec![address("0x42"), address("0x43")],
1000        );
1001        let expect = expect![[r#"
1002            Err(
1003                Internal(
1004                    "Unexpected value of type: signer.",
1005                ),
1006            )"#]];
1007        expect.assert_eq(&format!("{v:#?}"));
1008    }
1009
1010    #[test]
1011    fn signer_nested_json() {
1012        let err = json(
1013            vector_layout!(L::Signer),
1014            vec![address("0x42"), address("0x43")],
1015        )
1016        .unwrap_err();
1017
1018        let expect = expect![[r#"Internal("Unexpected value of type: signer.")"#]];
1019        expect.assert_eq(&format!("{err:?}"));
1020    }
1021}