safecoin_frozen_abi/
abi_digester.rs

1use {
2    crate::{
3        abi_example::{normalize_type_name, AbiEnumVisitor},
4        hash::{Hash, Hasher},
5    },
6    log::*,
7    serde::{
8        ser::{Error as SerdeError, *},
9        Serialize, Serializer,
10    },
11    std::{any::type_name, io::Write},
12    thiserror::Error,
13};
14
15#[derive(Debug)]
16pub struct AbiDigester {
17    data_types: std::rc::Rc<std::cell::RefCell<Vec<String>>>,
18    depth: usize,
19    for_enum: bool,
20    opaque_scope: Option<String>,
21}
22
23pub type DigestResult = Result<AbiDigester, DigestError>;
24type Sstr = &'static str;
25
26#[derive(Debug, Error)]
27pub enum DigestError {
28    #[error("Option::None is serialized; no ABI digest for Option::Some")]
29    NoneIsSerialized,
30    #[error("nested error")]
31    Node(Sstr, Box<DigestError>),
32    #[error("leaf error")]
33    Leaf(Sstr, Sstr, Box<DigestError>),
34    #[error("arithmetic overflow")]
35    ArithmeticOverflow,
36}
37
38impl SerdeError for DigestError {
39    fn custom<T: std::fmt::Display>(_msg: T) -> DigestError {
40        unreachable!("This error should never be used");
41    }
42}
43
44impl DigestError {
45    pub(crate) fn wrap_by_type<T: ?Sized>(e: DigestError) -> DigestError {
46        DigestError::Node(type_name::<T>(), Box::new(e))
47    }
48
49    pub(crate) fn wrap_by_str(e: DigestError, s: Sstr) -> DigestError {
50        DigestError::Node(s, Box::new(e))
51    }
52}
53
54const INDENT_WIDTH: usize = 4;
55
56pub(crate) fn shorten_serialize_with(type_name: &str) -> &str {
57    // Fully qualified type names for the generated `__SerializeWith` types are very
58    // long and do not add extra value to the digest. They also cause the digest
59    // to change when a struct is moved to an inner module.
60    if type_name.ends_with("__SerializeWith") {
61        "__SerializeWith"
62    } else {
63        type_name
64    }
65}
66
67impl AbiDigester {
68    pub fn create() -> Self {
69        AbiDigester {
70            data_types: std::rc::Rc::new(std::cell::RefCell::new(vec![])),
71            for_enum: false,
72            depth: 0,
73            opaque_scope: None,
74        }
75    }
76
77    // must create separate instances because we can't pass the single instance to
78    // `.serialize()` multiple times
79    pub fn create_new(&self) -> Self {
80        Self {
81            data_types: self.data_types.clone(),
82            depth: self.depth,
83            for_enum: false,
84            opaque_scope: self.opaque_scope.clone(),
85        }
86    }
87
88    pub fn create_new_opaque(&self, top_scope: &str) -> Self {
89        Self {
90            data_types: self.data_types.clone(),
91            depth: self.depth,
92            for_enum: false,
93            opaque_scope: Some(top_scope.to_owned()),
94        }
95    }
96
97    pub fn create_child(&self) -> Result<Self, DigestError> {
98        let depth = self
99            .depth
100            .checked_add(1)
101            .ok_or(DigestError::ArithmeticOverflow)?;
102        Ok(Self {
103            data_types: self.data_types.clone(),
104            depth,
105            for_enum: false,
106            opaque_scope: self.opaque_scope.clone(),
107        })
108    }
109
110    pub fn create_enum_child(&self) -> Result<Self, DigestError> {
111        let depth = self
112            .depth
113            .checked_add(1)
114            .ok_or(DigestError::ArithmeticOverflow)?;
115        Ok(Self {
116            data_types: self.data_types.clone(),
117            depth,
118            for_enum: true,
119            opaque_scope: self.opaque_scope.clone(),
120        })
121    }
122
123    pub fn digest_data<T: ?Sized + Serialize>(&mut self, value: &T) -> DigestResult {
124        let type_name = normalize_type_name(type_name::<T>());
125        if type_name.ends_with("__SerializeWith")
126            || (self.opaque_scope.is_some()
127                && type_name.starts_with(self.opaque_scope.as_ref().unwrap()))
128        {
129            // we can't use the AbiEnumVisitor trait for these cases.
130            value.serialize(self.create_new())
131        } else {
132            // Don't call value.visit_for_abi(...) to prefer autoref specialization
133            // resolution for IgnoreAsHelper
134            <&T>::visit_for_abi(&value, &mut self.create_new())
135        }
136    }
137
138    pub fn update(&mut self, strs: &[&str]) {
139        let mut buf = strs
140            .iter()
141            .map(|s| {
142                // this is a bit crude, but just normalize all strings as if they're
143                // `type_name`s!
144                normalize_type_name(s)
145            })
146            .collect::<Vec<_>>()
147            .join(" ");
148        buf = format!(
149            "{:0width$}{}\n",
150            "",
151            buf,
152            width = self.depth.saturating_mul(INDENT_WIDTH)
153        );
154        info!("updating with: {}", buf.trim_end());
155        (*self.data_types.borrow_mut()).push(buf);
156    }
157
158    pub fn update_with_type<T: ?Sized>(&mut self, label: &str) {
159        self.update(&[label, type_name::<T>()]);
160    }
161
162    pub fn update_with_string(&mut self, label: String) {
163        self.update(&[&label]);
164    }
165
166    #[allow(clippy::unnecessary_wraps)]
167    fn digest_primitive<T: Serialize>(mut self) -> Result<AbiDigester, DigestError> {
168        self.update_with_type::<T>("primitive");
169        Ok(self)
170    }
171
172    fn digest_element<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), DigestError> {
173        self.update_with_type::<T>("element");
174        self.create_child()?.digest_data(v).map(|_| ())
175    }
176
177    fn digest_named_field<T: ?Sized + Serialize>(
178        &mut self,
179        key: Sstr,
180        v: &T,
181    ) -> Result<(), DigestError> {
182        let field_type_name = shorten_serialize_with(type_name::<T>());
183        self.update_with_string(format!("field {}: {}", key, field_type_name));
184        self.create_child()?
185            .digest_data(v)
186            .map(|_| ())
187            .map_err(|e| DigestError::wrap_by_str(e, key))
188    }
189
190    fn digest_unnamed_field<T: ?Sized + Serialize>(&mut self, v: &T) -> Result<(), DigestError> {
191        self.update_with_type::<T>("field");
192        self.create_child()?.digest_data(v).map(|_| ())
193    }
194
195    #[allow(clippy::unnecessary_wraps)]
196    fn check_for_enum(
197        &mut self,
198        label: &'static str,
199        variant: &'static str,
200    ) -> Result<(), DigestError> {
201        assert!(self.for_enum, "derive AbiEnumVisitor or implement it for the enum, which contains a variant ({}) named {}", label, variant);
202        Ok(())
203    }
204
205    pub fn finalize(self) -> Hash {
206        let mut hasher = Hasher::default();
207
208        for buf in (*self.data_types.borrow()).iter() {
209            hasher.hash(buf.as_bytes());
210        }
211
212        let hash = hasher.result();
213
214        if let Ok(dir) = std::env::var("SAFECOIN_ABI_DUMP_DIR") {
215            let thread_name = std::thread::current()
216                .name()
217                .unwrap_or("unknown-test-thread")
218                .replace(':', "_");
219            if thread_name == "main" {
220                error!("Bad thread name detected for dumping; Maybe, --test-threads=1? Sorry, SAFECOIN_ABI_DUMP_DIR doesn't work under 1; increase it");
221            }
222
223            let path = format!("{}/{}_{}", dir, thread_name, hash,);
224            let mut file = std::fs::File::create(path).unwrap();
225            for buf in (*self.data_types.borrow()).iter() {
226                file.write_all(buf.as_bytes()).unwrap();
227            }
228            file.sync_data().unwrap();
229        }
230
231        hash
232    }
233}
234
235impl Serializer for AbiDigester {
236    type Ok = Self;
237    type Error = DigestError;
238    type SerializeSeq = Self;
239    type SerializeTuple = Self;
240    type SerializeTupleStruct = Self;
241    type SerializeTupleVariant = Self;
242    type SerializeMap = Self;
243    type SerializeStruct = Self;
244    type SerializeStructVariant = Self;
245
246    fn serialize_bool(self, _data: bool) -> DigestResult {
247        self.digest_primitive::<bool>()
248    }
249
250    fn serialize_i8(self, _data: i8) -> DigestResult {
251        self.digest_primitive::<i8>()
252    }
253
254    fn serialize_i16(self, _data: i16) -> DigestResult {
255        self.digest_primitive::<i16>()
256    }
257
258    fn serialize_i32(self, _data: i32) -> DigestResult {
259        self.digest_primitive::<i32>()
260    }
261
262    fn serialize_i64(self, _data: i64) -> DigestResult {
263        self.digest_primitive::<i64>()
264    }
265
266    fn serialize_i128(self, _data: i128) -> DigestResult {
267        self.digest_primitive::<i128>()
268    }
269
270    fn serialize_u8(self, _data: u8) -> DigestResult {
271        self.digest_primitive::<u8>()
272    }
273
274    fn serialize_u16(self, _data: u16) -> DigestResult {
275        self.digest_primitive::<u16>()
276    }
277
278    fn serialize_u32(self, _data: u32) -> DigestResult {
279        self.digest_primitive::<u32>()
280    }
281
282    fn serialize_u64(self, _data: u64) -> DigestResult {
283        self.digest_primitive::<u64>()
284    }
285
286    fn serialize_u128(self, _data: u128) -> DigestResult {
287        self.digest_primitive::<u128>()
288    }
289
290    fn serialize_f32(self, _data: f32) -> DigestResult {
291        self.digest_primitive::<f32>()
292    }
293
294    fn serialize_f64(self, _data: f64) -> DigestResult {
295        self.digest_primitive::<f64>()
296    }
297
298    fn serialize_char(self, _data: char) -> DigestResult {
299        self.digest_primitive::<char>()
300    }
301
302    fn serialize_str(self, _data: &str) -> DigestResult {
303        self.digest_primitive::<&str>()
304    }
305
306    fn serialize_unit(self) -> DigestResult {
307        self.digest_primitive::<()>()
308    }
309
310    fn serialize_bytes(mut self, v: &[u8]) -> DigestResult {
311        self.update_with_string(format!("bytes [u8] (len = {})", v.len()));
312        Ok(self)
313    }
314
315    fn serialize_none(self) -> DigestResult {
316        Err(DigestError::NoneIsSerialized)
317    }
318
319    fn serialize_some<T>(mut self, v: &T) -> DigestResult
320    where
321        T: ?Sized + Serialize,
322    {
323        // emulate the ABI digest for the Option enum; see TestMyOption
324        self.update(&["enum Option (variants = 2)"]);
325        let mut variant_digester = self.create_child()?;
326
327        variant_digester.update_with_string("variant(0) None (unit)".to_owned());
328        variant_digester
329            .update_with_string(format!("variant(1) Some({}) (newtype)", type_name::<T>()));
330        variant_digester.create_child()?.digest_data(v)
331    }
332
333    fn serialize_unit_struct(mut self, name: Sstr) -> DigestResult {
334        self.update(&["struct", name, "(unit)"]);
335        Ok(self)
336    }
337
338    fn serialize_unit_variant(mut self, _name: Sstr, index: u32, variant: Sstr) -> DigestResult {
339        self.check_for_enum("unit_variant", variant)?;
340        self.update_with_string(format!("variant({}) {} (unit)", index, variant));
341        Ok(self)
342    }
343
344    fn serialize_newtype_struct<T>(mut self, name: Sstr, v: &T) -> DigestResult
345    where
346        T: ?Sized + Serialize,
347    {
348        self.update_with_string(format!("struct {}({}) (newtype)", name, type_name::<T>()));
349        self.create_child()?
350            .digest_data(v)
351            .map_err(|e| DigestError::wrap_by_str(e, "newtype_struct"))
352    }
353
354    fn serialize_newtype_variant<T>(
355        mut self,
356        _name: Sstr,
357        i: u32,
358        variant: Sstr,
359        v: &T,
360    ) -> DigestResult
361    where
362        T: ?Sized + Serialize,
363    {
364        self.check_for_enum("newtype_variant", variant)?;
365        self.update_with_string(format!(
366            "variant({}) {}({}) (newtype)",
367            i,
368            variant,
369            type_name::<T>()
370        ));
371        self.create_child()?
372            .digest_data(v)
373            .map_err(|e| DigestError::wrap_by_str(e, "newtype_variant"))
374    }
375
376    fn serialize_seq(mut self, len: Option<usize>) -> DigestResult {
377        let len = len.unwrap();
378        assert_eq!(
379            len, 1,
380            "Exactly 1 seq element is needed to generate the ABI digest precisely"
381        );
382        self.update_with_string(format!("seq (elements = {})", len));
383        self.create_child()
384    }
385
386    fn serialize_tuple(mut self, len: usize) -> DigestResult {
387        self.update_with_string(format!("tuple (elements = {})", len));
388        self.create_child()
389    }
390
391    fn serialize_tuple_struct(mut self, name: Sstr, len: usize) -> DigestResult {
392        self.update_with_string(format!("struct {} (fields = {}) (tuple)", name, len));
393        self.create_child()
394    }
395
396    fn serialize_tuple_variant(
397        mut self,
398        _name: Sstr,
399        i: u32,
400        variant: Sstr,
401        len: usize,
402    ) -> DigestResult {
403        self.check_for_enum("tuple_variant", variant)?;
404        self.update_with_string(format!("variant({}) {} (fields = {})", i, variant, len));
405        self.create_child()
406    }
407
408    fn serialize_map(mut self, len: Option<usize>) -> DigestResult {
409        let len = len.unwrap();
410        assert_eq!(
411            len, 1,
412            "Exactly 1 map entry is needed to generate the ABI digest precisely"
413        );
414        self.update_with_string(format!("map (entries = {})", len));
415        self.create_child()
416    }
417
418    fn serialize_struct(mut self, name: Sstr, len: usize) -> DigestResult {
419        self.update_with_string(format!("struct {} (fields = {})", name, len));
420        self.create_child()
421    }
422
423    fn serialize_struct_variant(
424        mut self,
425        _name: Sstr,
426        i: u32,
427        variant: Sstr,
428        len: usize,
429    ) -> DigestResult {
430        self.check_for_enum("struct_variant", variant)?;
431        self.update_with_string(format!(
432            "variant({}) struct {} (fields = {})",
433            i, variant, len
434        ));
435        self.create_child()
436    }
437}
438
439impl SerializeSeq for AbiDigester {
440    type Ok = Self;
441    type Error = DigestError;
442
443    fn serialize_element<T: ?Sized + Serialize>(&mut self, data: &T) -> Result<(), DigestError> {
444        self.digest_element(data)
445    }
446
447    fn end(self) -> DigestResult {
448        Ok(self)
449    }
450}
451
452impl SerializeTuple for AbiDigester {
453    type Ok = Self;
454    type Error = DigestError;
455
456    fn serialize_element<T: ?Sized + Serialize>(&mut self, data: &T) -> Result<(), DigestError> {
457        self.digest_element(data)
458    }
459
460    fn end(self) -> DigestResult {
461        Ok(self)
462    }
463}
464impl SerializeTupleStruct for AbiDigester {
465    type Ok = Self;
466    type Error = DigestError;
467
468    fn serialize_field<T: ?Sized + Serialize>(&mut self, data: &T) -> Result<(), DigestError> {
469        self.digest_unnamed_field(data)
470    }
471
472    fn end(self) -> DigestResult {
473        Ok(self)
474    }
475}
476
477impl SerializeTupleVariant for AbiDigester {
478    type Ok = Self;
479    type Error = DigestError;
480
481    fn serialize_field<T: ?Sized + Serialize>(&mut self, data: &T) -> Result<(), DigestError> {
482        self.digest_unnamed_field(data)
483    }
484
485    fn end(self) -> DigestResult {
486        Ok(self)
487    }
488}
489
490impl SerializeMap for AbiDigester {
491    type Ok = Self;
492    type Error = DigestError;
493
494    fn serialize_key<T: ?Sized + Serialize>(&mut self, key: &T) -> Result<(), DigestError> {
495        self.update_with_type::<T>("key");
496        self.create_child()?.digest_data(key).map(|_| ())
497    }
498
499    fn serialize_value<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), DigestError> {
500        self.update_with_type::<T>("value");
501        self.create_child()?.digest_data(value).map(|_| ())
502    }
503
504    fn end(self) -> DigestResult {
505        Ok(self)
506    }
507}
508
509impl SerializeStruct for AbiDigester {
510    type Ok = Self;
511    type Error = DigestError;
512
513    fn serialize_field<T: ?Sized + Serialize>(
514        &mut self,
515        key: Sstr,
516        data: &T,
517    ) -> Result<(), DigestError> {
518        self.digest_named_field(key, data)
519    }
520
521    fn end(self) -> DigestResult {
522        Ok(self)
523    }
524}
525
526impl SerializeStructVariant for AbiDigester {
527    type Ok = Self;
528    type Error = DigestError;
529
530    fn serialize_field<T: ?Sized + Serialize>(
531        &mut self,
532        key: Sstr,
533        data: &T,
534    ) -> Result<(), DigestError> {
535        self.digest_named_field(key, data)
536    }
537
538    fn end(self) -> DigestResult {
539        Ok(self)
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use std::{collections::HashMap, sync::atomic::AtomicIsize};
546
547    #[frozen_abi(digest = "CQiGCzsGquChkwffHjZKFqa3tCYtS3GWYRRYX7iDR38Q")]
548    type TestTypeAlias = i32;
549
550    #[frozen_abi(digest = "Apwkp9Ah9zKirzwuSzVoU9QRc43EghpkD1nGVakJLfUY")]
551    #[derive(Serialize, AbiExample)]
552    struct TestStruct {
553        test_field: i8,
554        test_field2: i8,
555    }
556
557    #[frozen_abi(digest = "4LbuvQLX78XPbm4hqqZcHFHpseDJcw4qZL9EUZXSi2Ss")]
558    #[derive(Serialize, AbiExample)]
559    struct TestTupleStruct(i8, i8);
560
561    #[frozen_abi(digest = "FNHa6mNYJZa59Fwbipep5dXRXcFreaDHn9jEUZEH1YLv")]
562    #[derive(Serialize, AbiExample)]
563    struct TestNewtypeStruct(i8);
564
565    #[frozen_abi(digest = "Hbs1X2X7TF2gFEfsspwfZ1JKr8ZGbLY3uidQBebqcMYt")]
566    #[derive(Serialize, AbiExample)]
567    struct Foo<'a> {
568        #[serde(with = "serde_bytes")]
569        data1: Vec<u8>,
570        #[serde(with = "serde_bytes")]
571        data2: &'a [u8],
572        #[serde(with = "serde_bytes")]
573        data3: &'a Vec<u8>,
574    }
575
576    #[frozen_abi(digest = "5qio5qYurHDv6fq5kcwP2ue2RBEazSZF8CPk2kUuwC2j")]
577    #[derive(Serialize, AbiExample)]
578    struct TestStructReversed {
579        test_field2: i8,
580        test_field: i8,
581    }
582
583    #[frozen_abi(digest = "DLLrTWprsMjdJGR447A4mui9HpqxbKdsFXBfaWPcwhny")]
584    #[derive(Serialize, AbiExample)]
585    struct TestStructAnotherType {
586        test_field: i16,
587        test_field2: i8,
588    }
589
590    #[frozen_abi(digest = "GMeECsxg37a5qznstWXeeX3d6HXs6j12oB4SKaZZuNJk")]
591    #[derive(Serialize, AbiExample)]
592    struct TestNest {
593        nested_field: [TestStruct; 5],
594    }
595
596    #[frozen_abi(digest = "GttWH8FAY3teUjTaSds9mL3YbiDQ7qWw7WAvDXKd4ZzX")]
597    type TestUnitStruct = std::marker::PhantomData<i8>;
598
599    #[frozen_abi(digest = "6kj3mPXbzWTwZho48kZWxZjuseLU2oiqhbpqca4DmcRq")]
600    #[derive(Serialize, AbiExample, AbiEnumVisitor)]
601    enum TestEnum {
602        Variant1,
603        Variant2,
604    }
605
606    #[frozen_abi(digest = "3WqYwnbQEdu6iPZi5LJa2b5kw55hxBtZdqFqiViFCKPo")]
607    #[derive(Serialize, AbiExample, AbiEnumVisitor)]
608    enum TestTupleVariant {
609        Variant1(u8, u16),
610        Variant2(u8, u16),
611    }
612
613    #[frozen_abi(digest = "4E9gJjvKiETBeZ8dybZPAQ7maaHTHFucmLqgX2m6yrBh")]
614    #[derive(Serialize, AbiExample)]
615    struct TestVecEnum {
616        enums: Vec<TestTupleVariant>,
617    }
618
619    #[derive(Serialize, AbiExample)]
620    struct TestGenericStruct<T: Ord> {
621        test_field: T,
622    }
623
624    #[frozen_abi(digest = "2Dr5k3Z513mV4KrGeUfcMwjsVHLmVyLiZarmfnXawEbf")]
625    type TestConcreteStruct = TestGenericStruct<i64>;
626
627    #[derive(Serialize, AbiExample, AbiEnumVisitor)]
628    enum TestGenericEnum<T: serde::Serialize + Sized + Ord> {
629        TestVariant(T),
630    }
631
632    #[frozen_abi(digest = "2B2HqxHaziSfW3kdxJqV9vEMpCpRaEipXL6Bskv1GV7J")]
633    type TestConcreteEnum = TestGenericEnum<u128>;
634
635    #[frozen_abi(digest = "GyExD8nkYb9e6tijFL5S1gFtdN9GfY6L2sUDjTLhVGn4")]
636    type TestMap = HashMap<char, i128>;
637
638    #[frozen_abi(digest = "AFLTVyVBkjc1SAPnzyuwTvmie994LMhJGN7PrP7hCVwL")]
639    type TestVec = Vec<f32>;
640
641    #[frozen_abi(digest = "F5RniBQtNMBiDnyLEf72aQKHskV1TuBrD4jrEH5odPAW")]
642    type TestArray = [f64; 10];
643
644    #[frozen_abi(digest = "8cgZGpckC4dFovh3QuZpgvcvK2125ig7P4HsK9KCw39N")]
645    type TestUnit = ();
646
647    #[frozen_abi(digest = "FgnBPy2T5iNNbykMteq1M4FRpNeSkzRoi9oXeCjEW6uq")]
648    type TestResult = Result<u8, u16>;
649
650    #[frozen_abi(digest = "F5s6YyJkfz7LM56q5j9RzTLa7QX4Utx1ecNkHX5UU9Fp")]
651    type TestAtomic = AtomicIsize;
652
653    #[frozen_abi(digest = "7rH7gnEhJ8YouzqPT6VPyUDELvL51DGednSPcoLXG2rg")]
654    type TestOptionWithIsize = Option<isize>;
655
656    #[derive(Serialize, AbiExample, AbiEnumVisitor)]
657    enum TestMyOption<T: serde::Serialize + Sized + Ord> {
658        None,
659        Some(T),
660    }
661    #[frozen_abi(digest = "BzXkoRacijFTCPW4PyyvhkqMVgcuhmvPXjZfMsHJCeet")]
662    type TestMyOptionWithIsize = TestMyOption<isize>;
663
664    #[frozen_abi(digest = "9PMdHRb49BpkywrmPoJyZWMsEmf5E1xgmsFGkGmea5RW")]
665    type TestBitVec = bv::BitVec<u64>;
666
667    mod skip_should_be_same {
668        #[frozen_abi(digest = "4LbuvQLX78XPbm4hqqZcHFHpseDJcw4qZL9EUZXSi2Ss")]
669        #[derive(Serialize, AbiExample)]
670        struct TestTupleStruct(i8, i8, #[serde(skip)] i8);
671
672        #[frozen_abi(digest = "Hk7BYjZ71upWQJAx2PqoNcapggobPmFbMJd34xVdvRso")]
673        #[derive(Serialize, AbiExample)]
674        struct TestStruct {
675            test_field: i8,
676            #[serde(skip)]
677            _skipped_test_field: i8,
678        }
679
680        #[frozen_abi(digest = "6kj3mPXbzWTwZho48kZWxZjuseLU2oiqhbpqca4DmcRq")]
681        #[derive(Serialize, AbiExample, AbiEnumVisitor)]
682        enum TestEnum {
683            Variant1,
684            Variant2,
685            #[serde(skip)]
686            #[allow(dead_code)]
687            Variant3,
688        }
689
690        #[frozen_abi(digest = "3WqYwnbQEdu6iPZi5LJa2b5kw55hxBtZdqFqiViFCKPo")]
691        #[derive(Serialize, AbiExample, AbiEnumVisitor)]
692        enum TestTupleVariant {
693            Variant1(u8, u16),
694            Variant2(u8, u16, #[serde(skip)] u32),
695        }
696    }
697}