diff --git a/src/abi/full_program.rs b/src/abi/full_program.rs index d3a531b..c7c6ad9 100644 --- a/src/abi/full_program.rs +++ b/src/abi/full_program.rs @@ -1,28 +1,30 @@ use std::collections::HashMap; -use crate::{ - abi::program::{ - ABIFunction, Attribute, Configurable, LoggedType, ProgramABI, TypeApplication, - TypeDeclaration, - }, - utils::extract_custom_type_name, -}; +use crate::{abi::program::Attribute, utils::extract_custom_type_name}; use crate::{ error::{error, Error, Result}, utils::TypePath, }; -use super::program::Version; +use super::{ + program::Version, + unified_program::{ + UnifiedABIFunction, UnifiedConfigurable, UnifiedLoggedType, UnifiedProgramABI, + UnifiedTypeApplication, UnifiedTypeDeclaration, + }, +}; /// 'Full' versions of the ABI structures are needed to simplify duplicate -/// detection later on. The original ones([`ProgramABI`], [`TypeApplication`], -/// [`TypeDeclaration`] and others) are not suited for this due to their use of +/// detection later on. The original ones([`UnifiedProgramABI`], [`UnifiedTypeApplication`], +/// [`UnifiedTypeDeclaration`] and others) are not suited for this due to their use of /// ids, which might differ between contracts even though the type they /// represent is virtually the same. #[derive(Debug, Clone)] pub struct FullProgramABI { - pub encoding: Option, + pub program_type: String, + pub spec_version: Version, + pub encoding_version: Version, pub types: Vec, pub functions: Vec, pub logged_types: Vec, @@ -31,37 +33,37 @@ pub struct FullProgramABI { impl FullProgramABI { pub fn from_json_abi(abi: &str) -> Result { - let parsed_abi: ProgramABI = serde_json::from_str(abi)?; - FullProgramABI::from_counterpart(&parsed_abi) + let unified_program_abi = UnifiedProgramABI::from_json_abi(abi)?; + FullProgramABI::from_counterpart(&unified_program_abi) } - fn from_counterpart(program_abi: &ProgramABI) -> Result { - let lookup: HashMap<_, _> = program_abi + fn from_counterpart(unified_program_abi: &UnifiedProgramABI) -> Result { + let lookup: HashMap<_, _> = unified_program_abi .types .iter() - .map(|ttype| (ttype.type_id.clone(), ttype.clone())) + .map(|ttype| (ttype.type_id, ttype.clone())) .collect(); - let types = program_abi + let types = unified_program_abi .types .iter() .map(|ttype| FullTypeDeclaration::from_counterpart(ttype, &lookup)) .collect(); - let functions = program_abi + let functions = unified_program_abi .functions .iter() .map(|fun| FullABIFunction::from_counterpart(fun, &lookup)) .collect::>>()?; - let logged_types = program_abi + let logged_types = unified_program_abi .logged_types .iter() .flatten() .map(|logged_type| FullLoggedType::from_counterpart(logged_type, &lookup)) .collect(); - let configurables = program_abi + let configurables = unified_program_abi .configurables .iter() .flatten() @@ -69,7 +71,9 @@ impl FullProgramABI { .collect(); Ok(Self { - encoding: program_abi.encoding.clone(), + program_type: unified_program_abi.program_type.clone(), + spec_version: unified_program_abi.spec_version.clone(), + encoding_version: unified_program_abi.encoding_version.clone(), types, functions, logged_types, @@ -136,8 +140,8 @@ impl FullABIFunction { } pub fn from_counterpart( - abi_function: &ABIFunction, - types: &HashMap, + abi_function: &UnifiedABIFunction, + types: &HashMap, ) -> Result { let inputs = abi_function .inputs @@ -167,8 +171,8 @@ pub struct FullTypeDeclaration { impl FullTypeDeclaration { pub fn from_counterpart( - type_decl: &TypeDeclaration, - types: &HashMap, + type_decl: &UnifiedTypeDeclaration, + types: &HashMap, ) -> FullTypeDeclaration { let components = type_decl .components @@ -209,8 +213,8 @@ pub struct FullTypeApplication { impl FullTypeApplication { pub fn from_counterpart( - type_application: &TypeApplication, - types: &HashMap, + type_application: &UnifiedTypeApplication, + types: &HashMap, ) -> FullTypeApplication { let type_arguments = type_application .type_arguments @@ -241,8 +245,8 @@ pub struct FullLoggedType { impl FullLoggedType { fn from_counterpart( - logged_type: &LoggedType, - types: &HashMap, + logged_type: &UnifiedLoggedType, + types: &HashMap, ) -> FullLoggedType { FullLoggedType { log_id: logged_type.log_id.clone(), @@ -260,8 +264,8 @@ pub struct FullConfigurable { impl FullConfigurable { pub fn from_counterpart( - configurable: &Configurable, - types: &HashMap, + configurable: &UnifiedConfigurable, + types: &HashMap, ) -> FullConfigurable { FullConfigurable { name: configurable.name.clone(), @@ -311,34 +315,30 @@ mod tests { #[test] fn can_convert_into_full_type_decl() { // given - let type_0 = TypeDeclaration { - type_id: "9da470e78078ef5bf7aabdd59e465abbd0b288fb01443a5777c8bcf6488a747b".to_string(), + let type_0 = UnifiedTypeDeclaration { + type_id: 0, type_field: "type_0".to_string(), - components: Some(vec![TypeApplication { + components: Some(vec![UnifiedTypeApplication { name: "type_0_component_a".to_string(), - type_id: "0bb3a6b090834070f46e91d3e25184ec5d701976dc5cebe3d1fc121948231fb0" - .to_string(), - type_arguments: Some(vec![TypeApplication { + type_id: 1, + type_arguments: Some(vec![UnifiedTypeApplication { name: "type_0_type_arg_0".to_string(), - type_id: "00b4c853d51a6da239f08800898a6513eaa6950018fb89def110627830eb323f" - .to_string(), + type_id: 2, type_arguments: None, }]), }]), - type_parameters: Some(vec![ - "00b4c853d51a6da239f08800898a6513eaa6950018fb89def110627830eb323f".to_string(), - ]), + type_parameters: Some(vec![2]), }; - let type_1 = TypeDeclaration { - type_id: "0bb3a6b090834070f46e91d3e25184ec5d701976dc5cebe3d1fc121948231fb0".to_string(), + let type_1 = UnifiedTypeDeclaration { + type_id: 1, type_field: "type_1".to_string(), components: None, type_parameters: None, }; - let type_2 = TypeDeclaration { - type_id: "00b4c853d51a6da239f08800898a6513eaa6950018fb89def110627830eb323f".to_string(), + let type_2 = UnifiedTypeDeclaration { + type_id: 2, type_field: "type_2".to_string(), components: None, type_parameters: None, @@ -346,7 +346,7 @@ mod tests { let types = [&type_0, &type_1, &type_2] .iter() - .map(|&ttype| (ttype.type_id.clone(), ttype.clone())) + .map(|&ttype| (ttype.type_id, ttype.clone())) .collect::>(); // when @@ -382,26 +382,25 @@ mod tests { #[test] fn can_convert_into_full_type_appl() { - let application = TypeApplication { + let application = UnifiedTypeApplication { name: "ta_0".to_string(), - type_id: "9da470e78078ef5bf7aabdd59e465abbd0b288fb01443a5777c8bcf6488a747b".to_string(), - type_arguments: Some(vec![TypeApplication { + type_id: 0, + type_arguments: Some(vec![UnifiedTypeApplication { name: "ta_1".to_string(), - type_id: "0bb3a6b090834070f46e91d3e25184ec5d701976dc5cebe3d1fc121948231fb0" - .to_string(), + type_id: 1, type_arguments: None, }]), }; - let type_0 = TypeDeclaration { - type_id: "9da470e78078ef5bf7aabdd59e465abbd0b288fb01443a5777c8bcf6488a747b".to_string(), + let type_0 = UnifiedTypeDeclaration { + type_id: 0, type_field: "type_0".to_string(), components: None, type_parameters: None, }; - let type_1 = TypeDeclaration { - type_id: "0bb3a6b090834070f46e91d3e25184ec5d701976dc5cebe3d1fc121948231fb0".to_string(), + let type_1 = UnifiedTypeDeclaration { + type_id: 1, type_field: "type_1".to_string(), components: None, type_parameters: None, @@ -409,7 +408,7 @@ mod tests { let types = [&type_0, &type_1] .into_iter() - .map(|ttype| (ttype.type_id.clone(), ttype.clone())) + .map(|ttype| (ttype.type_id, ttype.clone())) .collect::>(); // given diff --git a/src/abi/mod.rs b/src/abi/mod.rs index 9d980b1..4a23872 100644 --- a/src/abi/mod.rs +++ b/src/abi/mod.rs @@ -1,2 +1,3 @@ pub mod full_program; pub mod program; +pub mod unified_program; diff --git a/src/abi/program.rs b/src/abi/program.rs index b74a9e8..475bbb2 100644 --- a/src/abi/program.rs +++ b/src/abi/program.rs @@ -12,10 +12,9 @@ use serde::{Deserialize, Serialize}; pub struct ProgramABI { pub program_type: String, pub spec_version: Version, - pub abi_version: Version, - #[serde(skip_serializing_if = "Option::is_none")] - pub encoding: Option, - pub types: Vec, + pub encoding_version: Version, + pub concrete_types: Vec, + pub metadata_types: Vec, pub functions: Vec, pub logged_types: Option>, pub messages_types: Option>, @@ -49,12 +48,37 @@ impl Version { } } +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct ConcreteTypeId(pub String); + +impl From<&str> for ConcreteTypeId { + fn from(value: &str) -> Self { + ConcreteTypeId(value.into()) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub struct MetadataTypeId(pub usize); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[serde(untagged)] +pub enum TypeId { + Concrete(ConcreteTypeId), + Metadata(MetadataTypeId), +} + +impl Default for TypeId { + fn default() -> Self { + TypeId::Metadata(MetadataTypeId(usize::MAX)) + } +} + #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ABIFunction { - pub inputs: Vec, + pub inputs: Vec, pub name: String, - pub output: TypeApplication, + pub output: ConcreteTypeId, pub attributes: Option>, } @@ -69,20 +93,41 @@ impl ABIFunction { #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct TypeDeclaration { - pub type_id: String, +pub struct TypeMetadataDeclaration { #[serde(rename = "type")] pub type_field: String, + pub metadata_type_id: MetadataTypeId, + #[serde(skip_serializing_if = "Option::is_none")] pub components: Option>, // Used for custom types - pub type_parameters: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_parameters: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TypeConcreteDeclaration { + #[serde(rename = "type")] + pub type_field: String, + pub concrete_type_id: ConcreteTypeId, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata_type_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub type_arguments: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TypeConcreteParameter { + pub name: String, + pub concrete_type_id: ConcreteTypeId, } #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TypeApplication { pub name: String, - #[serde(rename = "type")] - pub type_id: String, + pub type_id: TypeId, + #[serde(skip_serializing_if = "Option::is_none")] pub type_arguments: Option>, } @@ -90,24 +135,21 @@ pub struct TypeApplication { #[serde(rename_all = "camelCase")] pub struct LoggedType { pub log_id: String, - #[serde(rename = "loggedType")] - pub application: TypeApplication, + pub concrete_type_id: ConcreteTypeId, } #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MessageType { - pub message_id: u64, - #[serde(rename = "messageType")] - pub application: TypeApplication, + pub message_id: String, + pub concrete_type_id: ConcreteTypeId, } #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Configurable { pub name: String, - #[serde(rename = "configurableType")] - pub application: TypeApplication, + pub concrete_type_id: ConcreteTypeId, pub offset: u64, } diff --git a/src/abi/unified_program.rs b/src/abi/unified_program.rs new file mode 100644 index 0000000..c0bb63d --- /dev/null +++ b/src/abi/unified_program.rs @@ -0,0 +1,425 @@ +use std::collections::HashMap; + +use crate::{ + abi::program::{ + ABIFunction, Attribute, Configurable, LoggedType, ProgramABI, TypeApplication, + TypeConcreteDeclaration, TypeMetadataDeclaration, + }, + utils::extract_custom_type_name, +}; + +use crate::{ + error::{error, Result}, + utils::TypePath, +}; + +use super::program::{self, ConcreteTypeId, MessageType, TypeId, Version}; + +/// 'Unified' versions of the ABI structures removes concrete types and types metadata and unifies them under a single types declarations array. +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct UnifiedProgramABI { + pub program_type: String, + pub spec_version: Version, + pub encoding_version: Version, + pub types: Vec, + pub functions: Vec, + pub logged_types: Option>, + pub configurables: Option>, + pub messages_types: Option>, +} + +impl UnifiedProgramABI { + pub fn from_json_abi(abi: &str) -> Result { + let parsed_abi: ProgramABI = serde_json::from_str(abi)?; + UnifiedProgramABI::from_counterpart(&parsed_abi) + } + + pub fn from_counterpart(program_abi: &ProgramABI) -> Result { + let mut extended_concrete_types = program_abi.concrete_types.clone(); + let mut extended_metadata_types = program_abi.metadata_types.clone(); + let mut next_metadata_type_id = extended_metadata_types + .iter() + .map(|v| v.metadata_type_id.0) + .max() + .unwrap_or(0) + + 1; + + // Ensure every concrete type has an associated type metadata. + for concrete_type_decl in extended_concrete_types.iter_mut() { + if concrete_type_decl.metadata_type_id.is_none() { + extended_metadata_types.push(TypeMetadataDeclaration { + type_field: concrete_type_decl.type_field.clone(), + metadata_type_id: program::MetadataTypeId(next_metadata_type_id), + components: None, + type_parameters: None, + }); + concrete_type_decl.metadata_type_id = + Some(program::MetadataTypeId(next_metadata_type_id)); + next_metadata_type_id += 1; + } + } + + let concrete_types_lookup: HashMap<_, _> = extended_concrete_types + .iter() + .map(|ttype| (ttype.concrete_type_id.clone(), ttype.clone())) + .collect(); + + let types = extended_metadata_types + .iter() + .map(|ttype| UnifiedTypeDeclaration::from_counterpart(ttype, &concrete_types_lookup)) + .collect(); + + let functions = program_abi + .functions + .iter() + .map(|fun| UnifiedABIFunction::from_counterpart(fun, &concrete_types_lookup)) + .collect::>>()?; + + let logged_types: Vec = program_abi + .logged_types + .iter() + .flatten() + .map(|logged_type| { + UnifiedLoggedType::from_counterpart(logged_type, &concrete_types_lookup) + }) + .collect(); + + let configurables: Vec = program_abi + .configurables + .iter() + .flatten() + .map(|configurable| { + UnifiedConfigurable::from_counterpart(configurable, &concrete_types_lookup) + }) + .collect(); + + let messages_types: Vec = program_abi + .messages_types + .iter() + .flatten() + .map(|message_types| { + UnifiedMessageType::from_counterpart(message_types, &concrete_types_lookup) + }) + .collect(); + + Ok(Self { + program_type: program_abi.program_type.clone(), + spec_version: program_abi.spec_version.clone(), + encoding_version: program_abi.encoding_version.clone(), + types, + functions, + logged_types: if logged_types.is_empty() { + None + } else { + Some(logged_types) + }, + configurables: if configurables.is_empty() { + None + } else { + Some(configurables) + }, + messages_types: if messages_types.is_empty() { + None + } else { + Some(messages_types) + }, + }) + } +} + +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct UnifiedABIFunction { + pub name: String, + pub inputs: Vec, + pub output: UnifiedTypeApplication, + pub attributes: Option>, +} + +impl UnifiedABIFunction { + pub fn new( + name: String, + inputs: Vec, + output: UnifiedTypeApplication, + attributes: Vec, + ) -> Result { + if name.is_empty() { + Err(error!("UnifiedABIFunction's name cannot be empty!")) + } else { + Ok(Self { + name, + inputs, + output, + attributes: Some(attributes), + }) + } + } + + pub fn from_counterpart( + abi_function: &ABIFunction, + concrete_types_lookup: &HashMap, + ) -> Result { + let inputs = abi_function + .inputs + .iter() + .map(|input| { + UnifiedTypeApplication::from_concrete_type_id( + input.name.clone(), + input.concrete_type_id.clone(), + concrete_types_lookup, + ) + }) + .collect(); + + let attributes = abi_function + .attributes + .as_ref() + .map_or(vec![], Clone::clone); + UnifiedABIFunction::new( + abi_function.name.clone(), + inputs, + UnifiedTypeApplication::from_concrete_type_id( + "".to_string(), + abi_function.output.clone(), + concrete_types_lookup, + ), + attributes, + ) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UnifiedTypeDeclaration { + pub type_id: usize, + pub type_field: String, + pub components: Option>, + pub type_parameters: Option>, +} + +impl UnifiedTypeDeclaration { + pub fn from_counterpart( + type_decl: &TypeMetadataDeclaration, + concrete_types_lookup: &HashMap, + ) -> UnifiedTypeDeclaration { + let components: Vec = type_decl + .components + .clone() + .unwrap_or_default() + .into_iter() + .map(|application| { + UnifiedTypeApplication::from_counterpart(&application, concrete_types_lookup) + }) + .collect(); + let type_parameters: Vec = type_decl + .type_parameters + .clone() + .unwrap_or_default() + .into_iter() + .map(|id| id.0) + .collect(); + UnifiedTypeDeclaration { + type_id: type_decl.metadata_type_id.0, + type_field: type_decl.type_field.clone(), + components: if components.is_empty() { + None + } else { + Some(components) + }, + type_parameters: if type_parameters.is_empty() { + None + } else { + Some(type_parameters) + }, + } + } + + pub fn custom_type_path(&self) -> Result { + let type_field = &self.type_field; + let type_name = extract_custom_type_name(type_field) + .ok_or_else(|| error!("Couldn't extract custom type path from '{type_field}'"))?; + + TypePath::new(type_name) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UnifiedTypeApplication { + pub type_id: usize, + pub name: String, + pub type_arguments: Option>, +} + +impl UnifiedTypeApplication { + pub fn from_counterpart( + type_application: &TypeApplication, + concrete_types_lookup: &HashMap, + ) -> UnifiedTypeApplication { + let (metadata_type_id, type_arguments) = match type_application.type_id.clone() { + TypeId::Concrete(concrete_type_id) => ( + concrete_types_lookup + .get(&concrete_type_id) + .unwrap() + .metadata_type_id + .clone() + .unwrap(), + concrete_types_lookup + .get(&concrete_type_id) + .unwrap() + .type_arguments + .clone() + .unwrap_or_default() + .into_iter() + .map(|concrete_type_id| { + UnifiedTypeApplication::from_concrete_type_id( + "".to_string(), + concrete_type_id, + concrete_types_lookup, + ) + }) + .collect::>(), + ), + TypeId::Metadata(metadata_type_id) => ( + metadata_type_id, + type_application + .type_arguments + .clone() + .unwrap_or_default() + .into_iter() + .map(|application| { + UnifiedTypeApplication::from_counterpart( + &application, + concrete_types_lookup, + ) + }) + .collect(), + ), + }; + + UnifiedTypeApplication { + name: type_application.name.clone(), + type_id: metadata_type_id.0, + type_arguments: if type_arguments.is_empty() { + None + } else { + Some(type_arguments) + }, + } + } + + pub fn from_concrete_type_id( + name: String, + concrete_type_id: ConcreteTypeId, + concrete_types_lookup: &HashMap, + ) -> UnifiedTypeApplication { + let concrete_type_decl = concrete_types_lookup + .get(&concrete_type_id) + .unwrap() + .clone(); + let type_arguments: Vec = concrete_type_decl + .type_arguments + .clone() + .unwrap_or_default() + .into_iter() + .map(|concrete_type_id| { + UnifiedTypeApplication::from_concrete_type_id( + "".to_string(), + concrete_type_id, + concrete_types_lookup, + ) + }) + .collect(); + + let metadata_type_id = concrete_type_decl.metadata_type_id.unwrap(); + + UnifiedTypeApplication { + name, + type_id: metadata_type_id.0, + type_arguments: if type_arguments.is_empty() { + None + } else { + Some(type_arguments) + }, + } + } +} + +#[derive(Default, Debug, Clone, Eq, PartialEq)] +pub struct UnifiedLoggedType { + pub log_id: String, + pub application: UnifiedTypeApplication, +} + +impl UnifiedLoggedType { + fn from_counterpart( + logged_type: &LoggedType, + concrete_types_lookup: &HashMap, + ) -> UnifiedLoggedType { + UnifiedLoggedType { + log_id: logged_type.log_id.clone(), + application: UnifiedTypeApplication::from_concrete_type_id( + "".to_string(), + logged_type.concrete_type_id.clone(), + concrete_types_lookup, + ), + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UnifiedConfigurable { + pub name: String, + pub application: UnifiedTypeApplication, + pub offset: u64, +} + +impl UnifiedConfigurable { + pub fn from_counterpart( + configurable: &Configurable, + concrete_types_lookup: &HashMap, + ) -> UnifiedConfigurable { + UnifiedConfigurable { + name: configurable.name.clone(), + application: UnifiedTypeApplication::from_concrete_type_id( + "".to_string(), + configurable.concrete_type_id.clone(), + concrete_types_lookup, + ), + offset: configurable.offset, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UnifiedMessageType { + pub message_id: String, + pub application: UnifiedTypeApplication, +} + +impl UnifiedMessageType { + pub fn from_counterpart( + message_type: &MessageType, + concrete_types_lookup: &HashMap, + ) -> UnifiedMessageType { + UnifiedMessageType { + message_id: message_type.message_id.clone(), + application: UnifiedTypeApplication::from_concrete_type_id( + "".to_string(), + message_type.concrete_type_id.clone(), + concrete_types_lookup, + ), + } + } +} + +impl UnifiedTypeDeclaration { + pub fn is_custom_type(&self) -> bool { + self.is_struct_type() || self.is_enum_type() + } + + pub fn is_enum_type(&self) -> bool { + self.type_field.starts_with("enum ") + } + + pub fn is_struct_type(&self) -> bool { + self.type_field.starts_with("struct ") + } +}