unc_abi/
lib.rs

1use borsh::schema::{
2    BorshSchemaContainer, Declaration, Definition, DiscriminantValue, Fields, VariantName,
3};
4use schemars::schema::{RootSchema, Schema};
5use schemars::JsonSchema;
6use semver::Version;
7use serde::{de, Deserialize, Deserializer, Serialize};
8use std::collections::{BTreeMap, HashMap};
9
10#[doc(hidden)]
11#[cfg(feature = "__chunked-entries")]
12#[path = "private.rs"]
13pub mod __private;
14
15// Keep in sync with SCHEMA_VERSION below.
16const SCHEMA_SEMVER: Version = Version {
17    major: 0,
18    minor: 4,
19    patch: 0,
20    pre: semver::Prerelease::EMPTY,
21    build: semver::BuildMetadata::EMPTY,
22};
23
24/// Current version of the ABI schema format.
25pub const SCHEMA_VERSION: &str = "0.4.0";
26
27/// Contract ABI.
28#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
29#[serde(deny_unknown_fields)]
30pub struct AbiRoot {
31    /// Semver of the ABI schema format.
32    #[serde(deserialize_with = "ensure_current_version")]
33    pub schema_version: String,
34    /// Metadata information about the contract.
35    pub metadata: AbiMetadata,
36    /// Core ABI information (functions and types).
37    pub body: AbiBody,
38}
39
40fn ensure_current_version<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
41    let unchecked = String::deserialize(d)?;
42    let version = Version::parse(&unchecked)
43        .map_err(|_| de::Error::custom("expected `schema_version` to be a valid semver value"))?;
44    if version.major != SCHEMA_SEMVER.major || version.minor != SCHEMA_SEMVER.minor {
45        if version < SCHEMA_SEMVER {
46            return Err(de::Error::custom(format!(
47                "expected `schema_version` to be ~{}.{}, but got {}: consider re-generating your ABI file with a newer version of SDK and cargo-unc",
48                SCHEMA_SEMVER.major, SCHEMA_SEMVER.minor, version
49            )));
50        } else {
51            return Err(de::Error::custom(format!(
52                "expected `schema_version` to be ~{}.{}, but got {}: consider upgrading unc-abi to a newer version",
53                SCHEMA_SEMVER.major, SCHEMA_SEMVER.minor, version
54            )));
55        }
56    }
57    Ok(unchecked)
58}
59
60#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default, JsonSchema)]
61pub struct BuildInfo {
62    /// The compiler (versioned) that was used to build the contract.
63    pub compiler: String,
64    /// The build tool (versioned) that was used to build the contract.
65    pub builder: String,
66    /// The docker image (versioned) where the contract was built.
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub image: Option<String>,
69}
70
71#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Default, JsonSchema)]
72pub struct AbiMetadata {
73    /// The name of the smart contract.
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub name: Option<String>,
76    /// The version of the smart contract.
77    #[serde(default, skip_serializing_if = "Option::is_none")]
78    pub version: Option<String>,
79    /// The authors of the smart contract.
80    #[serde(default, skip_serializing_if = "Vec::is_empty")]
81    pub authors: Vec<String>,
82    /// The information about how this contract was built.
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub build: Option<BuildInfo>,
85    /// The SHA-256 hash of the contract WASM code in Base58 format.
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub wasm_hash: Option<String>,
88    /// Other arbitrary metadata.
89    #[serde(default, flatten, skip_serializing_if = "HashMap::is_empty")]
90    pub other: HashMap<String, String>,
91}
92
93/// Core ABI information.
94#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
95#[serde(deny_unknown_fields)]
96pub struct AbiBody {
97    /// ABIs of all contract's functions.
98    pub functions: Vec<AbiFunction>,
99    /// Root JSON Schema containing all types referenced in the functions.
100    pub root_schema: RootSchema,
101}
102
103/// ABI of a single function.
104#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
105#[serde(deny_unknown_fields)]
106pub struct AbiFunction {
107    pub name: String,
108    /// Human-readable documentation parsed from the source file.
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub doc: Option<String>,
111    /// Function kind that regulates whether the function has to be invoked from a transaction.
112    pub kind: AbiFunctionKind,
113    /// List of modifiers affecting the function.
114    #[serde(default, skip_serializing_if = "Vec::is_empty")]
115    pub modifiers: Vec<AbiFunctionModifier>,
116    /// Type identifiers of the function parameters.
117    #[serde(default, skip_serializing_if = "AbiParameters::is_empty")]
118    pub params: AbiParameters,
119    /// Type identifiers of the callbacks of the function.
120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
121    pub callbacks: Vec<AbiType>,
122    /// Type identifier of the vararg callbacks of the function.
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub callbacks_vec: Option<AbiType>,
125    /// Return type identifier.
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub result: Option<AbiType>,
128}
129
130/// Function kind regulates whether this function's invocation requires a transaction (so-called
131/// call functions) or not (view functions).
132#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, JsonSchema)]
133#[serde(rename_all = "lowercase")]
134pub enum AbiFunctionKind {
135    View,
136    Call,
137}
138
139/// Function can have multiple modifiers that can change its semantics.
140#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, JsonSchema)]
141#[serde(rename_all = "lowercase")]
142pub enum AbiFunctionModifier {
143    /// Init functions can be used to initialize the state of the contract.
144    Init,
145    /// Private functions can only be called from the contract containing them. Usually, when a
146    /// contract has to have a callback for a remote cross-contract call, this callback method
147    /// should only be called by the contract itself.
148    Private,
149    /// Payable functions can accept token transfer together with the function call.
150    /// This is done so that contracts can define a fee in tokens that needs to be payed when
151    /// they are used.
152    Payable,
153}
154
155/// A list of function parameters sharing the same serialization type.
156#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
157#[serde(tag = "serialization_type")]
158#[serde(rename_all = "lowercase")]
159#[serde(deny_unknown_fields)]
160pub enum AbiParameters {
161    Json { args: Vec<AbiJsonParameter> },
162    Borsh { args: Vec<AbiBorshParameter> },
163}
164
165impl Default for AbiParameters {
166    fn default() -> Self {
167        // JSON was picked arbitrarily for the default value, but generally it does not matter
168        // whether this is JSON or Borsh.
169        AbiParameters::Json { args: Vec::new() }
170    }
171}
172
173impl AbiParameters {
174    pub fn is_empty(&self) -> bool {
175        match self {
176            Self::Json { args } => args.is_empty(),
177            Self::Borsh { args } => args.is_empty(),
178        }
179    }
180}
181
182/// Information about a single named JSON function parameter.
183#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, JsonSchema)]
184#[serde(deny_unknown_fields)]
185pub struct AbiJsonParameter {
186    /// Parameter name (e.g. `p1` in `fn foo(p1: u32) {}`).
187    pub name: String,
188    /// JSON Subschema that represents this type (can be an inline primitive, a reference to the root schema and a few other corner-case things).
189    pub type_schema: Schema,
190}
191
192/// Information about a single named Borsh function parameter.
193#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
194#[serde(deny_unknown_fields)]
195pub struct AbiBorshParameter {
196    /// Parameter name (e.g. `p1` in `fn foo(p1: u32) {}`).
197    pub name: String,
198    /// Inline Borsh schema that represents this type.
199    #[serde(with = "BorshSchemaContainerDef")]
200    pub type_schema: BorshSchemaContainer,
201}
202
203impl JsonSchema for AbiBorshParameter {
204    fn schema_name() -> String {
205        "AbiBorshParameter".to_string()
206    }
207
208    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> Schema {
209        let mut name_schema_object = <String as JsonSchema>::json_schema(gen).into_object();
210        name_schema_object.metadata().description =
211            Some("Parameter name (e.g. `p1` in `fn foo(p1: u32) {}`).".to_string());
212
213        let mut type_schema_object = Schema::Bool(true).into_object();
214        type_schema_object.metadata().description =
215            Some("Inline Borsh schema that represents this type.".to_string());
216
217        let mut schema_object = schemars::schema::SchemaObject {
218            instance_type: Some(schemars::schema::InstanceType::Object.into()),
219            ..Default::default()
220        };
221        schema_object.metadata().description =
222            Some("Information about a single named Borsh function parameter.".to_string());
223        let object_validation = schema_object.object();
224        object_validation
225            .properties
226            .insert("name".to_string(), name_schema_object.into());
227        object_validation
228            .properties
229            // TODO: Narrow to BorshSchemaContainer once it derives JsonSchema
230            .insert("type_schema".to_string(), type_schema_object.into());
231        object_validation.required.insert("name".to_string());
232        object_validation.required.insert("type_schema".to_string());
233        object_validation.additional_properties =
234            Some(schemars::schema::Schema::Bool(false).into());
235        schema_object.into()
236    }
237}
238
239/// Information about a single type (e.g. return type).
240#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
241#[serde(tag = "serialization_type")]
242#[serde(rename_all = "lowercase")]
243#[serde(deny_unknown_fields)]
244pub enum AbiType {
245    Json {
246        /// JSON Subschema that represents this type (can be an inline primitive, a reference to the root schema and a few other corner-case things).
247        type_schema: Schema,
248    },
249    Borsh {
250        /// Inline Borsh schema that represents this type.
251        #[serde(with = "BorshSchemaContainerDef")]
252        type_schema: BorshSchemaContainer,
253    },
254}
255
256impl JsonSchema for AbiType {
257    fn schema_name() -> String {
258        "AbiType".to_string()
259    }
260
261    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> Schema {
262        let mut json_abi_type = schemars::schema::SchemaObject::default();
263        let json_abi_schema = json_abi_type.object();
264        json_abi_schema
265            .properties
266            .insert("serialization_type".to_string(), {
267                let schema = <String as JsonSchema>::json_schema(gen);
268                let mut schema = schema.into_object();
269                schema.enum_values = Some(vec!["json".into()]);
270                schema.into()
271            });
272        json_abi_schema
273            .properties
274            .insert("type_schema".to_string(), gen.subschema_for::<Schema>());
275        json_abi_schema
276            .required
277            .insert("serialization_type".to_string());
278        json_abi_schema.required.insert("type_schema".to_string());
279        json_abi_schema.additional_properties = Some(schemars::schema::Schema::Bool(false).into());
280
281        let mut borsh_abi_type = schemars::schema::SchemaObject::default();
282        let borsh_abi_schema = borsh_abi_type.object();
283        borsh_abi_schema
284            .properties
285            .insert("serialization_type".to_string(), {
286                let schema = <String as JsonSchema>::json_schema(gen);
287                let mut schema = schema.into_object();
288                schema.enum_values = Some(vec!["borsh".into()]);
289                schema.into()
290            });
291        borsh_abi_schema
292            .properties
293            // TODO: Narrow to BorshSchemaContainer once it derives JsonSchema
294            .insert(
295                "type_schema".to_string(),
296                schemars::schema::SchemaObject::default().into(),
297            );
298        borsh_abi_schema
299            .required
300            .insert("serialization_type".to_string());
301        borsh_abi_schema.required.insert("type_schema".to_string());
302        borsh_abi_schema.additional_properties = Some(schemars::schema::Schema::Bool(false).into());
303
304        let mut schema_object = schemars::schema::SchemaObject {
305            subschemas: Some(Box::new(schemars::schema::SubschemaValidation {
306                one_of: Some(vec![
307                    json_abi_type.into(),
308                    borsh_abi_type.into(), // TODO: Narrow to BorshSchemaContainer once it derives JsonSchema
309                ]),
310                ..Default::default()
311            })),
312            ..Default::default()
313        };
314        schema_object.metadata().description =
315            Some("Information about a single type (e.g. return type).".to_string());
316        schema_object.into()
317    }
318}
319
320#[derive(Serialize, Deserialize)]
321#[serde(remote = "BorshSchemaContainer")]
322struct BorshSchemaContainerDef {
323    #[serde(getter = "borsh_serde::getters::declaration")]
324    declaration: Declaration,
325    #[serde(with = "borsh_serde", getter = "borsh_serde::getters::definitions")]
326    definitions: BTreeMap<Declaration, Definition>,
327}
328
329impl From<BorshSchemaContainerDef> for BorshSchemaContainer {
330    fn from(value: BorshSchemaContainerDef) -> Self {
331        Self::new(value.declaration, value.definitions)
332    }
333}
334
335/// This submodules follows <https://serde.rs/remote-derive.html> to derive Serialize/Deserialize for
336/// `BorshSchemaContainer` parameters. The top-level serialization type is `BTreeMap<Declaration, Definition>`
337/// for the sake of being easily plugged into `BorshSchemaContainerDef` (see its parameters).
338mod borsh_serde {
339    use super::*;
340    use serde::ser::SerializeMap;
341    use serde::{Deserializer, Serializer};
342    pub mod getters {
343        use super::*;
344
345        pub fn declaration(obj: &BorshSchemaContainer) -> &Declaration {
346            obj.declaration()
347        }
348
349        pub fn definitions(obj: &BorshSchemaContainer) -> BTreeMap<Declaration, Definition> {
350            let definitions: BTreeMap<Declaration, Definition> = obj
351                .definitions()
352                .map(|(k, v)| (k.clone(), v.clone()))
353                .collect();
354            definitions
355        }
356    }
357
358    #[derive(Serialize, Deserialize)]
359    #[serde(remote = "Definition")]
360    enum DefinitionDef {
361        Primitive(u8),
362        Sequence {
363            length_width: u8,
364            length_range: core::ops::RangeInclusive<u64>,
365            elements: Declaration,
366        },
367        #[serde(with = "transparent")]
368        Tuple {
369            elements: Vec<Declaration>,
370        },
371        Enum {
372            tag_width: u8,
373            variants: Vec<(DiscriminantValue, VariantName, Declaration)>,
374        },
375        #[serde(with = "transparent_fields")]
376        Struct {
377            fields: Fields,
378        },
379    }
380
381    #[derive(Serialize, Deserialize)]
382    struct HelperDefinition(#[serde(with = "DefinitionDef")] Definition);
383
384    /// #[serde(transparent)] does not support enum variants, so we have to use a custom ser/de impls for now.
385    /// See <https://github.com/serde-rs/serde/issues/2092>.
386    mod transparent {
387        use serde::{Deserialize, Deserializer, Serialize, Serializer};
388
389        pub fn serialize<T, S>(field: &T, serializer: S) -> Result<S::Ok, S::Error>
390        where
391            T: Serialize,
392            S: Serializer,
393        {
394            serializer.serialize_some(&field)
395        }
396
397        pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
398        where
399            T: Deserialize<'de>,
400            D: Deserializer<'de>,
401        {
402            T::deserialize(deserializer)
403        }
404    }
405
406    /// Since `Fields` itself does not implement `Serialization`/`Deserialization`, we can't use
407    /// `transparent` in combination with `#[serde(with = "...")]. Instead we have do it in this
408    /// roundabout way.
409    mod transparent_fields {
410        use borsh::schema::{Declaration, FieldName, Fields};
411        use serde::{Deserialize, Deserializer, Serialize, Serializer};
412
413        #[derive(Serialize, Deserialize)]
414        #[serde(remote = "Fields", untagged)]
415        enum FieldsDef {
416            NamedFields(Vec<(FieldName, Declaration)>),
417            UnnamedFields(Vec<Declaration>),
418            Empty,
419        }
420
421        #[derive(Serialize, Deserialize)]
422        struct HelperFields(#[serde(with = "FieldsDef")] Fields);
423
424        pub fn serialize<S>(fields: &Fields, serializer: S) -> Result<S::Ok, S::Error>
425        where
426            S: Serializer,
427        {
428            HelperFields(fields.clone()).serialize(serializer)
429        }
430
431        pub fn deserialize<'de, D>(deserializer: D) -> Result<Fields, D::Error>
432        where
433            D: Deserializer<'de>,
434        {
435            Ok(HelperFields::deserialize(deserializer)?.0)
436        }
437    }
438
439    pub fn serialize<S>(
440        map: &BTreeMap<Declaration, Definition>,
441        serializer: S,
442    ) -> Result<S::Ok, S::Error>
443    where
444        S: Serializer,
445    {
446        let mut map_ser = serializer.serialize_map(Some(map.len()))?;
447        for (k, v) in map {
448            map_ser.serialize_entry(k, &HelperDefinition(v.clone()))?;
449        }
450        map_ser.end()
451    }
452
453    pub fn deserialize<'de, D>(
454        deserializer: D,
455    ) -> Result<BTreeMap<Declaration, Definition>, D::Error>
456    where
457        D: Deserializer<'de>,
458    {
459        let map = BTreeMap::<Declaration, HelperDefinition>::deserialize(deserializer)?;
460        Ok(map
461            .into_iter()
462            .map(|(k, HelperDefinition(v))| (k, v))
463            .collect())
464    }
465}
466
467#[cfg(test)]
468mod tests {
469    use super::*;
470    use borsh::BorshSchema;
471
472    fn get_definitions(type_schema: &BorshSchemaContainer) -> BTreeMap<Declaration, Definition> {
473        let definitions: BTreeMap<Declaration, Definition> = type_schema
474            .definitions()
475            .map(|(k, v)| (k.clone(), v.clone()))
476            .collect();
477        definitions
478    }
479
480    #[test]
481    fn test_serde_abitype_borsh_array() {
482        let abi_type = AbiType::Borsh {
483            type_schema: borsh::schema_container_of::<[u32; 2]>(),
484        };
485        let expected_json_str = serde_json::to_string_pretty(&abi_type).unwrap();
486        insta::assert_snapshot!(expected_json_str);
487
488        if let AbiType::Borsh { type_schema } = serde_json::from_str(&expected_json_str).unwrap() {
489            assert_eq!(type_schema.declaration(), "[u32; 2]");
490            let definitions = get_definitions(&type_schema);
491            assert_eq!(definitions.len(), 2);
492            assert_eq!(
493                definitions.get("[u32; 2]").unwrap(),
494                &Definition::Sequence {
495                    length_width: 0,
496                    length_range: 2..=2,
497                    elements: "u32".to_string()
498                }
499            );
500
501            assert_eq!(definitions.get("u32").unwrap(), &Definition::Primitive(4));
502        } else {
503            panic!("Unexpected serialization type")
504        }
505    }
506
507    #[test]
508    fn test_serde_abitype_borsh_sequence() {
509        let abi_type = AbiType::Borsh {
510            type_schema: borsh::schema_container_of::<Vec<u32>>(),
511        };
512        let expected_json_str = serde_json::to_string_pretty(&abi_type).unwrap();
513        insta::assert_snapshot!(expected_json_str);
514
515        if let AbiType::Borsh { type_schema } = serde_json::from_str(&expected_json_str).unwrap() {
516            assert_eq!(type_schema.declaration(), "Vec<u32>");
517            let definitions = get_definitions(&type_schema);
518            assert_eq!(definitions.len(), 2);
519            assert_eq!(
520                definitions.get("Vec<u32>").unwrap(),
521                &Definition::Sequence {
522                    length_width: Definition::DEFAULT_LENGTH_WIDTH,
523                    length_range: Definition::DEFAULT_LENGTH_RANGE,
524                    elements: "u32".to_string()
525                }
526            );
527            assert_eq!(definitions.get("u32").unwrap(), &Definition::Primitive(4));
528        } else {
529            panic!("Unexpected serialization type")
530        }
531    }
532
533    #[test]
534    fn test_serde_abitype_borsh_tuple() {
535        let abi_type = AbiType::Borsh {
536            type_schema: borsh::schema_container_of::<(u32, u32)>(),
537        };
538        let expected_json_str = serde_json::to_string_pretty(&abi_type).unwrap();
539        insta::assert_snapshot!(expected_json_str);
540
541        if let AbiType::Borsh { type_schema } = serde_json::from_str(&expected_json_str).unwrap() {
542            assert_eq!(type_schema.declaration(), "(u32, u32)");
543            let definitions = get_definitions(&type_schema);
544            assert_eq!(definitions.len(), 2);
545            assert_eq!(
546                definitions.get("(u32, u32)").unwrap(),
547                &Definition::Tuple {
548                    elements: vec!["u32".to_string(), "u32".to_string()]
549                }
550            );
551            assert_eq!(definitions.get("u32").unwrap(), &Definition::Primitive(4));
552        } else {
553            panic!("Unexpected serialization type")
554        }
555    }
556
557    #[test]
558    fn test_serde_abitype_borsh_enum() {
559        #[derive(BorshSchema)]
560        enum Either {
561            _Left(u32),
562            _Right(u32),
563        }
564        let abi_type = AbiType::Borsh {
565            type_schema: borsh::schema_container_of::<Either>(),
566        };
567        let expected_json_str = serde_json::to_string_pretty(&abi_type).unwrap();
568        insta::assert_snapshot!(expected_json_str);
569
570        if let AbiType::Borsh { type_schema } = serde_json::from_str(&expected_json_str).unwrap() {
571            assert_eq!(type_schema.declaration(), "Either");
572            let definitions = get_definitions(&type_schema);
573            assert_eq!(definitions.len(), 4);
574            assert_eq!(
575                definitions.get("Either").unwrap(),
576                &Definition::Enum {
577                    tag_width: 1,
578                    variants: vec![
579                        (0, "_Left".to_string(), "Either_Left".to_string()),
580                        (1, "_Right".to_string(), "Either_Right".to_string())
581                    ]
582                }
583            );
584        } else {
585            panic!("Unexpected serialization type")
586        }
587    }
588
589    #[test]
590    fn test_serde_abitype_borsh_struct_named() {
591        #[derive(BorshSchema)]
592        struct Pair {
593            _first: u32,
594            _second: u32,
595        }
596        let abi_type = AbiType::Borsh {
597            type_schema: borsh::schema_container_of::<Pair>(),
598        };
599        let expected_json_str = serde_json::to_string_pretty(&abi_type).unwrap();
600        insta::assert_snapshot!(expected_json_str);
601
602        if let AbiType::Borsh { type_schema } = serde_json::from_str(&expected_json_str).unwrap() {
603            assert_eq!(type_schema.declaration(), "Pair");
604            let definitions = get_definitions(&type_schema);
605            assert_eq!(definitions.len(), 2);
606            assert_eq!(
607                definitions.get("Pair").unwrap(),
608                &Definition::Struct {
609                    fields: Fields::NamedFields(vec![
610                        ("_first".to_string(), "u32".to_string()),
611                        ("_second".to_string(), "u32".to_string())
612                    ])
613                }
614            );
615        } else {
616            panic!("Unexpected serialization type")
617        }
618    }
619
620    #[test]
621    fn test_serde_abitype_borsh_struct_unnamed() {
622        #[derive(BorshSchema)]
623        struct Pair(u32, u32);
624        let abi_type = AbiType::Borsh {
625            type_schema: borsh::schema_container_of::<Pair>(),
626        };
627        let expected_json_str = serde_json::to_string_pretty(&abi_type).unwrap();
628        insta::assert_snapshot!(expected_json_str);
629
630        if let AbiType::Borsh { type_schema } = serde_json::from_str(&expected_json_str).unwrap() {
631            assert_eq!(type_schema.declaration(), "Pair");
632            let definitions = get_definitions(&type_schema);
633            assert_eq!(definitions.len(), 2);
634            assert_eq!(
635                definitions.get("Pair").unwrap(),
636                &Definition::Struct {
637                    fields: Fields::UnnamedFields(vec!["u32".to_string(), "u32".to_string()])
638                }
639            );
640        } else {
641            panic!("Unexpected serialization type")
642        }
643    }
644
645    #[test]
646    fn test_serde_abitype_borsh_struct_empty() {
647        #[derive(BorshSchema)]
648        struct Unit;
649        let abi_type = AbiType::Borsh {
650            type_schema: borsh::schema_container_of::<Unit>(),
651        };
652        let expected_json_str = serde_json::to_string_pretty(&abi_type).unwrap();
653        insta::assert_snapshot!(expected_json_str);
654
655        if let AbiType::Borsh { type_schema } = serde_json::from_str(&expected_json_str).unwrap() {
656            assert_eq!(type_schema.declaration(), "Unit");
657            let definitions = get_definitions(&type_schema);
658            assert_eq!(definitions.len(), 1);
659            assert_eq!(
660                definitions.get("Unit").unwrap(),
661                &Definition::Struct {
662                    fields: Fields::Empty
663                }
664            );
665        } else {
666            panic!("Unexpected serialization type")
667        }
668    }
669
670    #[test]
671    fn test_de_error_abitype_unknown_fields() {
672        let json = r#"
673          {
674            "serialization_type": "borsh",
675            "extra": "blah-blah",
676            "type_schema": {
677              "declaration": "Unit",
678              "definitions": {
679                "Unit": {
680                  "Struct": null
681                }
682              }
683            }
684          }
685        "#;
686        serde_json::from_str::<AbiType>(json)
687            .expect_err("Expected deserialization to fail due to unknown field");
688    }
689
690    #[test]
691    fn test_serde_abiborshparameter_struct_empty() {
692        #[derive(BorshSchema)]
693        struct Unit;
694        let expected_param = AbiBorshParameter {
695            name: "foo".to_string(),
696            type_schema: borsh::schema_container_of::<Unit>(),
697        };
698
699        let expected_json_str = serde_json::to_string_pretty(&expected_param).unwrap();
700        insta::assert_snapshot!(expected_json_str);
701
702        let param = serde_json::from_str::<AbiBorshParameter>(&expected_json_str).unwrap();
703        assert_eq!(param.name, "foo");
704        assert_eq!(param.type_schema.declaration(), "Unit");
705        let definitions = get_definitions(&param.type_schema);
706        assert_eq!(definitions.len(), 1);
707        assert_eq!(
708            definitions.get("Unit").unwrap(),
709            &Definition::Struct {
710                fields: Fields::Empty
711            }
712        );
713    }
714
715    #[test]
716    fn test_de_error_abiborshparameter_unknown_fields() {
717        let json = r#"
718          {
719            "name": "foo",
720            "extra": "blah-blah",
721            "type_schema": {
722              "declaration": "Unit",
723              "definitions": {
724                "Unit": {
725                  "Struct": null
726                }
727              }
728            }
729          }
730        "#;
731        serde_json::from_str::<AbiBorshParameter>(json)
732            .expect_err("Expected deserialization to fail due to unknown field");
733    }
734
735    #[test]
736    fn test_de_abiroot_correct_version() {
737        let json = format!(
738            r#"
739            {{
740                "schema_version": "{}",
741                "metadata": {{}},
742                "body": {{
743                    "functions": [],
744                    "root_schema": {{}}
745                }}
746            }}
747            "#,
748            SCHEMA_VERSION
749        );
750        let abi_root = serde_json::from_str::<AbiRoot>(&json).unwrap();
751        assert_eq!(abi_root.schema_version, SCHEMA_VERSION);
752    }
753
754    #[test]
755    fn test_de_error_abiroot_older_version() {
756        let json = r#"
757          {
758            "schema_version": "0.0.1",
759            "metadata": {},
760            "body": {
761                "functions": [],
762                "root_schema": {}
763            }
764          }
765        "#;
766        let err = serde_json::from_str::<AbiRoot>(json)
767            .expect_err("Expected deserialization to fail due to schema version mismatch");
768        assert!(err.to_string().contains(
769            "got 0.0.1: consider re-generating your ABI file with a newer version of SDK and cargo-unc"
770        ));
771    }
772
773    #[test]
774    fn test_de_error_abiroot_newer_version() {
775        let json = r#"
776          {
777            "schema_version": "99.99.99",
778            "metadata": {},
779            "body": {
780                "functions": [],
781                "root_schema": {}
782            }
783          }
784        "#;
785        let err = serde_json::from_str::<AbiRoot>(json)
786            .expect_err("Expected deserialization to fail due to schema version mismatch");
787        assert!(err
788            .to_string()
789            .contains("got 99.99.99: consider upgrading unc-abi to a newer version"));
790    }
791}