contract_transcode/
transcoder.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17use super::{
18    decode::Decoder,
19    encode::Encoder,
20    env_types::{
21        self,
22        CustomTypeDecoder,
23        CustomTypeEncoder,
24        EnvTypesTranscoder,
25        PathKey,
26        TypesByPath,
27    },
28    scon::Value,
29    AccountId32,
30};
31
32use anyhow::Result;
33use scale::Output;
34use scale_info::{
35    PortableRegistry,
36    TypeInfo,
37};
38use std::{
39    collections::HashMap,
40    fmt::Debug,
41};
42
43/// Encode strings to SCALE encoded output.
44/// Decode SCALE encoded input into `Value` objects.
45pub struct Transcoder {
46    env_types: EnvTypesTranscoder,
47}
48
49impl Transcoder {
50    pub fn new(env_types: EnvTypesTranscoder) -> Self {
51        Self { env_types }
52    }
53
54    pub fn encode<O>(
55        &self,
56        registry: &PortableRegistry,
57        type_id: u32,
58        value: &Value,
59        output: &mut O,
60    ) -> Result<()>
61    where
62        O: Output + Debug,
63    {
64        let encoder = Encoder::new(registry, &self.env_types);
65        encoder.encode(type_id, value, output)
66    }
67
68    pub fn decode(
69        &self,
70        registry: &PortableRegistry,
71        type_id: u32,
72        input: &mut &[u8],
73    ) -> Result<Value> {
74        let decoder = Decoder::new(registry, &self.env_types);
75        decoder.decode(type_id, input)
76    }
77}
78
79/// Construct a [`Transcoder`], allows registering custom transcoders for certain types.
80pub struct TranscoderBuilder {
81    types_by_path: TypesByPath,
82    encoders: HashMap<u32, Box<dyn CustomTypeEncoder>>,
83    decoders: HashMap<u32, Box<dyn CustomTypeDecoder>>,
84}
85
86impl TranscoderBuilder {
87    pub fn new(registry: &PortableRegistry) -> Self {
88        let types_by_path = registry
89            .types
90            .iter()
91            .map(|ty| (PathKey::from(&ty.ty.path), ty.id))
92            .collect::<TypesByPath>();
93        Self {
94            types_by_path,
95            encoders: HashMap::new(),
96            decoders: HashMap::new(),
97        }
98    }
99
100    pub fn with_default_custom_type_transcoders(self) -> Self {
101        self.register_custom_type_transcoder::<AccountId32, _>(env_types::AccountId)
102            .register_custom_type_decoder::<primitive_types::H256, _>(env_types::Hash)
103    }
104
105    pub fn register_custom_type_transcoder<T, U>(self, transcoder: U) -> Self
106    where
107        T: TypeInfo + 'static,
108        U: CustomTypeEncoder + CustomTypeDecoder + Clone + 'static,
109    {
110        self.register_custom_type_encoder::<T, U>(transcoder.clone())
111            .register_custom_type_decoder::<T, U>(transcoder)
112    }
113
114    pub fn register_custom_type_encoder<T, U>(self, encoder: U) -> Self
115    where
116        T: TypeInfo + 'static,
117        U: CustomTypeEncoder + 'static,
118    {
119        let mut this = self;
120
121        let path_key = PathKey::from_type::<T>();
122        let type_id = this.types_by_path.get(&path_key);
123
124        match type_id {
125            Some(type_id) => {
126                let existing = this.encoders.insert(*type_id, Box::new(encoder));
127                tracing::debug!("Registered custom encoder for type `{:?}`", type_id);
128                if existing.is_some() {
129                    panic!(
130                        "Attempted to register encoder with existing type id {type_id:?}"
131                    );
132                }
133            }
134            None => {
135                // if the type is not present in the registry, it just means it has not
136                // been used.
137                tracing::debug!("No matching type in registry for path {:?}.", path_key);
138            }
139        }
140        this
141    }
142
143    pub fn register_custom_type_decoder<T, U>(self, encoder: U) -> Self
144    where
145        T: TypeInfo + 'static,
146        U: CustomTypeDecoder + 'static,
147    {
148        let mut this = self;
149
150        let path_key = PathKey::from_type::<T>();
151        let type_id = this.types_by_path.get(&path_key);
152
153        match type_id {
154            Some(type_id) => {
155                let existing = this.decoders.insert(*type_id, Box::new(encoder));
156                tracing::debug!("Registered custom decoder for type `{:?}`", type_id);
157                if existing.is_some() {
158                    panic!(
159                        "Attempted to register decoder with existing type id {type_id:?}"
160                    );
161                }
162            }
163            None => {
164                // if the type is not present in the registry, it just means it has not
165                // been used.
166                tracing::debug!("No matching type in registry for path {:?}.", path_key);
167            }
168        }
169        this
170    }
171
172    pub fn done(self) -> Transcoder {
173        let env_types_transcoder = EnvTypesTranscoder::new(self.encoders, self.decoders);
174        Transcoder::new(env_types_transcoder)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::{
181        super::scon::{
182            self,
183            Hex,
184            Map,
185            Seq,
186            Tuple,
187            Value,
188        },
189        *,
190    };
191    use scale::Encode;
192    use scale_info::{
193        MetaType,
194        Registry,
195        TypeInfo,
196    };
197    use std::str::FromStr;
198
199    fn registry_with_type<T>() -> Result<(PortableRegistry, u32)>
200    where
201        T: scale_info::TypeInfo + 'static,
202    {
203        let mut registry = Registry::new();
204        let type_id = registry.register_type(&MetaType::new::<T>());
205        let registry: PortableRegistry = registry.into();
206
207        Ok((registry, type_id.id))
208    }
209
210    fn transcode_roundtrip<T>(input: &str, expected_output: Value) -> Result<()>
211    where
212        T: scale_info::TypeInfo + 'static,
213    {
214        let (registry, ty) = registry_with_type::<T>()?;
215        let transcoder = TranscoderBuilder::new(&registry)
216            .with_default_custom_type_transcoders()
217            .done();
218
219        let value = scon::parse_value(input)?;
220
221        let mut output = Vec::new();
222        transcoder.encode(&registry, ty, &value, &mut output)?;
223        let decoded = transcoder.decode(&registry, ty, &mut &output[..])?;
224        assert_eq!(expected_output, decoded, "decoding");
225        Ok(())
226    }
227
228    #[test]
229    fn transcode_bool() -> Result<()> {
230        transcode_roundtrip::<bool>("true", Value::Bool(true))?;
231        transcode_roundtrip::<bool>("false", Value::Bool(false))
232    }
233
234    #[test]
235    fn transcode_char_unsupported() -> Result<()> {
236        let (registry, ty) = registry_with_type::<char>()?;
237        let transcoder = Transcoder::new(Default::default());
238        let encoded = u32::from('c').encode();
239
240        assert!(transcoder
241            .encode(&registry, ty, &Value::Char('c'), &mut Vec::new())
242            .is_err());
243        assert!(transcoder.decode(&registry, ty, &mut &encoded[..]).is_err());
244        Ok(())
245    }
246
247    #[test]
248    fn transcode_str() -> Result<()> {
249        transcode_roundtrip::<String>("\"ink!\"", Value::String("ink!".to_string()))
250    }
251
252    #[test]
253    fn transcode_unsigned_integers() -> Result<()> {
254        transcode_roundtrip::<u8>("0", Value::UInt(0))?;
255        transcode_roundtrip::<u8>("255", Value::UInt(255))?;
256
257        transcode_roundtrip::<u16>("0", Value::UInt(0))?;
258        transcode_roundtrip::<u16>("65535", Value::UInt(65535))?;
259
260        transcode_roundtrip::<u32>("0", Value::UInt(0))?;
261        transcode_roundtrip::<u32>("4294967295", Value::UInt(4294967295))?;
262
263        transcode_roundtrip::<u64>("0", Value::UInt(0))?;
264        transcode_roundtrip::<u64>(
265            "\"18_446_744_073_709_551_615\"",
266            Value::UInt(18446744073709551615),
267        )?;
268
269        transcode_roundtrip::<u128>("0", Value::UInt(0))?;
270        transcode_roundtrip::<u128>(
271            "\"340_282_366_920_938_463_463_374_607_431_768_211_455\"",
272            Value::UInt(340282366920938463463374607431768211455),
273        )
274    }
275
276    #[test]
277    fn transcode_integers() -> Result<()> {
278        transcode_roundtrip::<i8>("-128", Value::Int(i8::MIN.into()))?;
279        transcode_roundtrip::<i8>("127", Value::Int(i8::MAX.into()))?;
280
281        transcode_roundtrip::<i16>("-32768", Value::Int(i16::MIN.into()))?;
282        transcode_roundtrip::<i16>("32767", Value::Int(i16::MAX.into()))?;
283
284        transcode_roundtrip::<i32>("-2147483648", Value::Int(i32::MIN.into()))?;
285        transcode_roundtrip::<i32>("2147483647", Value::Int(i32::MAX.into()))?;
286
287        transcode_roundtrip::<i64>("-9223372036854775808", Value::Int(i64::MIN.into()))?;
288        transcode_roundtrip::<i64>(
289            "\"9_223_372_036_854_775_807\"",
290            Value::Int(i64::MAX.into()),
291        )?;
292
293        transcode_roundtrip::<i128>(
294            "-170141183460469231731687303715884105728",
295            Value::Int(i128::MIN),
296        )?;
297        transcode_roundtrip::<i128>(
298            "\"170141183460469231731687303715884105727\"",
299            Value::Int(i128::MAX),
300        )
301    }
302
303    #[test]
304    fn transcode_byte_array() -> Result<()> {
305        transcode_roundtrip::<[u8; 2]>(
306            r#"0x0000"#,
307            Value::Seq(vec![Value::UInt(0x00), Value::UInt(0x00)].into()),
308        )?;
309        transcode_roundtrip::<[u8; 4]>(
310            r#"0xDEADBEEF"#,
311            Value::Seq(
312                vec![
313                    Value::UInt(0xDE),
314                    Value::UInt(0xAD),
315                    Value::UInt(0xBE),
316                    Value::UInt(0xEF),
317                ]
318                .into(),
319            ),
320        )?;
321        transcode_roundtrip::<[u8; 4]>(
322            r#"0xdeadbeef"#,
323            Value::Seq(
324                vec![
325                    Value::UInt(0xDE),
326                    Value::UInt(0xAD),
327                    Value::UInt(0xBE),
328                    Value::UInt(0xEF),
329                ]
330                .into(),
331            ),
332        )
333    }
334
335    #[test]
336    fn transcode_array() -> Result<()> {
337        transcode_roundtrip::<[u32; 3]>(
338            "[1, 2, 3]",
339            Value::Seq(vec![Value::UInt(1), Value::UInt(2), Value::UInt(3)].into()),
340        )?;
341        transcode_roundtrip::<[String; 2]>(
342            "[\"hello\", \"world\"]",
343            Value::Seq(
344                vec![
345                    Value::String("hello".to_string()),
346                    Value::String("world".to_string()),
347                ]
348                .into(),
349            ),
350        )
351    }
352
353    #[test]
354    fn transcode_seq() -> Result<()> {
355        transcode_roundtrip::<Vec<u32>>(
356            "[1, 2, 3]",
357            Value::Seq(vec![Value::UInt(1), Value::UInt(2), Value::UInt(3)].into()),
358        )?;
359        transcode_roundtrip::<Vec<String>>(
360            "[\"hello\", \"world\"]",
361            Value::Seq(
362                vec![
363                    Value::String("hello".to_string()),
364                    Value::String("world".to_string()),
365                ]
366                .into(),
367            ),
368        )
369    }
370
371    #[test]
372    fn transcode_tuple() -> Result<()> {
373        transcode_roundtrip::<(u32, String, [u8; 4])>(
374            r#"(1, "ink!", 0xDEADBEEF)"#,
375            Value::Tuple(Tuple::new(
376                None,
377                vec![
378                    Value::UInt(1),
379                    Value::String("ink!".to_string()),
380                    Value::Seq(
381                        vec![
382                            Value::UInt(0xDE),
383                            Value::UInt(0xAD),
384                            Value::UInt(0xBE),
385                            Value::UInt(0xEF),
386                        ]
387                        .into(),
388                    ),
389                ],
390            )),
391        )
392    }
393
394    #[test]
395    fn transcode_composite_struct() -> Result<()> {
396        #[allow(dead_code)]
397        #[derive(TypeInfo)]
398        struct S {
399            a: u32,
400            b: String,
401            c: [u8; 4],
402            // recursive struct ref
403            d: Vec<S>,
404        }
405
406        transcode_roundtrip::<S>(
407            r#"S(a: 1, b: "ink!", c: 0xDEADBEEF, d: [S(a: 2, b: "ink!", c: 0xDEADBEEF, d: [])])"#,
408            Value::Map(
409                vec![
410                    (Value::String("a".to_string()), Value::UInt(1)),
411                    (
412                        Value::String("b".to_string()),
413                        Value::String("ink!".to_string()),
414                    ),
415                    (
416                        Value::String("c".to_string()),
417                        Value::Seq(
418                            vec![
419                                Value::UInt(0xDE),
420                                Value::UInt(0xAD),
421                                Value::UInt(0xBE),
422                                Value::UInt(0xEF),
423                            ]
424                            .into(),
425                        ),
426                    ),
427                    (
428                        Value::String("d".to_string()),
429                        Value::Seq(
430                            vec![Value::Map(
431                                vec![
432                                    (Value::String("a".to_string()), Value::UInt(2)),
433                                    (
434                                        Value::String("b".to_string()),
435                                        Value::String("ink!".to_string()),
436                                    ),
437                                    (
438                                        Value::String("c".to_string()),
439                                        Value::Seq(
440                                            vec![
441                                                Value::UInt(0xDE),
442                                                Value::UInt(0xAD),
443                                                Value::UInt(0xBE),
444                                                Value::UInt(0xEF),
445                                            ]
446                                            .into(),
447                                        ),
448                                    ),
449                                    (
450                                        Value::String("d".to_string()),
451                                        Value::Seq(
452                                            Vec::new()
453                                                .into_iter()
454                                                .collect::<Vec<_>>()
455                                                .into(),
456                                        ),
457                                    ),
458                                ]
459                                .into_iter()
460                                .collect(),
461                            )]
462                            .into(),
463                        ),
464                    ),
465                ]
466                .into_iter()
467                .collect(),
468            ),
469        )
470    }
471
472    #[test]
473    fn transcode_composite_struct_nested() -> Result<()> {
474        #[allow(dead_code)]
475        #[derive(TypeInfo)]
476        struct S {
477            nested: Nested,
478        }
479
480        #[allow(dead_code)]
481        #[derive(TypeInfo)]
482        struct Nested(u32);
483
484        transcode_roundtrip::<S>(
485            r#"S { nested: Nested(33) }"#,
486            Value::Map(Map::new(
487                Some("S"),
488                vec![(
489                    Value::String("nested".to_string()),
490                    Value::Tuple(Tuple::new(
491                        Some("Nested"),
492                        vec![Value::UInt(33)].into_iter().collect(),
493                    )),
494                )]
495                .into_iter()
496                .collect(),
497            )),
498        )
499    }
500
501    #[test]
502    fn transcode_composite_struct_out_of_order_fields() -> Result<()> {
503        #[allow(dead_code)]
504        #[derive(TypeInfo)]
505        struct S {
506            a: u32,
507            b: String,
508            c: [u8; 4],
509        }
510
511        transcode_roundtrip::<S>(
512            r#"S(b: "ink!", a: 1,  c: 0xDEADBEEF)"#,
513            Value::Map(
514                vec![
515                    (Value::String("a".to_string()), Value::UInt(1)),
516                    (
517                        Value::String("b".to_string()),
518                        Value::String("ink!".to_string()),
519                    ),
520                    (
521                        Value::String("c".to_string()),
522                        Value::Seq(
523                            vec![
524                                Value::UInt(0xDE),
525                                Value::UInt(0xAD),
526                                Value::UInt(0xBE),
527                                Value::UInt(0xEF),
528                            ]
529                            .into(),
530                        ),
531                    ),
532                ]
533                .into_iter()
534                .collect(),
535            ),
536        )
537    }
538
539    #[test]
540    fn transcode_composite_tuple_struct() -> Result<()> {
541        #[allow(dead_code)]
542        #[derive(TypeInfo)]
543        struct S(u32, String, [u8; 4]);
544
545        transcode_roundtrip::<S>(
546            r#"S(1, "ink!", 0xDEADBEEF)"#,
547            Value::Tuple(Tuple::new(
548                Some("S"),
549                vec![
550                    Value::UInt(1),
551                    Value::String("ink!".to_string()),
552                    Value::Seq(
553                        vec![
554                            Value::UInt(0xDE),
555                            Value::UInt(0xAD),
556                            Value::UInt(0xBE),
557                            Value::UInt(0xEF),
558                        ]
559                        .into(),
560                    ),
561                ],
562            )),
563        )
564    }
565
566    #[test]
567    fn transcode_composite_single_field_struct() -> Result<()> {
568        #[allow(dead_code)]
569        #[derive(TypeInfo)]
570        struct S([u8; 4]);
571
572        transcode_roundtrip::<S>(
573            r#"0xDEADBEEF"#,
574            Value::Tuple(Tuple::new(
575                Some("S"),
576                vec![Value::Seq(
577                    vec![
578                        Value::UInt(0xDE),
579                        Value::UInt(0xAD),
580                        Value::UInt(0xBE),
581                        Value::UInt(0xEF),
582                    ]
583                    .into(),
584                )],
585            )),
586        )
587    }
588
589    #[test]
590    fn transcode_composite_single_field_tuple() -> Result<()> {
591        transcode_roundtrip::<([u8; 4],)>(
592            r#"0xDEADBEEF"#,
593            Value::Tuple(Tuple::new(
594                None,
595                vec![Value::Seq(
596                    vec![
597                        Value::UInt(0xDE),
598                        Value::UInt(0xAD),
599                        Value::UInt(0xBE),
600                        Value::UInt(0xEF),
601                    ]
602                    .into(),
603                )],
604            )),
605        )
606    }
607
608    #[test]
609    fn transcode_enum_variant_tuple() -> Result<()> {
610        #[derive(TypeInfo)]
611        #[allow(dead_code)]
612        enum E {
613            A(u32, String),
614            B { a: [u8; 4], b: Vec<E> },
615            C,
616        }
617
618        transcode_roundtrip::<E>(
619            r#"A(1, "2")"#,
620            Value::Tuple(Tuple::new(
621                Some("A"),
622                vec![Value::UInt(1), Value::String("2".into())],
623            )),
624        )
625    }
626
627    #[test]
628    fn transcode_enum_variant_map() -> Result<()> {
629        #[derive(TypeInfo)]
630        #[allow(dead_code)]
631        enum E {
632            A { a: u32, b: bool },
633        }
634
635        transcode_roundtrip::<E>(
636            r#"A { a: 33, b: false }"#,
637            Value::Map(Map::new(
638                Some("A"),
639                vec![
640                    (Value::String("a".to_string()), Value::UInt(33)),
641                    (Value::String("b".to_string()), Value::Bool(false)),
642                ]
643                .into_iter()
644                .collect(),
645            )),
646        )
647    }
648
649    #[test]
650    fn transcode_enum_variant_map_out_of_order_fields() -> Result<()> {
651        #[derive(TypeInfo)]
652        #[allow(dead_code)]
653        enum E {
654            A { a: u32, b: bool },
655        }
656
657        transcode_roundtrip::<E>(
658            r#"A { a: 33, b: false }"#,
659            Value::Map(Map::new(
660                Some("A"),
661                vec![
662                    (Value::String("a".to_string()), Value::UInt(33)),
663                    (Value::String("b".to_string()), Value::Bool(false)),
664                ]
665                .into_iter()
666                .collect(),
667            )),
668        )
669    }
670
671    #[test]
672    fn transcode_option() -> Result<()> {
673        transcode_roundtrip::<Option<u32>>(
674            r#"Some(32)"#,
675            Value::Tuple(Tuple::new(Some("Some"), vec![Value::UInt(32)])),
676        )?;
677
678        transcode_roundtrip::<Option<u32>>(
679            r#"None"#,
680            Value::Tuple(Tuple::new(Some("None"), Vec::new())),
681        )
682    }
683
684    #[test]
685    fn transcode_account_id_custom_ss58_encoding() -> Result<()> {
686        type AccountId = AccountId32;
687
688        #[allow(dead_code)]
689        #[derive(TypeInfo)]
690        struct S {
691            no_alias: AccountId32,
692            aliased: AccountId,
693        }
694
695        transcode_roundtrip::<S>(
696            r#"S(
697                no_alias: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,
698                aliased: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty,
699             )"#,
700            Value::Map(Map::new(
701                Some("S"),
702                vec![
703                    (
704                        Value::String("no_alias".into()),
705                        Value::Literal(
706                            "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".into(),
707                        ),
708                    ),
709                    (
710                        Value::String("aliased".into()),
711                        Value::Literal(
712                            "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty".into(),
713                        ),
714                    ),
715                ]
716                .into_iter()
717                .collect(),
718            )),
719        )
720    }
721
722    #[test]
723    fn transcode_account_id_custom_ss58_encoding_seq() -> Result<()> {
724        let hex_to_bytes = |hex: &str| -> Result<Value> {
725            let hex = Hex::from_str(hex)?;
726            let values = hex.bytes().iter().map(|b| Value::UInt((*b).into()));
727            Ok(Value::Seq(Seq::new(values.collect())))
728        };
729
730        transcode_roundtrip::<Vec<AccountId32>>(
731            r#"[
732                5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,
733                5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty,
734             ]"#,
735            Value::Seq(Seq::new(
736                vec![
737                    Value::Tuple(
738                        Tuple::new(
739                            Some("AccountId32"),
740                            vec![hex_to_bytes("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d")?]
741                        )
742                    ),
743                    Value::Tuple(
744                        Tuple::new(
745                            Some("AccountId32"),
746                            vec![hex_to_bytes("0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48")?]
747                        )
748                    )
749                ]
750                    .into_iter()
751                    .collect(),
752            )),
753        )
754    }
755
756    #[test]
757    fn decode_h256_as_hex_string() -> Result<()> {
758        #[allow(dead_code)]
759        #[derive(TypeInfo)]
760        struct S {
761            hash: primitive_types::H256,
762        }
763
764        transcode_roundtrip::<S>(
765            r#"S(
766                hash: 0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de,
767             )"#,
768            Value::Map(Map::new(
769                Some("S"),
770                vec![
771                    (
772                        Value::String("hash".into()),
773                        Value::Hex(
774                            Hex::from_str("0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de")?,
775                        ),
776                    ),
777                ]
778                    .into_iter()
779                    .collect(),
780            )),
781        )
782    }
783
784    #[test]
785    fn transcode_compact_primitives() -> Result<()> {
786        transcode_roundtrip::<scale::Compact<u8>>(r#"33"#, Value::UInt(33))?;
787
788        transcode_roundtrip::<scale::Compact<u16>>(r#"33"#, Value::UInt(33))?;
789
790        transcode_roundtrip::<scale::Compact<u32>>(r#"33"#, Value::UInt(33))?;
791
792        transcode_roundtrip::<scale::Compact<u64>>(r#"33"#, Value::UInt(33))?;
793
794        transcode_roundtrip::<scale::Compact<u128>>(r#"33"#, Value::UInt(33))
795    }
796
797    #[test]
798    fn transcode_compact_struct() -> Result<()> {
799        #[derive(scale::Encode, scale::CompactAs, TypeInfo)]
800        struct CompactStruct(u32);
801
802        #[allow(dead_code)]
803        #[derive(scale::Encode, TypeInfo)]
804        struct S {
805            #[codec(compact)]
806            a: CompactStruct,
807        }
808
809        transcode_roundtrip::<S>(
810            r#"S { a: CompactStruct(33) }"#,
811            Value::Map(Map::new(
812                Some("S"),
813                vec![(
814                    Value::String("a".to_string()),
815                    Value::Tuple(Tuple::new(
816                        Some("CompactStruct"),
817                        vec![Value::UInt(33)],
818                    )),
819                )]
820                .into_iter()
821                .collect(),
822            )),
823        )
824    }
825
826    #[test]
827    fn transcode_hex_literal_uints() -> Result<()> {
828        #[derive(scale::Encode, TypeInfo)]
829        struct S(u8, u16, u32, u64, u128);
830
831        transcode_roundtrip::<S>(
832            r#"S (0xDE, 0xDEAD, 0xDEADBEEF, 0xDEADBEEF12345678, 0xDEADBEEF0123456789ABCDEF01234567)"#,
833            Value::Tuple(Tuple::new(
834                Some("S"),
835                vec![
836                    Value::UInt(0xDE),
837                    Value::UInt(0xDEAD),
838                    Value::UInt(0xDEADBEEF),
839                    Value::UInt(0xDEADBEEF12345678),
840                    Value::UInt(0xDEADBEEF0123456789ABCDEF01234567),
841                ]
842                .into_iter()
843                .collect(),
844            )),
845        )
846    }
847}