Skip to content

Commit

Permalink
ABI decode fix, type desciption from json
Browse files Browse the repository at this point in the history
  • Loading branch information
andrei-marinica committed Nov 20, 2023
1 parent 6b125fc commit cb5ba4e
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 14 deletions.
30 changes: 29 additions & 1 deletion contracts/feature-tests/abi-tester/tests/abi_tester_abi_test.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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")
}
}
21 changes: 17 additions & 4 deletions framework/base/src/abi/type_description.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use alloc::{string::String, vec::Vec};
use alloc::{
string::{String, ToString},
vec::Vec,
};

#[derive(Clone, Debug)]
pub struct TypeDescription {
Expand Down Expand Up @@ -33,21 +36,31 @@ impl TypeContents {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnumVariantDescription {
pub docs: &'static [&'static str],
pub name: &'static str,
pub discriminant: usize,
pub fields: Vec<StructFieldDescription>,
}

#[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.
Expand Down
10 changes: 5 additions & 5 deletions framework/derive/src/type_abi_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
1 change: 1 addition & 0 deletions framework/meta/src/abi_json/build_info_abi_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
16 changes: 16 additions & 0 deletions framework/meta/src/abi_json/contract_abi_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ pub struct ContractAbiJson {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub build_info: Option<BuildInfoAbiJson>,

#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub docs: Vec<String>,

pub name: String,

#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub constructor: Option<ConstructorAbiJson>,

pub endpoints: Vec<EndpointAbiJson>,

#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub promises_callback_names: Vec<String>,

#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub events: Vec<EventAbiJson>,

pub esdt_attributes: Vec<EsdtAttributeJson>,
pub has_callback: bool,
pub types: BTreeMap<String, TypeDescriptionJson>,
Expand Down Expand Up @@ -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<ContractAbiJson, String> {
serde_json::from_str(input).map_err(|err| err.to_string())
}
21 changes: 21 additions & 0 deletions framework/meta/src/abi_json/endpoint_abi_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>,
}
Expand All @@ -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<bool>,
}
Expand All @@ -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<String>,
#[serde(rename = "payableInTokens")]
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub payable_in_tokens: Vec<String>,
pub inputs: Vec<InputAbiJson>,
Expand All @@ -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<String>,
pub name: String,

#[serde(rename = "onlyOwner")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub only_owner: Option<bool>,

#[serde(rename = "onlyAdmin")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub only_admin: Option<bool>,

pub mutability: EndpointMutabilityAbiJson,

#[serde(rename = "payableInTokens")]
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub payable_in_tokens: Vec<String>,

pub inputs: Vec<InputAbiJson>,

pub outputs: Vec<OutputAbiJson>,

#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub labels: Vec<String>,

#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_multiple_var_args: Option<bool>,
}
Expand Down
1 change: 1 addition & 0 deletions framework/meta/src/abi_json/esdt_attribute_abi_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, TypeDescriptionJson>,
}
Expand Down
4 changes: 4 additions & 0 deletions framework/meta/src/abi_json/event_abi_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>,
}
Expand All @@ -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<String>,
pub identifier: String,
Expand Down
74 changes: 70 additions & 4 deletions framework/meta/src/abi_json/type_abi_json.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
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<String>,

#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub variants: Vec<EnumVariantDescriptionJson>,

#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<StructFieldDescriptionJson>,
}

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(),
Expand Down Expand Up @@ -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<String>,

pub name: String,

#[serde(rename = "type")]
pub field_type: String,
}
Expand All @@ -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<String>,

pub name: String,

#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub discriminant: Option<usize>,

#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<StructFieldDescriptionJson>,
}
Expand Down Expand Up @@ -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(),
}
}
}

0 comments on commit cb5ba4e

Please sign in to comment.