From cb5ba4e0c98ae644cff5716b0436bca6b55096e0 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Mon, 20 Nov 2023 03:12:32 +0200 Subject: [PATCH] ABI decode fix, type desciption from json --- .../abi-tester/tests/abi_tester_abi_test.rs | 30 +++++++- framework/base/src/abi/type_description.rs | 21 +++++- framework/derive/src/type_abi_derive.rs | 10 +-- .../meta/src/abi_json/build_info_abi_json.rs | 1 + .../meta/src/abi_json/contract_abi_json.rs | 16 ++++ .../meta/src/abi_json/endpoint_abi_json.rs | 21 ++++++ .../src/abi_json/esdt_attribute_abi_json.rs | 1 + framework/meta/src/abi_json/event_abi_json.rs | 4 + framework/meta/src/abi_json/type_abi_json.rs | 74 ++++++++++++++++++- 9 files changed, 164 insertions(+), 14 deletions(-) diff --git a/contracts/feature-tests/abi-tester/tests/abi_tester_abi_test.rs b/contracts/feature-tests/abi-tester/tests/abi_tester_abi_test.rs index 85716a7577..c66b2e4660 100644 --- a/contracts/feature-tests/abi-tester/tests/abi_tester_abi_test.rs +++ b/contracts/feature-tests/abi-tester/tests/abi_tester_abi_test.rs @@ -1,6 +1,9 @@ use std::{fs, fs::File, io::Write}; -use multiversx_sc::contract_base::ContractAbiProvider; +use multiversx_sc::{ + abi::{EnumVariantDescription, TypeContents}, + contract_base::ContractAbiProvider, +}; use multiversx_sc_meta::{ abi_json::{self, EsdtAttributeAbiJson}, esdt_attr_file_json::serialize_esdt_attribute_json, @@ -82,3 +85,28 @@ fn check_multi_contract_config() { vec!["external_view", "payable_any_token", "label_a"] ); } + +#[test] +fn abi_deserialization_check() { + let main_json = fs::read_to_string("./abi_tester_expected_main.abi.json").unwrap(); + let main_abi = multiversx_sc_meta::abi_json::deserialize_abi_from_json(&main_json).unwrap(); + let abi_enum_type = main_abi + .types + .get("AbiEnum") + .unwrap() + .to_type_description("AbiEnum"); + if let TypeContents::Enum(variants) = abi_enum_type.contents { + assert_eq!(variants.len(), 4); + assert_eq!( + variants[0], + EnumVariantDescription { + docs: &[], + name: "", + discriminant: 0, + fields: vec![] + } + ); + } else { + panic!("wrong AbiEnum type contents") + } +} diff --git a/framework/base/src/abi/type_description.rs b/framework/base/src/abi/type_description.rs index 30baaa9e9c..c763fe3e48 100644 --- a/framework/base/src/abi/type_description.rs +++ b/framework/base/src/abi/type_description.rs @@ -1,4 +1,7 @@ -use alloc::{string::String, vec::Vec}; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; #[derive(Clone, Debug)] pub struct TypeDescription { @@ -33,7 +36,7 @@ impl TypeContents { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct EnumVariantDescription { pub docs: &'static [&'static str], pub name: &'static str, @@ -41,13 +44,23 @@ pub struct EnumVariantDescription { pub fields: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct StructFieldDescription { pub docs: &'static [&'static str], - pub name: &'static str, + pub name: String, pub field_type: String, } +impl StructFieldDescription { + pub fn new(docs: &'static [&'static str], name: &str, field_type: String) -> Self { + Self { + docs, + name: name.to_string(), + field_type, + } + } +} + /// An explicit enum is an enum that gets serialized by name instead of discriminant. /// /// This makes it easier for humans to read readable in the transaction output. diff --git a/framework/derive/src/type_abi_derive.rs b/framework/derive/src/type_abi_derive.rs index a7a17d4c1f..f927001525 100644 --- a/framework/derive/src/type_abi_derive.rs +++ b/framework/derive/src/type_abi_derive.rs @@ -11,11 +11,11 @@ fn field_snippet(index: usize, field: &syn::Field) -> proc_macro2::TokenStream { }; let field_ty = &field.ty; quote! { - field_descriptions.push(multiversx_sc::abi::StructFieldDescription { - docs: &[ #(#field_docs),* ], - name: #field_name_str, - field_type: <#field_ty>::type_name(), - }); + field_descriptions.push(multiversx_sc::abi::StructFieldDescription::new( + &[ #(#field_docs),* ], + #field_name_str, + <#field_ty>::type_name(), + )); <#field_ty>::provide_type_descriptions(accumulator); } } diff --git a/framework/meta/src/abi_json/build_info_abi_json.rs b/framework/meta/src/abi_json/build_info_abi_json.rs index 92643713bb..fb79f1cbb6 100644 --- a/framework/meta/src/abi_json/build_info_abi_json.rs +++ b/framework/meta/src/abi_json/build_info_abi_json.rs @@ -47,6 +47,7 @@ impl RustcAbiJson { pub struct ContractCrateBuildAbiJson { pub name: String, pub version: String, + #[serde(default)] #[serde(skip_serializing_if = "String::is_empty")] pub git_version: String, } diff --git a/framework/meta/src/abi_json/contract_abi_json.rs b/framework/meta/src/abi_json/contract_abi_json.rs index 51b1f00ded..a365aaa815 100644 --- a/framework/meta/src/abi_json/contract_abi_json.rs +++ b/framework/meta/src/abi_json/contract_abi_json.rs @@ -9,15 +9,27 @@ pub struct ContractAbiJson { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub build_info: Option, + + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub docs: Vec, + pub name: String, + + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub constructor: Option, + pub endpoints: Vec, + + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub promises_callback_names: Vec, + + #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty")] pub events: Vec, + pub esdt_attributes: Vec, pub has_callback: bool, pub types: BTreeMap, @@ -72,3 +84,7 @@ pub fn serialize_abi_to_json(abi_json: &ContractAbiJson) -> String { serialized.push('\n'); serialized } + +pub fn deserialize_abi_from_json(input: &str) -> Result { + serde_json::from_str(input).map_err(|err| err.to_string()) +} diff --git a/framework/meta/src/abi_json/endpoint_abi_json.rs b/framework/meta/src/abi_json/endpoint_abi_json.rs index 41a0addd77..ca8f35079e 100644 --- a/framework/meta/src/abi_json/endpoint_abi_json.rs +++ b/framework/meta/src/abi_json/endpoint_abi_json.rs @@ -5,9 +5,12 @@ use serde::{Deserialize, Serialize}; pub struct InputAbiJson { #[serde(rename = "name")] pub arg_name: String, + #[serde(rename = "type")] pub type_name: String, + /// Bool that is only serialized when true + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub multi_arg: Option, } @@ -25,11 +28,13 @@ impl From<&InputAbi> for InputAbiJson { #[derive(Serialize, Deserialize)] pub struct OutputAbiJson { #[serde(rename = "name")] + #[serde(default)] #[serde(skip_serializing_if = "String::is_empty")] pub output_name: String, #[serde(rename = "type")] pub type_name: String, /// Bool that is only serialized when true + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub multi_result: Option, } @@ -55,9 +60,11 @@ pub enum EndpointMutabilityAbiJson { /// Same as EndpointAbiJson but ignores the name #[derive(Serialize, Deserialize)] pub struct ConstructorAbiJson { + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub docs: Vec, #[serde(rename = "payableInTokens")] + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub payable_in_tokens: Vec, pub inputs: Vec, @@ -81,23 +88,37 @@ impl From<&EndpointAbi> for ConstructorAbiJson { #[derive(Serialize, Deserialize)] pub struct EndpointAbiJson { + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub docs: Vec, pub name: String, + #[serde(rename = "onlyOwner")] + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub only_owner: Option, + #[serde(rename = "onlyAdmin")] + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub only_admin: Option, + pub mutability: EndpointMutabilityAbiJson, + #[serde(rename = "payableInTokens")] + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub payable_in_tokens: Vec, + pub inputs: Vec, + pub outputs: Vec, + + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub labels: Vec, + + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub allow_multiple_var_args: Option, } diff --git a/framework/meta/src/abi_json/esdt_attribute_abi_json.rs b/framework/meta/src/abi_json/esdt_attribute_abi_json.rs index 27fedfa63d..b6d3e1b278 100644 --- a/framework/meta/src/abi_json/esdt_attribute_abi_json.rs +++ b/framework/meta/src/abi_json/esdt_attribute_abi_json.rs @@ -11,6 +11,7 @@ use super::{convert_type_descriptions_to_json, EsdtAttributeJson, TypeDescriptio pub struct EsdtAttributeAbiJson { pub esdt_attribute: EsdtAttributeJson, + #[serde(default)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub types: BTreeMap, } diff --git a/framework/meta/src/abi_json/event_abi_json.rs b/framework/meta/src/abi_json/event_abi_json.rs index 562c337320..ddde69b8ab 100644 --- a/framework/meta/src/abi_json/event_abi_json.rs +++ b/framework/meta/src/abi_json/event_abi_json.rs @@ -5,9 +5,12 @@ use serde::{Deserialize, Serialize}; pub struct EventInputAbiJson { #[serde(rename = "name")] pub arg_name: String, + #[serde(rename = "type")] pub type_name: String, + /// Bool that is only serialized when true + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub indexed: Option, } @@ -24,6 +27,7 @@ impl From<&EventInputAbi> for EventInputAbiJson { #[derive(Serialize, Deserialize)] pub struct EventAbiJson { + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub docs: Vec, pub identifier: String, diff --git a/framework/meta/src/abi_json/type_abi_json.rs b/framework/meta/src/abi_json/type_abi_json.rs index fb1e0f8ba3..3d0bc22cc2 100644 --- a/framework/meta/src/abi_json/type_abi_json.rs +++ b/framework/meta/src/abi_json/type_abi_json.rs @@ -1,17 +1,25 @@ use multiversx_sc::abi::*; use serde::{Deserialize, Serialize}; +pub const TYPE_DESCRIPTION_JSON_TYPE_NOT_SPECIFIED: &str = "not-specified"; +pub const TYPE_DESCRIPTION_JSON_TYPE_ENUM: &str = "enum"; +pub const TYPE_DESCRIPTION_JSON_TYPE_EXPLICIT_ENUM: &str = "explicit-enum"; +pub const TYPE_DESCRIPTION_JSON_TYPE_STRUCT: &str = "struct"; + #[derive(Serialize, Deserialize)] pub struct TypeDescriptionJson { #[serde(rename = "type")] pub content_type: String, + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub docs: Vec, + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub variants: Vec, + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub fields: Vec, } @@ -19,10 +27,10 @@ pub struct TypeDescriptionJson { impl From<&TypeDescription> for TypeDescriptionJson { fn from(abi: &TypeDescription) -> Self { let content_type = match &abi.contents { - TypeContents::NotSpecified => "not-specified", - TypeContents::Enum(_) => "enum", - TypeContents::Struct(_) => "struct", - TypeContents::ExplicitEnum(_) => "explicit-enum", + TypeContents::NotSpecified => TYPE_DESCRIPTION_JSON_TYPE_NOT_SPECIFIED, + TypeContents::Enum(_) => TYPE_DESCRIPTION_JSON_TYPE_ENUM, + TypeContents::ExplicitEnum(_) => TYPE_DESCRIPTION_JSON_TYPE_EXPLICIT_ENUM, + TypeContents::Struct(_) => TYPE_DESCRIPTION_JSON_TYPE_STRUCT, }; let mut type_desc_json = TypeDescriptionJson { content_type: content_type.to_string(), @@ -59,11 +67,38 @@ impl From<&TypeDescription> for TypeDescriptionJson { } } +impl TypeDescriptionJson { + pub fn to_type_description(&self, name: &str) -> TypeDescription { + TypeDescription { + docs: &[], + name: name.to_string(), + contents: match self.content_type.as_str() { + TYPE_DESCRIPTION_JSON_TYPE_STRUCT => TypeContents::Struct( + self.fields + .iter() + .map(StructFieldDescriptionJson::to_struct_field_description) + .collect(), + ), + TYPE_DESCRIPTION_JSON_TYPE_ENUM => TypeContents::Enum( + self.variants + .iter() + .map(EnumVariantDescriptionJson::to_enum_variant_description) + .collect(), + ), + _ => TypeContents::NotSpecified, + }, + } + } +} + #[derive(Serialize, Deserialize)] pub struct StructFieldDescriptionJson { + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub docs: Vec, + pub name: String, + #[serde(rename = "type")] pub field_type: String, } @@ -78,13 +113,29 @@ impl From<&StructFieldDescription> for StructFieldDescriptionJson { } } +impl StructFieldDescriptionJson { + pub fn to_struct_field_description(&self) -> StructFieldDescription { + StructFieldDescription { + docs: &[], + name: self.name.clone(), + field_type: self.field_type.clone(), + } + } +} + #[derive(Serialize, Deserialize)] pub struct EnumVariantDescriptionJson { + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub docs: Vec, + pub name: String, + + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub discriminant: Option, + + #[serde(default)] #[serde(skip_serializing_if = "Vec::is_empty")] pub fields: Vec, } @@ -114,3 +165,18 @@ impl From<&ExplicitEnumVariantDescription> for EnumVariantDescriptionJson { } } } + +impl EnumVariantDescriptionJson { + pub fn to_enum_variant_description(&self) -> EnumVariantDescription { + EnumVariantDescription { + docs: &[], + discriminant: self.discriminant.unwrap_or_default(), + name: "", + fields: self + .fields + .iter() + .map(StructFieldDescriptionJson::to_struct_field_description) + .collect(), + } + } +}