From dde493ce11ce18f4e9ba229c784e28247191541a Mon Sep 17 00:00:00 2001 From: hal3e Date: Tue, 12 Mar 2024 10:30:16 +0100 Subject: [PATCH] feat!: add `decode_as_debug_str` to `ABIDecoder` (#1291) PR: https://github.com/FuelLabs/fuels-rs/pull/690 updated `ParamType` and added names for `struct` and `enum` types and their fields/variants. This was later removed in PR: https://github.com/FuelLabs/fuels-rs/pull/885 as we completely changed how log decoding is done. This PR returns struct and enum names (also fields/variants) so that users can use `ParamTypes` at runtime to debug log or return receipts. In addition, users are able to go directly from `ProgramABI` and some data to decoded debug. Here is an example how this is used: BREAKING CHANGE: `EnumVariants` are now imported through `param_types::EnumVariants` --- examples/debugging/src/lib.rs | 102 +- packages/fuels-core/src/codec/abi_decoder.rs | 146 +- .../src/codec/abi_decoder/bounded_decoder.rs | 24 +- .../codec/abi_decoder/decode_as_debug_str.rs | 262 +++ .../experimental_bounded_decoder.rs | 21 +- packages/fuels-core/src/codec/abi_encoder.rs | 36 +- .../src/codec/abi_encoder/bounded_encoder.rs | 2 +- .../fuels-core/src/codec/function_selector.rs | 67 +- .../fuels-core/src/traits/parameterize.rs | 39 +- packages/fuels-core/src/traits/tokenizable.rs | 8 +- packages/fuels-core/src/types.rs | 5 +- packages/fuels-core/src/types/core/bits.rs | 3 +- packages/fuels-core/src/types/param_types.rs | 1939 +---------------- .../src/types/param_types/debug_with_depth.rs | 256 +++ .../types/{ => param_types}/enum_variants.rs | 79 +- .../param_types/from_type_application.rs | 1196 ++++++++++ .../src/types/param_types/param_type.rs | 608 ++++++ packages/fuels-core/src/utils.rs | 10 + .../fuels-macros/src/derive/parameterize.rs | 16 +- .../fuels-macros/src/derive/tokenizable.rs | 6 +- packages/fuels-macros/src/parse_utils.rs | 7 + packages/fuels-programs/src/call_utils.rs | 22 +- .../types/contracts/generics/src/main.sw | 12 +- packages/fuels/tests/types_contracts.rs | 6 +- 24 files changed, 2756 insertions(+), 2116 deletions(-) create mode 100644 packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs create mode 100644 packages/fuels-core/src/types/param_types/debug_with_depth.rs rename packages/fuels-core/src/types/{ => param_types}/enum_variants.rs (55%) create mode 100644 packages/fuels-core/src/types/param_types/from_type_application.rs create mode 100644 packages/fuels-core/src/types/param_types/param_type.rs diff --git a/examples/debugging/src/lib.rs b/examples/debugging/src/lib.rs index ef3424f348..bae1cceca6 100644 --- a/examples/debugging/src/lib.rs +++ b/examples/debugging/src/lib.rs @@ -5,9 +5,10 @@ mod tests { use fuel_abi_types::abi::program::ProgramABI; use fuels::{ core::{ - codec::{calldata, fn_selector, resolve_fn_selector}, + codec::{calldata, fn_selector, resolve_fn_selector, ABIDecoder}, traits::Parameterize, }, + macros::abigen, types::{errors::Result, param_types::ParamType, SizedAsciiString}, }; @@ -69,4 +70,103 @@ mod tests { Ok(()) } + + #[test] + fn decoded_debug_matches_rust_debug() -> Result<()> { + abigen!(Contract( + name = "MyContract", + abi = "packages/fuels/tests/types/contracts/generics/out/debug/generics-abi.json" + )); + + let json_abi_file = + "../../packages/fuels/tests/types/contracts/generics/out/debug/generics-abi.json"; + let abi_file_contents = std::fs::read_to_string(json_abi_file)?; + + let parsed_abi: ProgramABI = serde_json::from_str(&abi_file_contents)?; + + let type_lookup = parsed_abi + .types + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + let get_first_fn_argument = |fn_name: &str| { + parsed_abi + .functions + .iter() + .find(|abi_fun| abi_fun.name == fn_name) + .expect("should be there") + .inputs + .first() + .expect("should be there") + }; + let decoder = ABIDecoder::default(); + + { + // simple struct with a single generic parameter + let type_application = get_first_fn_argument("struct_w_generic"); + let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; + + let expected_struct = SimpleGeneric { + single_generic_param: 123u64, + }; + + assert_eq!( + format!("{expected_struct:?}"), + decoder.decode_as_debug_str(¶m_type, &[0, 0, 0, 0, 0, 0, 0, 123])? + ); + } + { + // struct that delegates the generic param internally + let type_application = get_first_fn_argument("struct_delegating_generic"); + let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; + + let expected_struct = PassTheGenericOn { + one: SimpleGeneric { + single_generic_param: SizedAsciiString::<3>::try_from("abc")?, + }, + }; + + assert_eq!( + format!("{expected_struct:?}"), + decoder.decode_as_debug_str(¶m_type, &[97, 98, 99])? + ); + } + { + // enum with generic in variant + let type_application = get_first_fn_argument("enum_w_generic"); + let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?; + + let expected_enum = EnumWGeneric::B(10u64); + + assert_eq!( + format!("{expected_enum:?}"), + decoder.decode_as_debug_str( + ¶m_type, + &[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10] + )? + ); + } + { + // logged type + let logged_type = parsed_abi + .logged_types + .as_ref() + .expect("has logs") + .first() + .expect("has log"); + + let param_type = + ParamType::try_from_type_application(&logged_type.application, &type_lookup)?; + + let expected_u8 = 1; + + assert_eq!( + format!("{expected_u8}"), + decoder.decode_as_debug_str(¶m_type, &[0, 0, 0, 0, 0, 0, 0, 1])? + ); + } + + Ok(()) + } } diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index 27f171b857..4d15a5b8af 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -1,11 +1,14 @@ mod bounded_decoder; +mod decode_as_debug_str; #[cfg(experimental)] mod experimental_bounded_decoder; #[cfg(experimental)] use crate::codec::abi_decoder::experimental_bounded_decoder::ExperimentalBoundedDecoder; use crate::{ - codec::abi_decoder::bounded_decoder::BoundedDecoder, + codec::abi_decoder::{ + bounded_decoder::BoundedDecoder, decode_as_debug_str::decode_as_debug_str, + }, types::{errors::Result, param_types::ParamType, Token}, }; @@ -82,6 +85,32 @@ impl ABIDecoder { BoundedDecoder::new(self.config).decode_multiple(param_types, bytes) } + /// Decodes `bytes` following the schema described in `param_type` into its respective debug + /// string. + /// + /// # Arguments + /// + /// * `param_type`: The `ParamType` of the type we expect is encoded + /// inside `bytes`. + /// * `bytes`: The bytes to be used in the decoding process. + /// # Examples + /// + /// ``` + /// use fuels_core::codec::ABIDecoder; + /// use fuels_core::types::param_types::ParamType; + /// + /// let decoder = ABIDecoder::default(); + /// + /// let debug_string = decoder.decode_as_debug_str(&ParamType::U64, &[0, 0, 0, 0, 0, 0, 0, 7]).unwrap(); + /// let expected_value = 7u64; + /// + /// assert_eq!(debug_string, format!("{expected_value}")); + /// ``` + pub fn decode_as_debug_str(&self, param_type: &ParamType, bytes: &[u8]) -> Result { + let token = BoundedDecoder::new(self.config).decode(param_type, bytes)?; + decode_as_debug_str(param_type, &token) + } + #[cfg(experimental)] pub fn experimental_decode(&self, param_type: &ParamType, bytes: &[u8]) -> Result { ExperimentalBoundedDecoder::new(self.config).decode(param_type, bytes) @@ -106,8 +135,9 @@ mod tests { use super::*; use crate::{ constants::WORD_SIZE, + to_named, traits::Parameterize, - types::{enum_variants::EnumVariants, errors::Error, StaticStringToken, U256}, + types::{errors::Error, param_types::EnumVariants, StaticStringToken, U256}, }; #[test] @@ -258,7 +288,8 @@ mod tests { 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ]; let param_type = ParamType::Struct { - fields: vec![ParamType::U8, ParamType::Bool], + name: "".to_string(), + fields: to_named(&[ParamType::U8, ParamType::Bool]), generics: vec![], }; @@ -290,10 +321,11 @@ mod tests { // y: bool, // } - let types = vec![ParamType::U32, ParamType::Bool]; + let types = to_named(&[ParamType::U32, ParamType::Bool]); let inner_enum_types = EnumVariants::new(types)?; let types = vec![ParamType::Enum { - variants: inner_enum_types.clone(), + name: "".to_string(), + enum_variants: inner_enum_types.clone(), generics: vec![], }]; @@ -322,17 +354,19 @@ mod tests { // y: u32, // } - let types = vec![ParamType::B256, ParamType::U32]; + let types = to_named(&[ParamType::B256, ParamType::U32]); let inner_enum_types = EnumVariants::new(types)?; - let fields = vec![ + let fields = to_named(&[ ParamType::Enum { - variants: inner_enum_types.clone(), + name: "".to_string(), + enum_variants: inner_enum_types.clone(), generics: vec![], }, ParamType::U32, - ]; + ]); let struct_type = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -375,17 +409,19 @@ mod tests { // b: u8[2], // } - let fields = vec![ + let fields = to_named(&[ ParamType::U16, ParamType::Struct { - fields: vec![ + name: "".to_string(), + fields: to_named(&[ ParamType::Bool, ParamType::Array(Box::new(ParamType::U8), 2), - ], + ]), generics: vec![], }, - ]; + ]); let nested_struct = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -425,17 +461,19 @@ mod tests { // fn: long_function(Foo,u8[2],b256,str[3],str) // Parameters - let fields = vec![ + let fields = to_named(&[ ParamType::U16, ParamType::Struct { - fields: vec![ + name: "".to_string(), + fields: to_named(&[ ParamType::Bool, ParamType::Array(Box::new(ParamType::U8), 2), - ], + ]), generics: vec![], }, - ]; + ]); let nested_struct = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -493,7 +531,8 @@ mod tests { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ]; let struct_type = ParamType::Struct { - fields: vec![ParamType::Unit, ParamType::U64], + name: "".to_string(), + fields: to_named(&[ParamType::Unit, ParamType::U64]), generics: vec![], }; @@ -508,16 +547,17 @@ mod tests { #[test] fn enums_with_all_unit_variants_are_decoded_from_one_word() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 1]; - let types = vec![ParamType::Unit, ParamType::Unit]; - let variants = EnumVariants::new(types)?; + let types = to_named(&[ParamType::Unit, ParamType::Unit]); + let enum_variants = EnumVariants::new(types)?; let enum_w_only_units = ParamType::Enum { - variants: variants.clone(), + name: "".to_string(), + enum_variants: enum_variants.clone(), generics: vec![], }; let result = ABIDecoder::default().decode(&enum_w_only_units, &data)?; - let expected_enum = Token::Enum(Box::new((1, Token::Unit, variants))); + let expected_enum = Token::Enum(Box::new((1, Token::Unit, enum_variants))); assert_eq!(result, expected_enum); Ok(()) @@ -526,10 +566,11 @@ mod tests { #[test] fn out_of_bounds_discriminant_is_detected() -> Result<()> { let data = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]; - let types = vec![ParamType::U32]; - let variants = EnumVariants::new(types)?; + let types = to_named(&[ParamType::U32]); + let enum_variants = EnumVariants::new(types)?; let enum_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; @@ -554,7 +595,8 @@ mod tests { pub fn multiply_overflow_enum() { let result = ABIDecoder::default().decode( &Enum { - variants: EnumVariants::new(vec![ + name: "".to_string(), + enum_variants: EnumVariants::new(to_named(&[ Array(Box::new(Array(Box::new(RawSlice), 8)), usize::MAX), B256, B256, @@ -566,12 +608,13 @@ mod tests { B256, B256, B256, - ]) + ])) .unwrap(), generics: vec![U16], }, &[], ); + assert!(matches!(result, Err(Error::Codec(_)))); } @@ -583,7 +626,8 @@ mod tests { } let result = ABIDecoder::default().decode( &Enum { - variants: EnumVariants::new(vec![param_type]).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new(to_named(&[param_type])).unwrap(), generics: vec![U16], }, &[], @@ -629,18 +673,20 @@ mod tests { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; - let variants = EnumVariants::new(param_types.clone())?; + let enum_variants = EnumVariants::new(to_named(¶m_types))?; let enum_param_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; // it works if there is only one heap type let _ = ABIDecoder::default().decode(&enum_param_type, &data)?; param_types.append(&mut vec![ParamType::Bytes]); - let variants = EnumVariants::new(param_types)?; + let enum_variants = EnumVariants::new(to_named(¶m_types))?; let enum_param_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; // fails if there is more than one variant using heap type in the enum @@ -656,16 +702,18 @@ mod tests { #[test] fn enums_w_too_deeply_nested_heap_types_not_allowed() { - let param_types = vec![ + let variants = to_named(&[ ParamType::U8, ParamType::Struct { - fields: vec![ParamType::RawSlice], + name: "".to_string(), + fields: to_named(&[ParamType::RawSlice]), generics: vec![], }, - ]; - let variants = EnumVariants::new(param_types).unwrap(); + ]); + let enum_variants = EnumVariants::new(variants).unwrap(); let enum_param_type = ParamType::Enum { - variants, + name: "".to_string(), + enum_variants, generics: vec![], }; @@ -721,7 +769,8 @@ mod tests { // Wrapping everything in a structure so that we may check whether the depth is // decremented after finishing every struct field. ParamType::Struct { - fields: vec![param_type.clone(), param_type], + name: "".to_string(), + fields: to_named(&[param_type.clone(), param_type]), generics: vec![], } }) @@ -738,15 +787,16 @@ mod tests { }; let data = [0; 3 * WORD_SIZE]; - let el = ParamType::U8; + let inner_param_types = vec![ParamType::U8; 3]; for param_type in [ ParamType::Struct { - fields: vec![el.clone(); 3], + name: "".to_string(), + fields: to_named(&inner_param_types), generics: vec![], }, - ParamType::Tuple(vec![el.clone(); 3]), - ParamType::Array(Box::new(el.clone()), 3), - ParamType::Vector(Box::new(el)), + ParamType::Tuple(inner_param_types.clone()), + ParamType::Array(Box::new(ParamType::U8), 3), + ParamType::Vector(Box::new(ParamType::U8)), ] { assert_decoding_failed_w_data( config, @@ -816,10 +866,11 @@ mod tests { let fields = if depth == 1 { vec![] } else { - vec![nested_struct(depth - 1)] + to_named(&[nested_struct(depth - 1)]) }; ParamType::Struct { + name: "".to_string(), fields, generics: vec![], } @@ -827,13 +878,14 @@ mod tests { fn nested_enum(depth: usize) -> ParamType { let fields = if depth == 1 { - vec![ParamType::U8] + to_named(&[ParamType::U8]) } else { - vec![nested_enum(depth - 1)] + to_named(&[nested_enum(depth - 1)]) }; ParamType::Enum { - variants: EnumVariants::new(fields).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new(fields).unwrap(), generics: vec![], } } diff --git a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs index 556ceb3f70..4bc169d504 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -8,9 +8,8 @@ use crate::{ }, constants::WORD_SIZE, types::{ - enum_variants::EnumVariants, errors::{error, Result}, - param_types::ParamType, + param_types::{EnumVariants, NamedParamType, ParamType}, StaticStringToken, Token, U256, }, }; @@ -107,8 +106,8 @@ impl BoundedDecoder { ParamType::Struct { fields, .. } => { self.run_w_depth_tracking(|ctx| ctx.decode_struct(fields, bytes)) } - ParamType::Enum { variants, .. } => { - self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, variants)) + ParamType::Enum { enum_variants, .. } => { + self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, enum_variants)) } ParamType::Tuple(types) => { self.run_w_depth_tracking(|ctx| ctx.decode_tuple(types, bytes)) @@ -167,12 +166,12 @@ impl BoundedDecoder { }) } - fn decode_struct(&mut self, param_types: &[ParamType], bytes: &[u8]) -> Result { + fn decode_struct(&mut self, param_types: &[NamedParamType], bytes: &[u8]) -> Result { let mut tokens = vec![]; let mut bytes_read = 0; - for param_type in param_types.iter() { + for (_, param_type) in param_types.iter() { // padding has to be taken into account bytes_read = checked_round_up_to_word_alignment(bytes_read)?; let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?; @@ -324,13 +323,13 @@ impl BoundedDecoder { /// /// * `data`: slice of encoded data on whose beginning we're expecting an encoded enum /// * `variants`: all types that this particular enum type could hold - fn decode_enum(&mut self, bytes: &[u8], variants: &EnumVariants) -> Result { - let enum_width_in_bytes = variants.compute_enum_width_in_bytes()?; + fn decode_enum(&mut self, bytes: &[u8], enum_variants: &EnumVariants) -> Result { + let enum_width_in_bytes = enum_variants.compute_enum_width_in_bytes()?; let discriminant = peek_u64(bytes)?; - let selected_variant = variants.param_type_of_variant(discriminant)?; + let (_, selected_variant) = enum_variants.select_variant(discriminant)?; - let skip_extra_in_bytes = match variants.heap_type_variant() { + let skip_extra_in_bytes = match enum_variants.heap_type_variant() { Some((heap_type_discriminant, heap_type)) if heap_type_discriminant == discriminant => { heap_type.compute_encoding_in_bytes()? } @@ -341,9 +340,10 @@ impl BoundedDecoder { + skip_extra_in_bytes; let enum_content_bytes = skip(bytes, bytes_to_skip)?; - let result = self.decode_token_in_enum(enum_content_bytes, variants, selected_variant)?; + let result = + self.decode_token_in_enum(enum_content_bytes, enum_variants, selected_variant)?; - let selector = Box::new((discriminant, result.token, variants.clone())); + let selector = Box::new((discriminant, result.token, enum_variants.clone())); Ok(Decoded { token: Token::Enum(selector), bytes_read: enum_width_in_bytes, diff --git a/packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs b/packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs new file mode 100644 index 0000000000..15c4d9e9c3 --- /dev/null +++ b/packages/fuels-core/src/codec/abi_decoder/decode_as_debug_str.rs @@ -0,0 +1,262 @@ +use std::iter::zip; + +use crate::types::{ + errors::{error, Result}, + param_types::ParamType, + Token, +}; + +fn inner_types_debug(tokens: &[Token], inner_type: &ParamType, join_str: &str) -> Result { + let inner_types_log = tokens + .iter() + .map(|token| decode_as_debug_str(inner_type, token)) + .collect::>>()? + .join(join_str); + + Ok(inner_types_log) +} + +pub(crate) fn decode_as_debug_str(param_type: &ParamType, token: &Token) -> Result { + let result = match (param_type, token) { + (ParamType::Unit, Token::Unit) => "()".to_string(), + (ParamType::Bool, Token::Bool(val)) => val.to_string(), + (ParamType::U8, Token::U8(val)) => val.to_string(), + (ParamType::U16, Token::U16(val)) => val.to_string(), + (ParamType::U32, Token::U32(val)) => val.to_string(), + (ParamType::U64, Token::U64(val)) => val.to_string(), + (ParamType::U128, Token::U128(val)) => val.to_string(), + (ParamType::U256, Token::U256(val)) => val.to_string(), + (ParamType::B256, Token::B256(val)) => { + format!("Bits256({val:?})") + } + (ParamType::Bytes, Token::Bytes(val)) => { + format!("Bytes({val:?})") + } + (ParamType::String, Token::String(val)) => val.clone(), + (ParamType::RawSlice, Token::RawSlice(val)) => { + format!("RawSlice({val:?})") + } + (ParamType::StringArray(..), Token::StringArray(str_token)) => { + format!("SizedAsciiString {{ data: \"{}\" }}", str_token.data) + } + (ParamType::StringSlice, Token::StringSlice(str_token)) => { + format!("AsciiString {{ data: \"{}\" }}", str_token.data) + } + (ParamType::Tuple(types), Token::Tuple(tokens)) => { + let elements = zip(types, tokens) + .map(|(ptype, token)| decode_as_debug_str(ptype, token)) + .collect::>>()? + .join(", "); + + format!("({elements})") + } + (ParamType::Array(inner_type, _), Token::Array(tokens)) => { + let elements = inner_types_debug(tokens, inner_type, ", ")?; + format!("[{elements}]") + } + (ParamType::Vector(inner_type), Token::Vector(tokens)) => { + let elements = inner_types_debug(tokens, inner_type, ", ")?; + format!("[{elements}]") + } + (ParamType::Struct { name, fields, .. }, Token::Struct(field_tokens)) => { + let fields = zip(fields, field_tokens) + .map(|((field_name, param_type), token)| -> Result<_> { + Ok(format!( + "{field_name}: {}", + decode_as_debug_str(param_type, token)? + )) + }) + .collect::>>()? + .join(", "); + format!("{name} {{ {fields} }}") + } + (ParamType::Enum { .. }, Token::Enum(selector)) => { + let (discriminant, token, variants) = selector.as_ref(); + + let (variant_name, variant_param_type) = variants.select_variant(*discriminant)?; + let variant_str = decode_as_debug_str(variant_param_type, token)?; + let variant_str = if variant_str == "()" { + "".into() + } else { + format!("({variant_str})") + }; + + format!("{variant_name}{variant_str}") + } + _ => { + return Err(error!( + Codec, + "could not decode debug from param type: `{param_type:?}` and token: `{token:?}`" + )) + } + }; + Ok(result) +} + +#[cfg(test)] +mod tests { + use crate::{ + codec::ABIDecoder, + traits::Parameterize, + types::{ + errors::Result, AsciiString, Bits256, Bytes, EvmAddress, RawSlice, SizedAsciiString, + U256, + }, + }; + + #[test] + fn param_type_decode_debug() -> Result<()> { + let decoder = ABIDecoder::default(); + { + assert_eq!( + format!("{:?}", true), + decoder.decode_as_debug_str(&bool::param_type(), &[0, 0, 0, 0, 0, 0, 0, 1])? + ); + + assert_eq!( + format!("{:?}", 128u8), + decoder.decode_as_debug_str(&u8::param_type(), &[0, 0, 0, 0, 0, 0, 0, 128])? + ); + + assert_eq!( + format!("{:?}", 256u16), + decoder.decode_as_debug_str(&u16::param_type(), &[0, 0, 0, 0, 0, 0, 1, 0])? + ); + + assert_eq!( + format!("{:?}", 512u32), + decoder.decode_as_debug_str(&u32::param_type(), &[0, 0, 0, 0, 0, 0, 2, 0])? + ); + + assert_eq!( + format!("{:?}", 1024u64), + decoder.decode_as_debug_str(&u64::param_type(), &[0, 0, 0, 0, 0, 0, 4, 0])? + ); + + assert_eq!( + format!("{:?}", 1024u128), + decoder.decode_as_debug_str( + &u128::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0] + )? + ); + + assert_eq!( + format!("{:?}", U256::from(2048)), + decoder.decode_as_debug_str( + &U256::param_type(), + &[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 8, 0 + ] + )? + ); + } + { + let bytes = [ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, + 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74, + ]; + let bits256 = Bits256(bytes); + + assert_eq!( + format!("{bits256:?}"), + decoder.decode_as_debug_str( + &Bits256::param_type(), + &[ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, + 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + + assert_eq!( + format!("{:?}", Bytes(bytes.to_vec())), + decoder.decode_as_debug_str( + &Bytes::param_type(), + &[ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, + 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + + assert_eq!( + format!("{:?}", RawSlice(bytes.to_vec())), + decoder.decode_as_debug_str( + &RawSlice::param_type(), + &[ + 239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, + 161, 16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + + assert_eq!( + format!("{:?}", EvmAddress::from(bits256)), + decoder.decode_as_debug_str( + &EvmAddress::param_type(), + &[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 166, 225, 89, 161, 16, 60, 239, 183, + 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74 + ] + )? + ); + } + { + assert_eq!( + format!("{:?}", AsciiString::new("Fuel".to_string())?), + decoder.decode_as_debug_str(&AsciiString::param_type(), &[70, 117, 101, 108])? + ); + + assert_eq!( + format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?), + decoder.decode_as_debug_str( + &SizedAsciiString::<4>::param_type(), + &[70, 117, 101, 108, 0, 0, 0, 0] + )? + ); + + assert_eq!( + format!("{}", "Fuel"), + decoder.decode_as_debug_str(&String::param_type(), &[70, 117, 101, 108])? + ); + } + { + assert_eq!( + format!("{:?}", (1, 2)), + decoder.decode_as_debug_str( + &<(u8, u8)>::param_type(), + &[1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0] + )? + ); + + assert_eq!( + format!("{:?}", [3, 4]), + decoder.decode_as_debug_str( + &<[u64; 2]>::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4] + )? + ); + } + { + assert_eq!( + format!("{:?}", Some(42)), + decoder.decode_as_debug_str( + &>::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42] + )? + ); + + assert_eq!( + format!("{:?}", Err::(42u64)), + decoder.decode_as_debug_str( + &>::param_type(), + &[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 42] + )? + ); + } + + Ok(()) + } +} diff --git a/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs b/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs index 5876b387f4..4660c863aa 100644 --- a/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/experimental_bounded_decoder.rs @@ -4,9 +4,8 @@ use crate::{ codec::DecoderConfig, constants::WORD_SIZE, types::{ - enum_variants::EnumVariants, errors::{error, Result}, - param_types::ParamType, + param_types::{EnumVariants, NamedParamType, ParamType}, StaticStringToken, Token, U256, }, }; @@ -93,8 +92,8 @@ impl ExperimentalBoundedDecoder { ParamType::Struct { fields, .. } => { self.run_w_depth_tracking(|ctx| ctx.decode_struct(fields, bytes)) } - ParamType::Enum { variants, .. } => { - self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, variants)) + ParamType::Enum { enum_variants, .. } => { + self.run_w_depth_tracking(|ctx| ctx.decode_enum(bytes, enum_variants)) } } } @@ -249,8 +248,8 @@ impl ExperimentalBoundedDecoder { }) } - fn decode_struct(&mut self, param_types: &[ParamType], bytes: &[u8]) -> Result { - let (tokens, bytes_read) = self.decode_params(param_types, bytes)?; + fn decode_struct(&mut self, fields: &[NamedParamType], bytes: &[u8]) -> Result { + let (tokens, bytes_read) = self.decode_params(fields.iter().map(|(_, pt)| pt), bytes)?; Ok(Decoded { token: Token::Struct(tokens), @@ -258,15 +257,19 @@ impl ExperimentalBoundedDecoder { }) } - fn decode_enum(&mut self, bytes: &[u8], variants: &EnumVariants) -> Result { + fn decode_enum(&mut self, bytes: &[u8], enum_variants: &EnumVariants) -> Result { let discriminant = peek_discriminant(bytes)?; let variant_bytes = skip(bytes, DISCRIMINANT_BYTES_SIZE)?; - let selected_variant = variants.param_type_of_variant(discriminant)?; + let (_, selected_variant) = enum_variants.select_variant(discriminant)?; let decoded = self.decode_param(selected_variant, variant_bytes)?; Ok(Decoded { - token: Token::Enum(Box::new((discriminant, decoded.token, variants.clone()))), + token: Token::Enum(Box::new(( + discriminant, + decoded.token, + enum_variants.clone(), + ))), bytes_read: DISCRIMINANT_BYTES_SIZE + decoded.bytes_read, }) } diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index 86a93b6244..785160ee84 100644 --- a/packages/fuels-core/src/codec/abi_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder.rs @@ -59,9 +59,11 @@ mod tests { use crate::{ codec::first_four_bytes_of_sha256_hash, constants::WORD_SIZE, + to_named, types::{ - enum_variants::EnumVariants, errors::Error, param_types::ParamType, StaticStringToken, - U256, + errors::Error, + param_types::{EnumVariants, ParamType}, + StaticStringToken, U256, }, }; @@ -494,7 +496,7 @@ mod tests { // x: u32, // y: bool, // } - let types = vec![ParamType::U32, ParamType::Bool]; + let types = to_named(&[ParamType::U32, ParamType::Bool]); let params = EnumVariants::new(types)?; // An `EnumSelector` indicating that we've chosen the first Enum variant, @@ -528,7 +530,7 @@ mod tests { // Our enum has two variants: B256, and U64. So the enum will set aside // 256b of space or 4 WORDS because that is the space needed to fit the // largest variant(B256). - let types = vec![ParamType::B256, ParamType::U64]; + let types = to_named(&[ParamType::B256, ParamType::U64]); let enum_variants = EnumVariants::new(types)?; let enum_selector = Box::new((1, Token::U64(42), enum_variants)); @@ -559,7 +561,7 @@ mod tests { v2: str[10] } */ - let types = vec![ParamType::Bool, ParamType::StringArray(10)]; + let types = to_named(&[ParamType::Bool, ParamType::StringArray(10)]); let deeper_enum_variants = EnumVariants::new(types)?; let deeper_enum_token = Token::StringArray(StaticStringToken::new("0123456789".into(), Some(10))); @@ -571,14 +573,16 @@ mod tests { } */ - let fields = vec![ + let fields = to_named(&[ ParamType::Enum { - variants: deeper_enum_variants.clone(), + name: "".to_string(), + enum_variants: deeper_enum_variants.clone(), generics: vec![], }, ParamType::Bool, - ]; + ]); let struct_a_type = ParamType::Struct { + name: "".to_string(), fields, generics: vec![], }; @@ -596,7 +600,7 @@ mod tests { } */ - let types = vec![struct_a_type, ParamType::Bool, ParamType::U64]; + let types = to_named(&[struct_a_type, ParamType::Bool, ParamType::U64]); let top_level_enum_variants = EnumVariants::new(types)?; let top_level_enum_token = Token::Enum(Box::new((0, struct_a_token, top_level_enum_variants))); @@ -773,7 +777,7 @@ mod tests { fn enums_with_only_unit_variants_are_encoded_in_one_word() -> Result<()> { let expected = [0, 0, 0, 0, 0, 0, 0, 1]; - let types = vec![ParamType::Unit, ParamType::Unit]; + let types = to_named(&[ParamType::Unit, ParamType::Unit]); let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?)); let actual = ABIEncoder::default() @@ -802,7 +806,7 @@ mod tests { let padding = vec![0; 32]; let expected: Vec = [discriminant, padding].into_iter().flatten().collect(); - let types = vec![ParamType::B256, ParamType::Unit]; + let types = to_named(&[ParamType::B256, ParamType::Unit]); let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?)); let actual = ABIEncoder::default() @@ -876,7 +880,7 @@ mod tests { fn a_vec_in_an_enum() -> Result<()> { // arrange let offset = 40; - let types = vec![ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))]; + let types = to_named(&[ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))]); let variants = EnumVariants::new(types)?; let selector = (1, Token::Vector(vec![Token::U64(5)]), variants); let token = Token::Enum(Box::new(selector)); @@ -917,7 +921,7 @@ mod tests { fn an_enum_in_a_vec() -> Result<()> { // arrange let offset = 40; - let types = vec![ParamType::B256, ParamType::U8]; + let types = to_named(&[ParamType::B256, ParamType::U8]); let variants = EnumVariants::new(types)?; let selector = (1, Token::U8(8), variants); let enum_token = Token::Enum(Box::new(selector)); @@ -1090,10 +1094,10 @@ mod tests { let token = Token::Enum(Box::new(( 1, Token::String("".to_string()), - EnumVariants::new(vec![ + EnumVariants::new(to_named(&[ ParamType::StringArray(18446742977385549567), ParamType::U8, - ])?, + ]))?, ))); let capacity_overflow_error = ABIEncoder::default().encode(&[token]).unwrap_err(); @@ -1154,7 +1158,7 @@ mod tests { let selector = ( 0u64, inner_enum, - EnumVariants::new(vec![ParamType::U64]).unwrap(), + EnumVariants::new(to_named(&[ParamType::U64])).unwrap(), ); Token::Enum(Box::new(selector)) diff --git a/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs b/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs index d52a28d759..5bbfcf68c9 100644 --- a/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder/bounded_encoder.rs @@ -189,7 +189,7 @@ impl BoundedEncoder { // Enums that contain only Units as variants have only their discriminant encoded. if !variants.only_units_inside() { - let variant_param_type = variants.param_type_of_variant(*discriminant)?; + let (_, variant_param_type) = variants.select_variant(*discriminant)?; let enum_width_in_bytes = variants.compute_enum_width_in_bytes()?; if enum_width_in_bytes > self.max_total_enum_width { diff --git a/packages/fuels-core/src/codec/function_selector.rs b/packages/fuels-core/src/codec/function_selector.rs index 4877ac15eb..77c179c1a8 100644 --- a/packages/fuels-core/src/codec/function_selector.rs +++ b/packages/fuels-core/src/codec/function_selector.rs @@ -1,6 +1,9 @@ use sha2::{Digest, Sha256}; -use crate::types::{param_types::ParamType, ByteArray}; +use crate::types::{ + param_types::{NamedParamType, ParamType}, + ByteArray, +}; /// Given a function name and its inputs will return a ByteArray representing /// the function selector as specified in the Fuel specs. @@ -16,8 +19,15 @@ fn resolve_fn_signature(name: &str, inputs: &[ParamType]) -> String { format!("{name}({fn_args})") } -fn resolve_args(arg: &[ParamType]) -> String { - arg.iter().map(resolve_arg).collect::>().join(",") +fn resolve_args(args: &[ParamType]) -> String { + args.iter().map(resolve_arg).collect::>().join(",") +} + +fn resolve_named_args(args: &[NamedParamType]) -> String { + args.iter() + .map(|(_, param_type)| resolve_arg(param_type)) + .collect::>() + .join(",") } fn resolve_arg(arg: &ParamType) -> String { @@ -43,7 +53,7 @@ fn resolve_arg(arg: &ParamType) -> String { fields, generics, .. } => { let gen_params = resolve_args(generics); - let field_params = resolve_args(fields); + let field_params = resolve_named_args(fields); let gen_params = if !gen_params.is_empty() { format!("<{gen_params}>") } else { @@ -52,12 +62,12 @@ fn resolve_arg(arg: &ParamType) -> String { format!("s{gen_params}({field_params})") } ParamType::Enum { - variants: fields, + enum_variants, generics, .. } => { let gen_params = resolve_args(generics); - let field_params = resolve_args(fields.param_types()); + let field_params = resolve_named_args(enum_variants.variants()); let gen_params = if !gen_params.is_empty() { format!("<{gen_params}>") } else { @@ -119,7 +129,7 @@ pub use calldata; #[cfg(test)] mod tests { use super::*; - use crate::types::enum_variants::EnumVariants; + use crate::{to_named, types::param_types::EnumVariants}; #[test] fn handles_primitive_types() { @@ -173,9 +183,13 @@ mod tests { #[test] fn handles_structs() { - let fields = vec![ParamType::U64, ParamType::U32]; + let fields = to_named(&[ParamType::U64, ParamType::U32]); let generics = vec![ParamType::U32]; - let inputs = [ParamType::Struct { fields, generics }]; + let inputs = [ParamType::Struct { + name: "".to_string(), + fields, + generics, + }]; let selector = resolve_fn_signature("some_fun", &inputs); @@ -202,10 +216,14 @@ mod tests { #[test] fn handles_enums() { - let types = vec![ParamType::U64, ParamType::U32]; - let variants = EnumVariants::new(types).unwrap(); + let types = to_named(&[ParamType::U64, ParamType::U32]); + let enum_variants = EnumVariants::new(types).unwrap(); let generics = vec![ParamType::U32]; - let inputs = [ParamType::Enum { variants, generics }]; + let inputs = [ParamType::Enum { + name: "".to_string(), + enum_variants, + generics, + }]; let selector = resolve_fn_signature("some_fun", &inputs); @@ -214,29 +232,34 @@ mod tests { #[test] fn ultimate_test() { - let fields = vec![ParamType::Struct { - fields: vec![ParamType::StringArray(2)], + let fields = to_named(&[ParamType::Struct { + name: "".to_string(), + + fields: to_named(&[ParamType::StringArray(2)]), generics: vec![ParamType::StringArray(2)], - }]; + }]); let struct_a = ParamType::Struct { + name: "".to_string(), fields, generics: vec![ParamType::StringArray(2)], }; - let fields = vec![ParamType::Array(Box::new(struct_a.clone()), 2)]; + let fields = to_named(&[ParamType::Array(Box::new(struct_a.clone()), 2)]); let struct_b = ParamType::Struct { + name: "".to_string(), fields, generics: vec![struct_a], }; - let fields = vec![ParamType::Tuple(vec![struct_b.clone(), struct_b.clone()])]; + let fields = to_named(&[ParamType::Tuple(vec![struct_b.clone(), struct_b.clone()])]); let struct_c = ParamType::Struct { + name: "".to_string(), fields, generics: vec![struct_b], }; - let types = vec![ParamType::U64, struct_c.clone()]; - let fields = vec![ + let types = to_named(&[ParamType::U64, struct_c.clone()]); + let fields = to_named(&[ ParamType::Tuple(vec![ ParamType::Array(Box::new(ParamType::B256), 2), ParamType::StringArray(2), @@ -244,16 +267,18 @@ mod tests { ParamType::Tuple(vec![ ParamType::Array( Box::new(ParamType::Enum { - variants: EnumVariants::new(types).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new(types).unwrap(), generics: vec![struct_c], }), 1, ), ParamType::U32, ]), - ]; + ]); let inputs = [ParamType::Struct { + name: "".to_string(), fields, generics: vec![ParamType::StringArray(2), ParamType::B256], }]; diff --git a/packages/fuels-core/src/traits/parameterize.rs b/packages/fuels-core/src/traits/parameterize.rs index 648cab1a0a..76777a905d 100644 --- a/packages/fuels-core/src/traits/parameterize.rs +++ b/packages/fuels-core/src/traits/parameterize.rs @@ -1,8 +1,8 @@ use fuel_types::{Address, AssetId, ContractId}; use crate::types::{ - enum_variants::EnumVariants, param_types::ParamType, AsciiString, Bits256, Bytes, RawSlice, - SizedAsciiString, + param_types::{EnumVariants, ParamType}, + AsciiString, Bits256, Bytes, RawSlice, SizedAsciiString, }; /// `abigen` requires `Parameterized` to construct nested types. It is also used by `try_from_bytes` @@ -50,7 +50,8 @@ impl Parameterize for String { impl Parameterize for Address { fn param_type() -> ParamType { ParamType::Struct { - fields: vec![ParamType::B256], + name: "Address".to_string(), + fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } @@ -59,7 +60,8 @@ impl Parameterize for Address { impl Parameterize for ContractId { fn param_type() -> ParamType { ParamType::Struct { - fields: vec![ParamType::B256], + name: "ContractId".to_string(), + fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } @@ -68,7 +70,8 @@ impl Parameterize for ContractId { impl Parameterize for AssetId { fn param_type() -> ParamType { ParamType::Struct { - fields: vec![ParamType::B256], + name: "AssetId".to_string(), + fields: vec![("0".to_string(), ParamType::B256)], generics: vec![], } } @@ -121,11 +124,15 @@ where T: Parameterize, { fn param_type() -> ParamType { - let param_types = vec![ParamType::Unit, T::param_type()]; - let variants = EnumVariants::new(param_types) - .expect("should never happen as we provided valid `Option` param types"); + let variant_param_types = vec![ + ("None".to_string(), ParamType::Unit), + ("Some".to_string(), T::param_type()), + ]; + let enum_variants = EnumVariants::new(variant_param_types) + .expect("should never happen as we provided valid Option param types"); ParamType::Enum { - variants, + name: "Option".to_string(), + enum_variants, generics: vec![T::param_type()], } } @@ -137,12 +144,16 @@ where E: Parameterize, { fn param_type() -> ParamType { - let param_types = vec![T::param_type(), E::param_type()]; - let variants = EnumVariants::new(param_types.clone()) - .expect("should never happen as we provided valid `Result` param types"); + let variant_param_types = vec![ + ("Ok".to_string(), T::param_type()), + ("Err".to_string(), E::param_type()), + ]; + let enum_variants = EnumVariants::new(variant_param_types) + .expect("should never happen as we provided valid Result param types"); ParamType::Enum { - variants, - generics: param_types, + name: "Result".to_string(), + enum_variants, + generics: vec![T::param_type(), E::param_type()], } } } diff --git a/packages/fuels-core/src/traits/tokenizable.rs b/packages/fuels-core/src/traits/tokenizable.rs index c00120f081..40cacafdcb 100644 --- a/packages/fuels-core/src/traits/tokenizable.rs +++ b/packages/fuels-core/src/traits/tokenizable.rs @@ -383,8 +383,8 @@ where None => (0, Token::Unit), Some(value) => (1, value.into_token()), }; - if let ParamType::Enum { variants, .. } = Self::param_type() { - let selector = (dis, tok, variants); + if let ParamType::Enum { enum_variants, .. } = Self::param_type() { + let selector = (dis, tok, enum_variants); Token::Enum(Box::new(selector)) } else { panic!("should never happen as `Option::param_type()` returns valid Enum variants"); @@ -420,8 +420,8 @@ where Ok(value) => (0, value.into_token()), Err(value) => (1, value.into_token()), }; - if let ParamType::Enum { variants, .. } = Self::param_type() { - let selector = (dis, tok, variants); + if let ParamType::Enum { enum_variants, .. } = Self::param_type() { + let selector = (dis, tok, enum_variants); Token::Enum(Box::new(selector)) } else { panic!("should never happen as Result::param_type() returns valid Enum variants"); diff --git a/packages/fuels-core/src/types.rs b/packages/fuels-core/src/types.rs index 0bd9b91250..40edd2f0b2 100644 --- a/packages/fuels-core/src/types.rs +++ b/packages/fuels-core/src/types.rs @@ -8,13 +8,12 @@ pub use fuel_types::{ pub use crate::types::{core::*, wrappers::*}; use crate::types::{ - enum_variants::EnumVariants, errors::{error, Error, Result}, + param_types::EnumVariants, }; pub mod bech32; mod core; -pub mod enum_variants; pub mod errors; pub mod param_types; pub mod transaction_builders; @@ -28,7 +27,7 @@ pub type EnumSelector = (u64, Token, EnumVariants); #[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] pub struct StaticStringToken { - data: String, + pub(crate) data: String, expected_len: Option, } diff --git a/packages/fuels-core/src/types/core/bits.rs b/packages/fuels-core/src/types/core/bits.rs index 05c25f3cba..e6b051243c 100644 --- a/packages/fuels-core/src/types/core/bits.rs +++ b/packages/fuels-core/src/types/core/bits.rs @@ -123,7 +123,8 @@ mod tests { assert_eq!( EvmAddress::param_type(), ParamType::Struct { - fields: vec![ParamType::B256], + name: "EvmAddress".to_string(), + fields: vec![("value".to_string(), ParamType::B256)], generics: vec![] } ); diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index f436140e05..5ea2d1aec7 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -1,1934 +1,7 @@ -use std::{collections::HashMap, fmt, iter::zip}; +mod debug_with_depth; +mod enum_variants; +mod from_type_application; +mod param_type; -use fuel_abi_types::{ - abi::program::{TypeApplication, TypeDeclaration}, - utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, -}; -use itertools::chain; - -use crate::{ - checked_round_up_to_word_alignment, - types::{ - enum_variants::EnumVariants, - errors::{error, Error, Result}, - }, -}; - -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum ParamType { - Unit, - Bool, - U8, - U16, - U32, - U64, - U128, - U256, - B256, - Bytes, - String, - RawSlice, - StringArray(usize), - StringSlice, - Tuple(Vec), - Array(Box, usize), - Vector(Box), - Struct { - fields: Vec, - generics: Vec, - }, - Enum { - variants: EnumVariants, - generics: Vec, - }, -} - -pub enum ReturnLocation { - Return, - ReturnData, -} - -impl ParamType { - // Depending on the type, the returned value will be stored - // either in `Return` or `ReturnData`. - pub fn get_return_location(&self) -> ReturnLocation { - match self { - Self::Unit | Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::Bool => { - ReturnLocation::Return - } - - _ => ReturnLocation::ReturnData, - } - } - - /// Given a [ParamType], return the number of elements of that [ParamType] that can fit in - /// `available_bytes`: it is the length of the corresponding heap type. - pub fn calculate_num_of_elements( - param_type: &ParamType, - available_bytes: usize, - ) -> Result { - let memory_size = param_type.compute_encoding_in_bytes()?; - if memory_size == 0 { - return Err(error!( - Codec, - "cannot calculate the number of elements because the type is zero-sized" - )); - } - - let remainder = available_bytes % memory_size; - if remainder != 0 { - return Err(error!( - Codec, - "{remainder} extra bytes detected while decoding heap type" - )); - } - let num_of_elements = available_bytes - .checked_div(memory_size) - .ok_or_else(|| error!(Codec, "type {param_type:?} has a memory_size of 0"))?; - - Ok(num_of_elements) - } - - pub fn children_need_extra_receipts(&self) -> bool { - match self { - ParamType::Array(inner, _) | ParamType::Vector(inner) => { - inner.is_extra_receipt_needed(false) - } - ParamType::Struct { fields, .. } => fields - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::Enum { variants, .. } => variants - .param_types() - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::Tuple(inner_types) => inner_types - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - _ => false, - } - } - - pub fn validate_is_decodable(&self, max_depth: usize) -> Result<()> { - if let ParamType::Enum { variants, .. } = self { - let all_param_types = variants.param_types(); - let grandchildren_need_receipts = all_param_types - .iter() - .any(|child| child.children_need_extra_receipts()); - if grandchildren_need_receipts { - return Err(error!( - Codec, - "enums currently support only one level deep heap types" - )); - } - - let num_of_children_needing_receipts = all_param_types - .iter() - .filter(|param_type| param_type.is_extra_receipt_needed(false)) - .count(); - if num_of_children_needing_receipts > 1 { - return Err(error!( - Codec, - "enums currently support only one heap-type variant. Found: \ - {num_of_children_needing_receipts}" - )); - } - } else if self.children_need_extra_receipts() { - return Err(error!( - Codec, - "type `{:?}` is not decodable: nested heap types are currently not \ - supported except in enums", - DebugWithDepth::new(self, max_depth) - )); - } - self.compute_encoding_in_bytes()?; - Ok(()) - } - - pub fn is_extra_receipt_needed(&self, top_level_type: bool) -> bool { - match self { - ParamType::Vector(_) | ParamType::Bytes | ParamType::String => true, - ParamType::Array(inner, _) => inner.is_extra_receipt_needed(false), - ParamType::Struct { fields, generics } => { - chain!(fields, generics).any(|param_type| param_type.is_extra_receipt_needed(false)) - } - ParamType::Enum { variants, generics } => chain!(variants.param_types(), generics) - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::Tuple(elements) => elements - .iter() - .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::RawSlice | ParamType::StringSlice => !top_level_type, - _ => false, - } - } - - /// Compute the inner memory size of a containing heap type (`Bytes` or `Vec`s). - pub fn heap_inner_element_size(&self, top_level_type: bool) -> Result> { - let heap_bytes_size = match &self { - ParamType::Vector(inner_param_type) => { - Some(inner_param_type.compute_encoding_in_bytes()?) - } - // `Bytes` type is byte-packed in the VM, so it's the size of an u8 - ParamType::Bytes | ParamType::String => Some(std::mem::size_of::()), - ParamType::StringSlice if !top_level_type => { - Some(ParamType::U8.compute_encoding_in_bytes()?) - } - ParamType::RawSlice if !top_level_type => { - Some(ParamType::U64.compute_encoding_in_bytes()?) - } - _ => None, - }; - Ok(heap_bytes_size) - } - - /// Calculates the number of bytes the VM expects this parameter to be encoded in. - pub fn compute_encoding_in_bytes(&self) -> Result { - let overflow_error = || { - error!( - Codec, - "reached overflow while computing encoding size for {:?}", self - ) - }; - match &self { - ParamType::Unit | ParamType::U8 | ParamType::Bool => Ok(1), - ParamType::U16 | ParamType::U32 | ParamType::U64 => Ok(8), - ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => Ok(16), - ParamType::U256 | ParamType::B256 => Ok(32), - ParamType::Vector(_) | ParamType::Bytes | ParamType::String => Ok(24), - ParamType::Array(param, count) => param - .compute_encoding_in_bytes()? - .checked_mul(*count) - .ok_or_else(overflow_error), - ParamType::StringArray(len) => { - checked_round_up_to_word_alignment(*len).map_err(|_| overflow_error()) - } - ParamType::Tuple(fields) | ParamType::Struct { fields, .. } => { - fields.iter().try_fold(0, |a: usize, param_type| { - let size = checked_round_up_to_word_alignment( - param_type.compute_encoding_in_bytes()?, - )?; - a.checked_add(size).ok_or_else(overflow_error) - }) - } - ParamType::Enum { variants, .. } => variants - .compute_enum_width_in_bytes() - .map_err(|_| overflow_error()), - } - } - - /// For when you need to convert a ABI JSON's TypeApplication into a ParamType. - /// - /// # Arguments - /// - /// * `type_application`: The TypeApplication you wish to convert into a ParamType - /// * `type_lookup`: A HashMap of TypeDeclarations mentioned in the - /// TypeApplication where the type id is the key. - pub fn try_from_type_application( - type_application: &TypeApplication, - type_lookup: &HashMap, - ) -> Result { - Type::try_from(type_application, type_lookup)?.try_into() - } -} - -#[derive(Debug, Clone)] -struct Type { - type_field: String, - generic_params: Vec, - components: Vec, -} - -impl Type { - /// Will recursively drill down the given generic parameters until all types are - /// resolved. - /// - /// # Arguments - /// - /// * `type_application`: the type we wish to resolve - /// * `types`: all types used in the function call - pub fn try_from( - type_application: &TypeApplication, - type_lookup: &HashMap, - ) -> Result { - Self::resolve(type_application, type_lookup, &[]) - } - - fn resolve( - type_application: &TypeApplication, - type_lookup: &HashMap, - parent_generic_params: &[(usize, Type)], - ) -> Result { - let type_declaration = type_lookup.get(&type_application.type_id).ok_or_else(|| { - error!( - Codec, - "type id {} not found in type lookup", type_application.type_id - ) - })?; - - if extract_generic_name(&type_declaration.type_field).is_some() { - let (_, generic_type) = parent_generic_params - .iter() - .find(|(id, _)| *id == type_application.type_id) - .ok_or_else(|| { - error!( - Codec, - "type id {} not found in parent's generic parameters", - type_application.type_id - ) - })?; - - return Ok(generic_type.clone()); - } - - // Figure out what does the current type do with the inherited generic - // parameters and reestablish the mapping since the current type might have - // renamed the inherited generic parameters. - let generic_params_lookup = Self::determine_generics_for_type( - type_application, - type_lookup, - type_declaration, - parent_generic_params, - )?; - - // Resolve the enclosed components (if any) with the newly resolved generic - // parameters. - let components = type_declaration - .components - .iter() - .flatten() - .map(|component| Self::resolve(component, type_lookup, &generic_params_lookup)) - .collect::>>()?; - - Ok(Type { - type_field: type_declaration.type_field.clone(), - components, - generic_params: generic_params_lookup - .into_iter() - .map(|(_, ty)| ty) - .collect(), - }) - } - - /// For the given type generates generic_type_id -> Type mapping describing to - /// which types generic parameters should be resolved. - /// - /// # Arguments - /// - /// * `type_application`: The type on which the generic parameters are defined. - /// * `types`: All types used. - /// * `parent_generic_params`: The generic parameters as inherited from the - /// enclosing type (a struct/enum/array etc.). - fn determine_generics_for_type( - type_application: &TypeApplication, - type_lookup: &HashMap, - type_declaration: &TypeDeclaration, - parent_generic_params: &[(usize, Type)], - ) -> Result> { - match &type_declaration.type_parameters { - // The presence of type_parameters indicates that the current type - // (a struct or an enum) defines some generic parameters (i.e. SomeStruct). - Some(params) if !params.is_empty() => { - // Determine what Types the generics will resolve to. - let generic_params_from_current_type = type_application - .type_arguments - .iter() - .flatten() - .map(|ty| Self::resolve(ty, type_lookup, parent_generic_params)) - .collect::>>()?; - - let generics_to_use = if !generic_params_from_current_type.is_empty() { - generic_params_from_current_type - } else { - // Types such as arrays and enums inherit and forward their - // generic parameters, without declaring their own. - parent_generic_params - .iter() - .map(|(_, ty)| ty) - .cloned() - .collect() - }; - - // All inherited but unused generic types are dropped. The rest are - // re-mapped to new type_ids since child types are free to rename - // the generic parameters as they see fit -- i.e. - // struct ParentStruct{ - // b: ChildStruct - // } - // struct ChildStruct { - // c: K - // } - - Ok(zip(params.clone(), generics_to_use).collect()) - } - _ => Ok(parent_generic_params.to_vec()), - } - } -} - -impl TryFrom for ParamType { - type Error = Error; - - fn try_from(value: Type) -> Result { - (&value).try_into() - } -} - -impl TryFrom<&Type> for ParamType { - type Error = Error; - - fn try_from(the_type: &Type) -> Result { - let matched_param_type = [ - try_primitive, - try_array, - try_str_array, - try_str_slice, - try_tuple, - try_vector, - try_bytes, - try_std_string, - try_raw_slice, - try_enum, - try_u128, - try_u256, - try_struct, - ] - .into_iter() - .map(|fun| fun(the_type)) - .flat_map(|result| result.ok().flatten()) - .next(); - - matched_param_type.map(Ok).unwrap_or_else(|| { - Err(error!( - Codec, - "type {} couldn't be converted into a ParamType", the_type.type_field - )) - }) - } -} - -fn convert_into_param_types(coll: &[Type]) -> Result> { - coll.iter().map(ParamType::try_from).collect() -} - -fn try_struct(the_type: &Type) -> Result> { - let result = if has_struct_format(&the_type.type_field) { - let generics = param_types(&the_type.generic_params)?; - - let fields = convert_into_param_types(&the_type.components)?; - Some(ParamType::Struct { fields, generics }) - } else { - None - }; - - Ok(result) -} - -fn has_struct_format(field: &str) -> bool { - field.starts_with("struct ") -} - -fn try_vector(the_type: &Type) -> Result> { - if !["struct std::vec::Vec", "struct Vec"].contains(&the_type.type_field.as_str()) { - return Ok(None); - } - - if the_type.generic_params.len() != 1 { - return Err(error!( - Codec, - "`Vec` must have exactly one generic argument for its type. Found: `{:?}`", - the_type.generic_params - )); - } - - let vec_elem_type = convert_into_param_types(&the_type.generic_params)?.remove(0); - - Ok(Some(ParamType::Vector(Box::new(vec_elem_type)))) -} - -fn try_u128(the_type: &Type) -> Result> { - Ok(["struct std::u128::U128", "struct U128"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::U128)) -} - -fn try_u256(the_type: &Type) -> Result> { - Ok(["struct std::u256::U256", "struct U256"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::U256)) -} - -fn try_bytes(the_type: &Type) -> Result> { - Ok(["struct std::bytes::Bytes", "struct Bytes"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::Bytes)) -} - -fn try_std_string(the_type: &Type) -> Result> { - Ok(["struct std::string::String", "struct String"] - .contains(&the_type.type_field.as_str()) - .then_some(ParamType::String)) -} - -fn try_raw_slice(the_type: &Type) -> Result> { - Ok((the_type.type_field == "raw untyped slice").then_some(ParamType::RawSlice)) -} - -fn try_enum(the_type: &Type) -> Result> { - let field = &the_type.type_field; - let result = if field.starts_with("enum ") { - let generics = param_types(&the_type.generic_params)?; - - let components = convert_into_param_types(&the_type.components)?; - let variants = EnumVariants::new(components)?; - - Some(ParamType::Enum { variants, generics }) - } else { - None - }; - - Ok(result) -} - -fn try_tuple(the_type: &Type) -> Result> { - let result = if has_tuple_format(&the_type.type_field) { - let tuple_elements = param_types(&the_type.components)?; - Some(ParamType::Tuple(tuple_elements)) - } else { - None - }; - - Ok(result) -} - -fn param_types(coll: &[Type]) -> Result> { - coll.iter().map(|e| e.try_into()).collect() -} - -fn try_str_array(the_type: &Type) -> Result> { - Ok(extract_str_len(&the_type.type_field).map(ParamType::StringArray)) -} - -fn try_str_slice(the_type: &Type) -> Result> { - Ok(if the_type.type_field == "str" { - Some(ParamType::StringSlice) - } else { - None - }) -} - -fn try_array(the_type: &Type) -> Result> { - if let Some(len) = extract_array_len(&the_type.type_field) { - return match the_type.components.as_slice() { - [single_type] => { - let array_type = single_type.try_into()?; - Ok(Some(ParamType::Array(Box::new(array_type), len))) - } - _ => Err(error!( - Codec, - "array must have elements of exactly one type. Array types: {:?}", - the_type.components - )), - }; - } - Ok(None) -} - -fn try_primitive(the_type: &Type) -> Result> { - let result = match the_type.type_field.as_str() { - "bool" => Some(ParamType::Bool), - "u8" => Some(ParamType::U8), - "u16" => Some(ParamType::U16), - "u32" => Some(ParamType::U32), - "u64" => Some(ParamType::U64), - "b256" => Some(ParamType::B256), - "()" => Some(ParamType::Unit), - "str" => Some(ParamType::StringSlice), - _ => None, - }; - - Ok(result) -} - -/// Allows `Debug` formatting of arbitrary-depth nested `ParamTypes` by -/// omitting the details of inner types if max depth is exceeded. -pub(crate) struct DebugWithDepth<'a> { - param_type: &'a ParamType, - depth_left: usize, -} - -impl<'a> DebugWithDepth<'a> { - pub(crate) fn new(param_type: &'a ParamType, depth_left: usize) -> Self { - Self { - param_type, - depth_left, - } - } - - fn descend(&'a self, param_type: &'a ParamType) -> Self { - Self { - param_type, - depth_left: self.depth_left - 1, - } - } -} - -impl<'a> fmt::Debug for DebugWithDepth<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.depth_left == 0 { - return write!(f, "..."); - } - - match &self.param_type { - ParamType::Array(inner, size) => f - .debug_tuple("Array") - .field(&self.descend(inner)) - .field(&size) - .finish(), - ParamType::Struct { fields, generics } => f - .debug_struct("Struct") - .field( - "fields", - &fields - .iter() - .map(|field| self.descend(field)) - .collect::>(), - ) - .field( - "generics", - &generics - .iter() - .map(|generic| self.descend(generic)) - .collect::>(), - ) - .finish(), - ParamType::Enum { variants, generics } => f - .debug_struct("Enum") - .field( - "variants", - &variants - .param_types() - .iter() - .map(|variant| self.descend(variant)) - .collect::>(), - ) - .field( - "generics", - &generics - .iter() - .map(|generic| self.descend(generic)) - .collect::>(), - ) - .finish(), - ParamType::Tuple(inner) => f - .debug_tuple("Tuple") - .field( - &inner - .iter() - .map(|param_type| self.descend(param_type)) - .collect::>(), - ) - .finish(), - ParamType::Vector(inner) => { - f.debug_tuple("Vector").field(&self.descend(inner)).finish() - } - _ => write!(f, "{:?}", self.param_type), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{codec::DecoderConfig, constants::WORD_SIZE, types::param_types::ParamType}; - - const WIDTH_OF_B256: usize = 32; - const WIDTH_OF_U32: usize = 8; - const WIDTH_OF_BOOL: usize = 1; - - #[test] - fn array_size_dependent_on_num_of_elements() { - const NUM_ELEMENTS: usize = 11; - let param = ParamType::Array(Box::new(ParamType::B256), NUM_ELEMENTS); - - let width = param.compute_encoding_in_bytes().unwrap(); - - let expected = NUM_ELEMENTS * WIDTH_OF_B256; - assert_eq!(expected, width); - } - - #[test] - fn string_size_dependent_on_num_of_elements() { - const NUM_ASCII_CHARS: usize = 9; - let param = ParamType::StringArray(NUM_ASCII_CHARS); - - let width = param.compute_encoding_in_bytes().unwrap(); - - assert_eq!(16, width); - } - - #[test] - fn structs_are_all_elements_combined_with_padding() -> Result<()> { - let inner_struct = ParamType::Struct { - fields: vec![ParamType::U32, ParamType::U32], - generics: vec![], - }; - - let a_struct = ParamType::Struct { - fields: vec![ParamType::B256, ParamType::Bool, inner_struct], - generics: vec![], - }; - - let width = a_struct.compute_encoding_in_bytes().unwrap(); - - const INNER_STRUCT_WIDTH: usize = WIDTH_OF_U32 * 2; - let expected_width: usize = - WIDTH_OF_B256 + checked_round_up_to_word_alignment(WIDTH_OF_BOOL)? + INNER_STRUCT_WIDTH; - assert_eq!(expected_width, width); - Ok(()) - } - - #[test] - fn enums_are_as_big_as_their_biggest_variant_plus_a_word() -> Result<()> { - let fields = vec![ParamType::B256]; - let inner_struct = ParamType::Struct { - fields, - generics: vec![], - }; - let types = vec![ParamType::U32, inner_struct]; - let param = ParamType::Enum { - variants: EnumVariants::new(types)?, - generics: vec![], - }; - - let width = param.compute_encoding_in_bytes().unwrap(); - - const INNER_STRUCT_SIZE: usize = WIDTH_OF_B256; - const EXPECTED_WIDTH: usize = INNER_STRUCT_SIZE + WORD_SIZE; - assert_eq!(EXPECTED_WIDTH, width); - Ok(()) - } - - #[test] - fn tuples_are_just_all_elements_combined() { - let inner_tuple = ParamType::Tuple(vec![ParamType::B256]); - let param = ParamType::Tuple(vec![ParamType::U32, inner_tuple]); - - let width = param.compute_encoding_in_bytes().unwrap(); - - const INNER_TUPLE_WIDTH: usize = WIDTH_OF_B256; - const EXPECTED_WIDTH: usize = WIDTH_OF_U32 + INNER_TUPLE_WIDTH; - assert_eq!(EXPECTED_WIDTH, width); - } - - #[test] - fn handles_simple_types() -> Result<()> { - let parse_param_type = |type_field: &str| { - let type_application = TypeApplication { - name: "".to_string(), - type_id: 0, - type_arguments: None, - }; - - let declarations = [TypeDeclaration { - type_id: 0, - type_field: type_field.to_string(), - components: None, - type_parameters: None, - }]; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - ParamType::try_from_type_application(&type_application, &type_lookup) - }; - - assert_eq!(parse_param_type("u8")?, ParamType::U8); - assert_eq!(parse_param_type("u16")?, ParamType::U16); - assert_eq!(parse_param_type("u32")?, ParamType::U32); - assert_eq!(parse_param_type("u64")?, ParamType::U64); - assert_eq!(parse_param_type("bool")?, ParamType::Bool); - assert_eq!(parse_param_type("b256")?, ParamType::B256); - assert_eq!(parse_param_type("()")?, ParamType::Unit); - assert_eq!(parse_param_type("str[21]")?, ParamType::StringArray(21)); - assert_eq!(parse_param_type("str")?, ParamType::StringSlice); - - Ok(()) - } - - #[test] - fn handles_arrays() -> Result<()> { - // given - let type_application = TypeApplication { - name: "".to_string(), - type_id: 0, - type_arguments: None, - }; - - let declarations = [ - TypeDeclaration { - type_id: 0, - type_field: "[_; 10]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 1, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!(result, ParamType::Array(Box::new(ParamType::U8), 10)); - - Ok(()) - } - - #[test] - fn handles_vectors() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "raw untyped ptr".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 3, - type_field: "struct std::vec::RawVec".to_string(), - components: Some(vec![ - TypeApplication { - name: "ptr".to_string(), - type_id: 2, - type_arguments: None, - }, - TypeApplication { - name: "cap".to_string(), - type_id: 5, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 4, - type_field: "struct std::vec::Vec".to_string(), - components: Some(vec![ - TypeApplication { - name: "buf".to_string(), - type_id: 3, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 1, - type_arguments: None, - }]), - }, - TypeApplication { - name: "len".to_string(), - type_id: 5, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 5, - type_field: "u64".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 6, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 4, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 6, - type_arguments: None, - }]), - }; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!(result, ParamType::Vector(Box::new(ParamType::U8))); - - Ok(()) - } - - #[test] - fn handles_structs() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "struct SomeStruct".to_string(), - components: Some(vec![TypeApplication { - name: "field".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 3, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 2, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 3, - type_arguments: None, - }]), - }; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!( - result, - ParamType::Struct { - fields: vec![ParamType::U8], - generics: vec![ParamType::U8] - } - ); - - Ok(()) - } - - #[test] - fn handles_enums() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "enum SomeEnum".to_string(), - components: Some(vec![TypeApplication { - name: "variant".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: Some(vec![1]), - }, - TypeDeclaration { - type_id: 3, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 2, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 3, - type_arguments: None, - }]), - }; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!( - result, - ParamType::Enum { - variants: EnumVariants::new(vec![ParamType::U8])?, - generics: vec![ParamType::U8] - } - ); - - Ok(()) - } - - #[test] - fn handles_tuples() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 3, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 2, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "str[15]".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 3, - type_field: "u8".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_application = TypeApplication { - name: "arg".to_string(), - type_id: 1, - type_arguments: None, - }; - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - assert_eq!( - result, - ParamType::Tuple(vec![ParamType::U8, ParamType::StringArray(15)]) - ); - - Ok(()) - } - - #[test] - fn ultimate_example() -> Result<()> { - // given - let declarations = [ - TypeDeclaration { - type_id: 1, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 11, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 11, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 2, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 4, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 24, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 3, - type_field: "(_, _)".to_string(), - components: Some(vec![ - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 5, - type_arguments: None, - }, - TypeApplication { - name: "__tuple_element".to_string(), - type_id: 13, - type_arguments: None, - }, - ]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 4, - type_field: "[_; 1]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 8, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 22, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 21, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 18, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 13, - type_arguments: None, - }]), - }]), - }]), - }]), - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 5, - type_field: "[_; 2]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 14, - type_arguments: None, - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 6, - type_field: "[_; 2]".to_string(), - components: Some(vec![TypeApplication { - name: "__array_element".to_string(), - type_id: 10, - type_arguments: None, - }]), - type_parameters: None, - }, - TypeDeclaration { - type_id: 7, - type_field: "b256".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 8, - type_field: "enum EnumWGeneric".to_string(), - components: Some(vec![ - TypeApplication { - name: "a".to_string(), - type_id: 25, - type_arguments: None, - }, - TypeApplication { - name: "b".to_string(), - type_id: 12, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![12]), - }, - TypeDeclaration { - type_id: 9, - type_field: "generic K".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 10, - type_field: "generic L".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 11, - type_field: "generic M".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 12, - type_field: "generic N".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 13, - type_field: "generic T".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 14, - type_field: "generic U".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 15, - type_field: "raw untyped ptr".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 16, - type_field: "str[2]".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 17, - type_field: "struct MegaExample".to_string(), - components: Some(vec![ - TypeApplication { - name: "a".to_string(), - type_id: 3, - type_arguments: None, - }, - TypeApplication { - name: "b".to_string(), - type_id: 23, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 2, - type_arguments: None, - }]), - }, - ]), - type_parameters: Some(vec![13, 14]), - }, - TypeDeclaration { - type_id: 18, - type_field: "struct PassTheGenericOn".to_string(), - components: Some(vec![TypeApplication { - name: "one".to_string(), - type_id: 20, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 9, - type_arguments: None, - }]), - }]), - type_parameters: Some(vec![9]), - }, - TypeDeclaration { - type_id: 19, - type_field: "struct std::vec::RawVec".to_string(), - components: Some(vec![ - TypeApplication { - name: "ptr".to_string(), - type_id: 15, - type_arguments: None, - }, - TypeApplication { - name: "cap".to_string(), - type_id: 25, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![13]), - }, - TypeDeclaration { - type_id: 20, - type_field: "struct SimpleGeneric".to_string(), - components: Some(vec![TypeApplication { - name: "single_generic_param".to_string(), - type_id: 13, - type_arguments: None, - }]), - type_parameters: Some(vec![13]), - }, - TypeDeclaration { - type_id: 21, - type_field: "struct StructWArrayGeneric".to_string(), - components: Some(vec![TypeApplication { - name: "a".to_string(), - type_id: 6, - type_arguments: None, - }]), - type_parameters: Some(vec![10]), - }, - TypeDeclaration { - type_id: 22, - type_field: "struct StructWTupleGeneric".to_string(), - components: Some(vec![TypeApplication { - name: "a".to_string(), - type_id: 1, - type_arguments: None, - }]), - type_parameters: Some(vec![11]), - }, - TypeDeclaration { - type_id: 23, - type_field: "struct std::vec::Vec".to_string(), - components: Some(vec![ - TypeApplication { - name: "buf".to_string(), - type_id: 19, - type_arguments: Some(vec![TypeApplication { - name: "".to_string(), - type_id: 13, - type_arguments: None, - }]), - }, - TypeApplication { - name: "len".to_string(), - type_id: 25, - type_arguments: None, - }, - ]), - type_parameters: Some(vec![13]), - }, - TypeDeclaration { - type_id: 24, - type_field: "u32".to_string(), - components: None, - type_parameters: None, - }, - TypeDeclaration { - type_id: 25, - type_field: "u64".to_string(), - components: None, - type_parameters: None, - }, - ]; - - let type_lookup = declarations - .into_iter() - .map(|decl| (decl.type_id, decl)) - .collect::>(); - - let type_application = TypeApplication { - name: "arg1".to_string(), - type_id: 17, - type_arguments: Some(vec![ - TypeApplication { - name: "".to_string(), - type_id: 16, - type_arguments: None, - }, - TypeApplication { - name: "".to_string(), - type_id: 7, - type_arguments: None, - }, - ]), - }; - - // when - let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; - - // then - let expected_param_type = { - let fields = vec![ParamType::Struct { - fields: vec![ParamType::StringArray(2)], - generics: vec![ParamType::StringArray(2)], - }]; - let pass_the_generic_on = ParamType::Struct { - fields, - generics: vec![ParamType::StringArray(2)], - }; - - let fields = vec![ParamType::Array(Box::from(pass_the_generic_on.clone()), 2)]; - let struct_w_array_generic = ParamType::Struct { - fields, - generics: vec![pass_the_generic_on], - }; - - let fields = vec![ParamType::Tuple(vec![ - struct_w_array_generic.clone(), - struct_w_array_generic.clone(), - ])]; - let struct_w_tuple_generic = ParamType::Struct { - fields, - generics: vec![struct_w_array_generic], - }; - - let types = vec![ParamType::U64, struct_w_tuple_generic.clone()]; - let fields = vec![ - ParamType::Tuple(vec![ - ParamType::Array(Box::from(ParamType::B256), 2), - ParamType::StringArray(2), - ]), - ParamType::Vector(Box::from(ParamType::Tuple(vec![ - ParamType::Array( - Box::from(ParamType::Enum { - variants: EnumVariants::new(types).unwrap(), - generics: vec![struct_w_tuple_generic], - }), - 1, - ), - ParamType::U32, - ]))), - ]; - ParamType::Struct { - fields, - generics: vec![ParamType::StringArray(2), ParamType::B256], - } - }; - - assert_eq!(result, expected_param_type); - - Ok(()) - } - - #[test] - fn validate_is_decodable_simple_types() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - assert!(ParamType::U8.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U16.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U32.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U64.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U128.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::U256.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::Bool.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::B256.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::Unit.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::StringSlice - .validate_is_decodable(max_depth) - .is_ok()); - assert!(ParamType::StringArray(10) - .validate_is_decodable(max_depth) - .is_ok()); - assert!(ParamType::RawSlice.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::Bytes.validate_is_decodable(max_depth).is_ok()); - assert!(ParamType::String.validate_is_decodable(max_depth).is_ok()); - Ok(()) - } - - #[test] - fn validate_is_decodable_complex_types_containing_bytes() -> Result<()> { - let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; - let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; - let max_depth = DecoderConfig::default().max_depth; - let nested_heap_type_error_message = |p: ParamType| { - format!( - "codec: type `{:?}` is not decodable: nested heap types are currently not \ - supported except in enums", - DebugWithDepth::new(&p, max_depth) - ) - }; - let cannot_be_decoded = |p: ParamType| { - assert_eq!( - p.validate_is_decodable(max_depth) - .expect_err(&format!("should not be decodable: {:?}", p)) - .to_string(), - nested_heap_type_error_message(p) - ) - }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - - can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); - cannot_be_decoded(ParamType::Array(Box::new(ParamType::Bytes), 10usize)); - - can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); - cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Bytes))); - - can_be_decoded(ParamType::Struct { - generics: param_types_no_bytes.clone(), - fields: param_types_no_bytes.clone(), - }); - cannot_be_decoded(ParamType::Struct { - fields: param_types_containing_bytes.clone(), - generics: param_types_no_bytes.clone(), - }); - - can_be_decoded(ParamType::Tuple(param_types_no_bytes.clone())); - cannot_be_decoded(ParamType::Tuple(param_types_containing_bytes.clone())); - - Ok(()) - } - - #[test] - fn validate_is_decodable_enum_containing_bytes() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; - let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; - let variants_no_bytes_type = EnumVariants::new(param_types_no_bytes.clone())?; - let variants_one_bytes_type = EnumVariants::new(param_types_containing_bytes.clone())?; - let variants_two_bytes_type = EnumVariants::new(vec![ParamType::Bytes, ParamType::Bytes])?; - - can_be_decoded(ParamType::Enum { - variants: variants_no_bytes_type.clone(), - generics: param_types_no_bytes.clone(), - }); - - can_be_decoded(ParamType::Enum { - variants: variants_one_bytes_type.clone(), - generics: param_types_no_bytes.clone(), - }); - - let expected = - "codec: enums currently support only one heap-type variant. Found: 2".to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_bytes_type.clone(), - generics: param_types_no_bytes.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("should not be decodable") - .to_string(), - expected - ); - - can_be_decoded(ParamType::Enum { - variants: variants_no_bytes_type, - generics: param_types_containing_bytes.clone(), - }); - - can_be_decoded(ParamType::Enum { - variants: variants_one_bytes_type, - generics: param_types_containing_bytes.clone(), - }); - - let expected = - "codec: enums currently support only one heap-type variant. Found: 2".to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_bytes_type.clone(), - generics: param_types_containing_bytes.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("should not be decodable") - .to_string(), - expected - ); - - Ok(()) - } - - #[test] - fn validate_is_decodable_complex_types_containing_string() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let base_string = ParamType::String; - let param_types_no_nested_string = vec![ParamType::U64, ParamType::U32]; - let param_types_nested_string = vec![ParamType::Unit, ParamType::Bool, base_string.clone()]; - let nested_heap_type_error_message = |p: ParamType| { - format!( - "codec: type `{:?}` is not decodable: nested heap types \ - are currently not supported except in enums", - DebugWithDepth::new(&p, max_depth) - ) - }; - let cannot_be_decoded = |p: ParamType| { - assert_eq!( - p.validate_is_decodable(max_depth) - .expect_err(&format!("should not be decodable: {:?}", p)) - .to_string(), - nested_heap_type_error_message(p) - ) - }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - - can_be_decoded(base_string.clone()); - cannot_be_decoded(ParamType::Vector(Box::from(base_string.clone()))); - - can_be_decoded(ParamType::Array(Box::from(ParamType::U8), 10)); - cannot_be_decoded(ParamType::Array(Box::from(base_string), 10)); - - can_be_decoded(ParamType::Tuple(param_types_no_nested_string.clone())); - cannot_be_decoded(ParamType::Tuple(param_types_nested_string.clone())); - - can_be_decoded(ParamType::Struct { - generics: param_types_no_nested_string.clone(), - fields: param_types_no_nested_string.clone(), - }); - - can_be_decoded(ParamType::Struct { - generics: param_types_nested_string.clone(), - fields: param_types_no_nested_string.clone(), - }); - - cannot_be_decoded(ParamType::Struct { - generics: param_types_no_nested_string.clone(), - fields: param_types_nested_string.clone(), - }); - - Ok(()) - } - - #[test] - fn validate_is_decodable_enum_containing_string() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - let param_types_containing_string = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; - let param_types_no_string = vec![ParamType::U64, ParamType::U32]; - let variants_no_string_type = EnumVariants::new(param_types_no_string.clone())?; - let variants_one_string_type = EnumVariants::new(param_types_containing_string.clone())?; - let variants_two_string_type = EnumVariants::new(vec![ParamType::Bytes, ParamType::Bytes])?; - - can_be_decoded(ParamType::Enum { - variants: variants_no_string_type.clone(), - generics: param_types_no_string.clone(), - }); - - can_be_decoded(ParamType::Enum { - variants: variants_one_string_type.clone(), - generics: param_types_no_string.clone(), - }); - - let expected = - "codec: enums currently support only one heap-type variant. Found: 2".to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_string_type.clone(), - generics: param_types_no_string.clone(), - } - .validate_is_decodable(1) - .expect_err("should not be decodable") - .to_string(), - expected - ); - - can_be_decoded(ParamType::Enum { - variants: variants_no_string_type, - generics: param_types_containing_string.clone(), - }); - - can_be_decoded(ParamType::Enum { - variants: variants_one_string_type, - generics: param_types_containing_string.clone(), - }); - - let expected = - "codec: enums currently support only one heap-type variant. Found: 2".to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_string_type.clone(), - generics: param_types_containing_string.clone(), - } - .validate_is_decodable(1) - .expect_err("should not be decodable") - .to_string(), - expected - ); - - Ok(()) - } - - #[test] - fn validate_is_decodable_complex_types_containing_vector() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let param_types_containing_vector = vec![ - ParamType::Vector(Box::new(ParamType::U32)), - ParamType::U64, - ParamType::Bool, - ]; - let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; - let nested_heap_type_error_message = |p: ParamType| { - format!( - "codec: type `{:?}` is not decodable: nested heap types \ - are currently not supported except in enums", - DebugWithDepth::new(&p, max_depth) - ) - }; - - let cannot_be_decoded = |p: ParamType| { - assert_eq!( - p.validate_is_decodable(max_depth) - .expect_err(&format!("should not be decodable: {:?}", p)) - .to_string(), - nested_heap_type_error_message(p) - ) - }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - - can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); - cannot_be_decoded(ParamType::Array( - Box::new(ParamType::Vector(Box::new(ParamType::U8))), - 10usize, - )); - - can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); - cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Vector(Box::new( - ParamType::U8, - ))))); - - can_be_decoded(ParamType::Struct { - fields: param_types_no_vector.clone(), - generics: param_types_no_vector.clone(), - }); - cannot_be_decoded(ParamType::Struct { - generics: param_types_no_vector.clone(), - fields: param_types_containing_vector.clone(), - }); - - can_be_decoded(ParamType::Tuple(param_types_no_vector.clone())); - cannot_be_decoded(ParamType::Tuple(param_types_containing_vector.clone())); - - Ok(()) - } - - #[test] - fn validate_is_decodable_enum_containing_vector() -> Result<()> { - let max_depth = DecoderConfig::default().max_depth; - let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); - let param_types_containing_vector = vec![ - ParamType::Vector(Box::new(ParamType::Bool)), - ParamType::U64, - ParamType::Bool, - ]; - let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; - let variants_no_vector_type = EnumVariants::new(param_types_no_vector.clone())?; - let variants_one_vector_type = EnumVariants::new(param_types_containing_vector.clone())?; - let variants_two_vector_type = EnumVariants::new(vec![ - ParamType::Vector(Box::new(ParamType::U8)), - ParamType::Vector(Box::new(ParamType::U16)), - ])?; - - can_be_decoded(ParamType::Enum { - variants: variants_no_vector_type.clone(), - generics: param_types_no_vector.clone(), - }); - - can_be_decoded(ParamType::Enum { - variants: variants_one_vector_type.clone(), - generics: param_types_no_vector.clone(), - }); - - let expected = - "codec: enums currently support only one heap-type variant. Found: 2".to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_vector_type.clone(), - generics: param_types_no_vector.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("should not be decodable") - .to_string(), - expected - ); - can_be_decoded(ParamType::Enum { - variants: variants_no_vector_type, - generics: param_types_containing_vector.clone(), - }); - can_be_decoded(ParamType::Enum { - variants: variants_one_vector_type, - generics: param_types_containing_vector.clone(), - }); - let expected = - "codec: enums currently support only one heap-type variant. Found: 2".to_string(); - assert_eq!( - ParamType::Enum { - variants: variants_two_vector_type.clone(), - generics: param_types_containing_vector.clone(), - } - .validate_is_decodable(max_depth) - .expect_err("should not be decodable") - .to_string(), - expected - ); - - Ok(()) - } - #[test] - fn try_vector_is_type_path_backward_compatible() { - // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. - let the_type = given_generic_type_with_path("Vec"); - - let param_type = try_vector(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); - } - - #[test] - fn try_vector_correctly_resolves_param_type() { - let the_type = given_generic_type_with_path("std::vec::Vec"); - - let param_type = try_vector(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); - } - - #[test] - fn try_bytes_is_type_path_backward_compatible() { - // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. - let the_type = given_type_with_path("Bytes"); - - let param_type = try_bytes(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Bytes); - } - - #[test] - fn try_bytes_correctly_resolves_param_type() { - let the_type = given_type_with_path("std::bytes::Bytes"); - - let param_type = try_bytes(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::Bytes); - } - - #[test] - fn try_raw_slice_correctly_resolves_param_type() { - let the_type = Type { - type_field: "raw untyped slice".to_string(), - generic_params: vec![], - components: vec![], - }; - - let param_type = try_raw_slice(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::RawSlice); - } - - #[test] - fn try_std_string_correctly_resolves_param_type() { - let the_type = given_type_with_path("std::string::String"); - - let param_type = try_std_string(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::String); - } - - #[test] - fn try_std_string_is_type_path_backward_compatible() { - // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. - let the_type = given_type_with_path("String"); - - let param_type = try_std_string(&the_type).unwrap().unwrap(); - - assert_eq!(param_type, ParamType::String); - } - - #[test] - fn test_compute_encoding_in_bytes_overflows() -> Result<()> { - let overflows = |p: ParamType| { - let error = p.compute_encoding_in_bytes().unwrap_err(); - let overflow_error = error!( - Codec, - "reached overflow while computing encoding size for {:?}", p - ); - assert_eq!(error.to_string(), overflow_error.to_string()); - }; - let tuple_with_fields_too_wide = ParamType::Tuple(vec![ - ParamType::StringArray(12514849900987264429), - ParamType::StringArray(7017071859781709229), - ]); - overflows(tuple_with_fields_too_wide); - - let struct_with_fields_too_wide = ParamType::Struct { - fields: vec![ - ParamType::StringArray(12514849900987264429), - ParamType::StringArray(7017071859781709229), - ], - generics: vec![], - }; - overflows(struct_with_fields_too_wide); - - let enum_with_variants_too_wide = ParamType::Enum { - variants: EnumVariants::new(vec![ParamType::StringArray(usize::MAX - 8)]).unwrap(), - generics: vec![], - }; - overflows(enum_with_variants_too_wide); - - let array_too_big = ParamType::Array(Box::new(ParamType::U64), usize::MAX); - overflows(array_too_big); - - let string_array_too_big = ParamType::StringArray(usize::MAX); - overflows(string_array_too_big); - Ok(()) - } - - #[test] - fn calculate_num_of_elements() -> Result<()> { - let failing_param_type = ParamType::Array(Box::new(ParamType::U16), usize::MAX); - assert!(ParamType::calculate_num_of_elements(&failing_param_type, 0) - .unwrap_err() - .to_string() - .contains("reached overflow")); - - let zero_sized_type = ParamType::Array(Box::new(ParamType::StringArray(0)), 1000); - assert!(ParamType::calculate_num_of_elements(&zero_sized_type, 0) - .unwrap_err() - .to_string() - .contains("the type is zero-sized")); - - assert!(ParamType::calculate_num_of_elements(&ParamType::U16, 9) - .unwrap_err() - .to_string() - .contains("1 extra bytes detected while decoding heap type")); - - Ok(()) - } - - fn given_type_with_path(path: &str) -> Type { - Type { - type_field: format!("struct {path}"), - generic_params: vec![], - components: vec![], - } - } - - fn given_generic_type_with_path(path: &str) -> Type { - Type { - type_field: format!("struct {path}"), - generic_params: vec![Type { - type_field: "u8".to_string(), - generic_params: vec![], - components: vec![], - }], - components: vec![], - } - } -} +pub use enum_variants::*; +pub use param_type::*; diff --git a/packages/fuels-core/src/types/param_types/debug_with_depth.rs b/packages/fuels-core/src/types/param_types/debug_with_depth.rs new file mode 100644 index 0000000000..7eef88ba40 --- /dev/null +++ b/packages/fuels-core/src/types/param_types/debug_with_depth.rs @@ -0,0 +1,256 @@ +use std::fmt; + +use crate::types::param_types::ParamType; + +/// Allows `Debug` formatting of arbitrary-depth nested `ParamTypes` by +/// omitting the details of inner types if max depth is exceeded. +pub(crate) struct DebugWithDepth<'a> { + param_type: &'a ParamType, + depth_left: usize, +} + +impl<'a> DebugWithDepth<'a> { + pub(crate) fn new(param_type: &'a ParamType, depth_left: usize) -> Self { + Self { + param_type, + depth_left, + } + } + + fn descend(&'a self, param_type: &'a ParamType) -> Self { + Self { + param_type, + depth_left: self.depth_left - 1, + } + } +} + +impl<'a> fmt::Debug for DebugWithDepth<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.depth_left == 0 { + return write!(f, "..."); + } + + match &self.param_type { + ParamType::Array(inner, size) => f + .debug_tuple("Array") + .field(&self.descend(inner)) + .field(&size) + .finish(), + ParamType::Struct { + fields, + generics, + name, + } => f + .debug_struct(name) + .field( + "fields", + &fields + .iter() + .map(|(_, field)| self.descend(field)) + .collect::>(), + ) + .field( + "generics", + &generics + .iter() + .map(|generic| self.descend(generic)) + .collect::>(), + ) + .finish(), + ParamType::Enum { + enum_variants, + generics, + name, + } => f + .debug_struct(name) + .field( + "variants", + &enum_variants + .param_types() + .map(|variant| self.descend(variant)) + .collect::>(), + ) + .field( + "generics", + &generics + .iter() + .map(|generic| self.descend(generic)) + .collect::>(), + ) + .finish(), + ParamType::Tuple(inner) => f + .debug_tuple("Tuple") + .field( + &inner + .iter() + .map(|param_type| self.descend(param_type)) + .collect::>(), + ) + .finish(), + ParamType::Vector(inner) => { + f.debug_tuple("Vector").field(&self.descend(inner)).finish() + } + _ => write!(f, "{:?}", self.param_type), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{codec::DecoderConfig, to_named, types::errors::Result}; + + #[test] + fn validate_is_decodable_complex_types_containing_bytes() -> Result<()> { + let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; + let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; + let max_depth = DecoderConfig::default().max_depth; + let nested_heap_type_error_message = |p: ParamType| { + format!( + "codec: type `{:?}` is not decodable: nested heap types are currently not \ + supported except in enums", + DebugWithDepth::new(&p, max_depth) + ) + }; + let cannot_be_decoded = |p: ParamType| { + assert_eq!( + p.validate_is_decodable(max_depth) + .expect_err(&format!("should not be decodable: {:?}", p)) + .to_string(), + nested_heap_type_error_message(p) + ) + }; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + + can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); + cannot_be_decoded(ParamType::Array(Box::new(ParamType::Bytes), 10usize)); + + can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); + cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Bytes))); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_bytes), + generics: param_types_no_bytes.clone(), + }); + cannot_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_containing_bytes), + generics: param_types_no_bytes.clone(), + }); + + can_be_decoded(ParamType::Tuple(param_types_no_bytes.clone())); + cannot_be_decoded(ParamType::Tuple(param_types_containing_bytes.clone())); + + Ok(()) + } + + #[test] + fn validate_is_decodable_complex_types_containing_string() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let base_string = ParamType::String; + let param_types_no_nested_string = vec![ParamType::U64, ParamType::U32]; + let param_types_nested_string = vec![ParamType::Unit, ParamType::Bool, base_string.clone()]; + let nested_heap_type_error_message = |p: ParamType| { + format!( + "codec: type `{:?}` is not decodable: nested heap types \ + are currently not supported except in enums", + DebugWithDepth::new(&p, max_depth) + ) + }; + let cannot_be_decoded = |p: ParamType| { + assert_eq!( + p.validate_is_decodable(max_depth) + .expect_err(&format!("should not be decodable: {:?}", p)) + .to_string(), + nested_heap_type_error_message(p) + ) + }; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + + can_be_decoded(base_string.clone()); + cannot_be_decoded(ParamType::Vector(Box::from(base_string.clone()))); + + can_be_decoded(ParamType::Array(Box::from(ParamType::U8), 10)); + cannot_be_decoded(ParamType::Array(Box::from(base_string), 10)); + + can_be_decoded(ParamType::Tuple(param_types_no_nested_string.clone())); + cannot_be_decoded(ParamType::Tuple(param_types_nested_string.clone())); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_nested_string), + generics: param_types_no_nested_string.clone(), + }); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_nested_string), + generics: param_types_nested_string.clone(), + }); + + cannot_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_nested_string), + generics: param_types_no_nested_string.clone(), + }); + + Ok(()) + } + + #[test] + fn validate_is_decodable_complex_types_containing_vector() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let param_types_containing_vector = vec![ + ParamType::Vector(Box::new(ParamType::U32)), + ParamType::U64, + ParamType::Bool, + ]; + let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; + let nested_heap_type_error_message = |p: ParamType| { + format!( + "codec: type `{:?}` is not decodable: nested heap types \ + are currently not supported except in enums", + DebugWithDepth::new(&p, max_depth) + ) + }; + + let cannot_be_decoded = |p: ParamType| { + assert_eq!( + p.validate_is_decodable(max_depth) + .expect_err(&format!("should not be decodable: {:?}", p)) + .to_string(), + nested_heap_type_error_message(p) + ) + }; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + + can_be_decoded(ParamType::Array(Box::new(ParamType::U64), 10usize)); + cannot_be_decoded(ParamType::Array( + Box::new(ParamType::Vector(Box::new(ParamType::U8))), + 10usize, + )); + + can_be_decoded(ParamType::Vector(Box::new(ParamType::U64))); + cannot_be_decoded(ParamType::Vector(Box::new(ParamType::Vector(Box::new( + ParamType::U8, + ))))); + + can_be_decoded(ParamType::Struct { + name: "".to_string(), + fields: to_named(¶m_types_no_vector), + generics: param_types_no_vector.clone(), + }); + cannot_be_decoded(ParamType::Struct { + name: "".to_string(), + generics: param_types_no_vector.clone(), + fields: to_named(¶m_types_containing_vector), + }); + + can_be_decoded(ParamType::Tuple(param_types_no_vector.clone())); + cannot_be_decoded(ParamType::Tuple(param_types_containing_vector.clone())); + + Ok(()) + } +} diff --git a/packages/fuels-core/src/types/enum_variants.rs b/packages/fuels-core/src/types/param_types/enum_variants.rs similarity index 55% rename from packages/fuels-core/src/types/enum_variants.rs rename to packages/fuels-core/src/types/param_types/enum_variants.rs index 5b19be7daf..fa82824ce9 100644 --- a/packages/fuels-core/src/types/enum_variants.rs +++ b/packages/fuels-core/src/types/param_types/enum_variants.rs @@ -2,49 +2,53 @@ use crate::{ constants::ENUM_DISCRIMINANT_BYTE_WIDTH, types::{ errors::{error, Result}, - param_types::ParamType, + param_types::{NamedParamType, ParamType}, }, utils::checked_round_up_to_word_alignment, }; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct EnumVariants { - param_types: Vec, + variants: Vec, } impl EnumVariants { - pub fn new(param_types: Vec) -> Result { - if param_types.is_empty() { + pub fn new(variants: Vec) -> Result { + if variants.is_empty() { return Err(error!(Other, "enum variants cannot be empty!")); } - Ok(EnumVariants { param_types }) + + Ok(EnumVariants { variants }) + } + + pub fn variants(&self) -> &Vec { + &self.variants } - pub fn param_types(&self) -> &[ParamType] { - &self.param_types + pub fn param_types(&self) -> impl Iterator { + self.variants.iter().map(|(_, param_type)| param_type) } - pub fn param_type_of_variant(&self, discriminant: u64) -> Result<&ParamType> { - self.param_types.get(discriminant as usize).ok_or_else(|| { + pub fn select_variant(&self, discriminant: u64) -> Result<&NamedParamType> { + self.variants.get(discriminant as usize).ok_or_else(|| { error!( Other, "discriminant `{discriminant}` doesn't point to any variant: {:?}", - self.param_types() + self.variants() ) }) } pub fn heap_type_variant(&self) -> Option<(u64, &ParamType)> { self.param_types() - .iter() .enumerate() .find_map(|(d, p)| p.is_extra_receipt_needed(false).then_some((d as u64, p))) } pub fn only_units_inside(&self) -> bool { - self.param_types + self.variants .iter() - .all(|param_type| *param_type == ParamType::Unit) + .all(|(_, param_type)| *param_type == ParamType::Unit) } /// Calculates how many bytes are needed to encode an enum. @@ -53,7 +57,7 @@ impl EnumVariants { return Ok(ENUM_DISCRIMINANT_BYTE_WIDTH); } - let width = self.param_types().iter().try_fold(0, |a, p| -> Result<_> { + let width = self.param_types().try_fold(0, |a, p| -> Result<_> { let size = p.compute_encoding_in_bytes()?; Ok(a.max(size)) })?; @@ -77,28 +81,37 @@ impl EnumVariants { #[cfg(test)] mod tests { use super::*; + use crate::to_named; #[test] fn test_get_heap_type_variant_discriminant() -> Result<()> { - let param_types = vec![ - ParamType::U64, - ParamType::Bool, - ParamType::Vector(Box::from(ParamType::U64)), - ]; - let variants = EnumVariants::new(param_types)?; - assert_eq!(variants.heap_type_variant().unwrap().0, 2); - - let param_types = vec![ - ParamType::Vector(Box::from(ParamType::U64)), - ParamType::U64, - ParamType::Bool, - ]; - let variants = EnumVariants::new(param_types)?; - assert_eq!(variants.heap_type_variant().unwrap().0, 0); - - let param_types = vec![ParamType::U64, ParamType::Bool]; - let variants = EnumVariants::new(param_types)?; - assert!(variants.heap_type_variant().is_none()); + { + let variants = to_named(&[ + ParamType::U64, + ParamType::Bool, + ParamType::Vector(Box::from(ParamType::U64)), + ]); + let enum_variants = EnumVariants::new(variants)?; + + assert_eq!(enum_variants.heap_type_variant().unwrap().0, 2); + } + { + let variants = to_named(&[ + ParamType::Vector(Box::from(ParamType::U64)), + ParamType::U64, + ParamType::Bool, + ]); + let enum_variants = EnumVariants::new(variants)?; + + assert_eq!(enum_variants.heap_type_variant().unwrap().0, 0); + } + { + let variants = to_named(&[ParamType::U64, ParamType::Bool]); + let enum_variants = EnumVariants::new(variants)?; + + assert!(enum_variants.heap_type_variant().is_none()); + } + Ok(()) } } diff --git a/packages/fuels-core/src/types/param_types/from_type_application.rs b/packages/fuels-core/src/types/param_types/from_type_application.rs new file mode 100644 index 0000000000..96139edc9c --- /dev/null +++ b/packages/fuels-core/src/types/param_types/from_type_application.rs @@ -0,0 +1,1196 @@ +use std::{collections::HashMap, iter::zip}; + +use fuel_abi_types::{ + abi::program::{TypeApplication, TypeDeclaration}, + utils::{extract_array_len, extract_generic_name, extract_str_len, has_tuple_format}, +}; + +use crate::types::{ + errors::{error, Error, Result}, + param_types::{EnumVariants, NamedParamType, ParamType}, +}; + +impl ParamType { + /// For when you need to convert a ABI JSON's TypeApplication into a ParamType. + /// + /// # Arguments + /// + /// * `type_application`: The TypeApplication you wish to convert into a ParamType + /// * `type_lookup`: A HashMap of TypeDeclarations mentioned in the + /// TypeApplication where the type id is the key. + pub fn try_from_type_application( + type_application: &TypeApplication, + type_lookup: &HashMap, + ) -> Result { + Type::try_from(type_application, type_lookup)?.try_into() + } +} + +#[derive(Debug, Clone)] +struct Type { + name: String, + type_field: String, + generic_params: Vec, + components: Vec, +} + +impl Type { + /// Will recursively drill down the given generic parameters until all types are + /// resolved. + /// + /// # Arguments + /// + /// * `type_application`: the type we wish to resolve + /// * `types`: all types used in the function call + pub fn try_from( + type_application: &TypeApplication, + type_lookup: &HashMap, + ) -> Result { + Self::resolve(type_application, type_lookup, &[]) + } + + fn resolve( + type_application: &TypeApplication, + type_lookup: &HashMap, + parent_generic_params: &[(usize, Type)], + ) -> Result { + let type_declaration = type_lookup.get(&type_application.type_id).ok_or_else(|| { + error!( + Codec, + "type id {} not found in type lookup", type_application.type_id + ) + })?; + + if extract_generic_name(&type_declaration.type_field).is_some() { + let (_, generic_type) = parent_generic_params + .iter() + .find(|(id, _)| *id == type_application.type_id) + .ok_or_else(|| { + error!( + Codec, + "type id {} not found in parent's generic parameters", + type_application.type_id + ) + })?; + + // The generic will inherit the name from the parent `type_application` + return Ok(Self { + name: type_application.name.clone(), + ..generic_type.clone() + }); + } + + // Figure out what does the current type do with the inherited generic + // parameters and reestablish the mapping since the current type might have + // renamed the inherited generic parameters. + let generic_params_lookup = Self::determine_generics_for_type( + type_application, + type_lookup, + type_declaration, + parent_generic_params, + )?; + + // Resolve the enclosed components (if any) with the newly resolved generic + // parameters. + let components = type_declaration + .components + .iter() + .flatten() + .map(|component| Self::resolve(component, type_lookup, &generic_params_lookup)) + .collect::>>()?; + + Ok(Type { + name: type_application.name.clone(), + type_field: type_declaration.type_field.clone(), + components, + generic_params: generic_params_lookup + .into_iter() + .map(|(_, ty)| ty) + .collect(), + }) + } + + /// For the given type generates generic_type_id -> Type mapping describing to + /// which types generic parameters should be resolved. + /// + /// # Arguments + /// + /// * `type_application`: The type on which the generic parameters are defined. + /// * `types`: All types used. + /// * `parent_generic_params`: The generic parameters as inherited from the + /// enclosing type (a struct/enum/array etc.). + fn determine_generics_for_type( + type_application: &TypeApplication, + type_lookup: &HashMap, + type_declaration: &TypeDeclaration, + parent_generic_params: &[(usize, Type)], + ) -> Result> { + match &type_declaration.type_parameters { + // The presence of type_parameters indicates that the current type + // (a struct or an enum) defines some generic parameters (i.e. SomeStruct). + Some(params) if !params.is_empty() => { + // Determine what Types the generics will resolve to. + let generic_params_from_current_type = type_application + .type_arguments + .iter() + .flatten() + .map(|ty| Self::resolve(ty, type_lookup, parent_generic_params)) + .collect::>>()?; + + let generics_to_use = if !generic_params_from_current_type.is_empty() { + generic_params_from_current_type + } else { + // Types such as arrays and enums inherit and forward their + // generic parameters, without declaring their own. + parent_generic_params + .iter() + .map(|(_, ty)| ty) + .cloned() + .collect() + }; + + // All inherited but unused generic types are dropped. The rest are + // re-mapped to new type_ids since child types are free to rename + // the generic parameters as they see fit -- i.e. + // struct ParentStruct{ + // b: ChildStruct + // } + // struct ChildStruct { + // c: K + // } + + Ok(zip(params.clone(), generics_to_use).collect()) + } + _ => Ok(parent_generic_params.to_vec()), + } + } +} + +impl TryFrom for ParamType { + type Error = Error; + + fn try_from(value: Type) -> Result { + (&value).try_into() + } +} + +impl TryFrom<&Type> for ParamType { + type Error = Error; + + fn try_from(the_type: &Type) -> Result { + let matched_param_type = [ + try_primitive, + try_array, + try_str_array, + try_str_slice, + try_tuple, + try_vector, + try_bytes, + try_std_string, + try_raw_slice, + try_enum, + try_u128, + try_struct, + ] + .into_iter() + .map(|fun| fun(the_type)) + .flat_map(|result| result.ok().flatten()) + .next(); + + matched_param_type.map(Ok).unwrap_or_else(|| { + Err(error!( + Codec, + "type {} couldn't be converted into a ParamType", the_type.type_field + )) + }) + } +} + +fn convert_into_param_types(coll: &[Type]) -> Result> { + coll.iter().map(ParamType::try_from).collect() +} + +fn named_param_types(coll: &[Type]) -> Result> { + coll.iter() + .map(|ttype| Ok((ttype.name.clone(), ttype.try_into()?))) + .collect() +} + +fn try_struct(the_type: &Type) -> Result> { + let field = &the_type.type_field; + if field.starts_with("struct ") { + let fields = named_param_types(&the_type.components)?; + let generics = param_types(&the_type.generic_params)?; + + return Ok(Some(ParamType::Struct { + name: the_type + .type_field + .strip_prefix("struct ") + .expect("has `struct`") + .to_string(), + fields, + generics, + })); + } + + Ok(None) +} + +fn try_vector(the_type: &Type) -> Result> { + if !["struct std::vec::Vec", "struct Vec"].contains(&the_type.type_field.as_str()) { + return Ok(None); + } + + if the_type.generic_params.len() != 1 { + return Err(error!( + Codec, + "`Vec` must have exactly one generic argument for its type. Found: `{:?}`", + the_type.generic_params + )); + } + + let vec_elem_type = convert_into_param_types(&the_type.generic_params)?.remove(0); + + Ok(Some(ParamType::Vector(Box::new(vec_elem_type)))) +} + +fn try_u128(the_type: &Type) -> Result> { + Ok(["struct std::u128::U128", "struct U128"] + .contains(&the_type.type_field.as_str()) + .then_some(ParamType::U128)) +} + +fn try_bytes(the_type: &Type) -> Result> { + Ok(["struct std::bytes::Bytes", "struct Bytes"] + .contains(&the_type.type_field.as_str()) + .then_some(ParamType::Bytes)) +} + +fn try_std_string(the_type: &Type) -> Result> { + Ok(["struct std::string::String", "struct String"] + .contains(&the_type.type_field.as_str()) + .then_some(ParamType::String)) +} + +fn try_raw_slice(the_type: &Type) -> Result> { + Ok((the_type.type_field == "raw untyped slice").then_some(ParamType::RawSlice)) +} + +fn try_enum(the_type: &Type) -> Result> { + let field = &the_type.type_field; + if field.starts_with("enum ") { + let components = named_param_types(&the_type.components)?; + let enum_variants = EnumVariants::new(components)?; + let generics = param_types(&the_type.generic_params)?; + + return Ok(Some(ParamType::Enum { + name: field.strip_prefix("enum ").expect("has `enum`").to_string(), + enum_variants, + generics, + })); + } + + Ok(None) +} + +fn try_tuple(the_type: &Type) -> Result> { + let result = if has_tuple_format(&the_type.type_field) { + let tuple_elements = param_types(&the_type.components)?; + Some(ParamType::Tuple(tuple_elements)) + } else { + None + }; + + Ok(result) +} + +fn param_types(coll: &[Type]) -> Result> { + coll.iter().map(|t| t.try_into()).collect() +} + +fn try_str_array(the_type: &Type) -> Result> { + Ok(extract_str_len(&the_type.type_field).map(ParamType::StringArray)) +} + +fn try_str_slice(the_type: &Type) -> Result> { + Ok(if the_type.type_field == "str" { + Some(ParamType::StringSlice) + } else { + None + }) +} + +fn try_array(the_type: &Type) -> Result> { + if let Some(len) = extract_array_len(&the_type.type_field) { + return match the_type.components.as_slice() { + [single_type] => { + let array_type = single_type.try_into()?; + Ok(Some(ParamType::Array(Box::new(array_type), len))) + } + _ => Err(error!( + Codec, + "array must have elements of exactly one type. Array types: {:?}", + the_type.components + )), + }; + } + Ok(None) +} + +fn try_primitive(the_type: &Type) -> Result> { + let result = match the_type.type_field.as_str() { + "bool" => Some(ParamType::Bool), + "u8" => Some(ParamType::U8), + "u16" => Some(ParamType::U16), + "u32" => Some(ParamType::U32), + "u64" => Some(ParamType::U64), + "u256" => Some(ParamType::U256), + "b256" => Some(ParamType::B256), + "()" => Some(ParamType::Unit), + "str" => Some(ParamType::StringSlice), + _ => None, + }; + + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn handles_simple_types() -> Result<()> { + let parse_param_type = |type_field: &str| { + let type_application = TypeApplication { + name: "".to_string(), + type_id: 0, + type_arguments: None, + }; + + let declarations = [TypeDeclaration { + type_id: 0, + type_field: type_field.to_string(), + components: None, + type_parameters: None, + }]; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + ParamType::try_from_type_application(&type_application, &type_lookup) + }; + + assert_eq!(parse_param_type("()")?, ParamType::Unit); + assert_eq!(parse_param_type("bool")?, ParamType::Bool); + assert_eq!(parse_param_type("u8")?, ParamType::U8); + assert_eq!(parse_param_type("u16")?, ParamType::U16); + assert_eq!(parse_param_type("u32")?, ParamType::U32); + assert_eq!(parse_param_type("u64")?, ParamType::U64); + assert_eq!(parse_param_type("u256")?, ParamType::U256); + assert_eq!(parse_param_type("b256")?, ParamType::B256); + assert_eq!(parse_param_type("str[21]")?, ParamType::StringArray(21)); + assert_eq!(parse_param_type("str")?, ParamType::StringSlice); + + Ok(()) + } + + #[test] + fn handles_arrays() -> Result<()> { + // given + let type_application = TypeApplication { + name: "".to_string(), + type_id: 0, + type_arguments: None, + }; + + let declarations = [ + TypeDeclaration { + type_id: 0, + type_field: "[_; 10]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 1, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!(result, ParamType::Array(Box::new(ParamType::U8), 10)); + + Ok(()) + } + + #[test] + fn handles_vectors() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "raw untyped ptr".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 3, + type_field: "struct std::vec::RawVec".to_string(), + components: Some(vec![ + TypeApplication { + name: "ptr".to_string(), + type_id: 2, + type_arguments: None, + }, + TypeApplication { + name: "cap".to_string(), + type_id: 5, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 4, + type_field: "struct std::vec::Vec".to_string(), + components: Some(vec![ + TypeApplication { + name: "buf".to_string(), + type_id: 3, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 1, + type_arguments: None, + }]), + }, + TypeApplication { + name: "len".to_string(), + type_id: 5, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 5, + type_field: "u64".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 6, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 4, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 6, + type_arguments: None, + }]), + }; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!(result, ParamType::Vector(Box::new(ParamType::U8))); + + Ok(()) + } + + #[test] + fn handles_structs() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "struct SomeStruct".to_string(), + components: Some(vec![TypeApplication { + name: "field".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 3, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 2, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 3, + type_arguments: None, + }]), + }; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!( + result, + ParamType::Struct { + name: "SomeStruct".to_string(), + fields: vec![("field".to_string(), ParamType::U8)], + generics: vec![ParamType::U8] + } + ); + + Ok(()) + } + + #[test] + fn handles_enums() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "enum SomeEnum".to_string(), + components: Some(vec![TypeApplication { + name: "Variant".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: Some(vec![1]), + }, + TypeDeclaration { + type_id: 3, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 2, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 3, + type_arguments: None, + }]), + }; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!( + result, + ParamType::Enum { + name: "SomeEnum".to_string(), + enum_variants: EnumVariants::new(vec![("Variant".to_string(), ParamType::U8)])?, + generics: vec![ParamType::U8] + } + ); + + Ok(()) + } + + #[test] + fn handles_tuples() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 3, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 2, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "str[15]".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 3, + type_field: "u8".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_application = TypeApplication { + name: "arg".to_string(), + type_id: 1, + type_arguments: None, + }; + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + assert_eq!( + result, + ParamType::Tuple(vec![ParamType::U8, ParamType::StringArray(15)]) + ); + + Ok(()) + } + + #[test] + fn ultimate_example() -> Result<()> { + // given + let declarations = [ + TypeDeclaration { + type_id: 1, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 11, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 11, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 2, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 4, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 24, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 3, + type_field: "(_, _)".to_string(), + components: Some(vec![ + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 5, + type_arguments: None, + }, + TypeApplication { + name: "__tuple_element".to_string(), + type_id: 13, + type_arguments: None, + }, + ]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 4, + type_field: "[_; 1]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 8, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 22, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 21, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 18, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 13, + type_arguments: None, + }]), + }]), + }]), + }]), + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 5, + type_field: "[_; 2]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 14, + type_arguments: None, + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 6, + type_field: "[_; 2]".to_string(), + components: Some(vec![TypeApplication { + name: "__array_element".to_string(), + type_id: 10, + type_arguments: None, + }]), + type_parameters: None, + }, + TypeDeclaration { + type_id: 7, + type_field: "b256".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 8, + type_field: "enum EnumWGeneric".to_string(), + components: Some(vec![ + TypeApplication { + name: "A".to_string(), + type_id: 25, + type_arguments: None, + }, + TypeApplication { + name: "B".to_string(), + type_id: 12, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![12]), + }, + TypeDeclaration { + type_id: 9, + type_field: "generic K".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 10, + type_field: "generic L".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 11, + type_field: "generic M".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 12, + type_field: "generic N".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 13, + type_field: "generic T".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 14, + type_field: "generic U".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 15, + type_field: "raw untyped ptr".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 16, + type_field: "str[2]".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 17, + type_field: "struct MegaExample".to_string(), + components: Some(vec![ + TypeApplication { + name: "a".to_string(), + type_id: 3, + type_arguments: None, + }, + TypeApplication { + name: "b".to_string(), + type_id: 23, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 2, + type_arguments: None, + }]), + }, + ]), + type_parameters: Some(vec![13, 14]), + }, + TypeDeclaration { + type_id: 18, + type_field: "struct PassTheGenericOn".to_string(), + components: Some(vec![TypeApplication { + name: "one".to_string(), + type_id: 20, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 9, + type_arguments: None, + }]), + }]), + type_parameters: Some(vec![9]), + }, + TypeDeclaration { + type_id: 19, + type_field: "struct std::vec::RawVec".to_string(), + components: Some(vec![ + TypeApplication { + name: "ptr".to_string(), + type_id: 15, + type_arguments: None, + }, + TypeApplication { + name: "cap".to_string(), + type_id: 25, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![13]), + }, + TypeDeclaration { + type_id: 20, + type_field: "struct SimpleGeneric".to_string(), + components: Some(vec![TypeApplication { + name: "single_generic_param".to_string(), + type_id: 13, + type_arguments: None, + }]), + type_parameters: Some(vec![13]), + }, + TypeDeclaration { + type_id: 21, + type_field: "struct StructWArrayGeneric".to_string(), + components: Some(vec![TypeApplication { + name: "a".to_string(), + type_id: 6, + type_arguments: None, + }]), + type_parameters: Some(vec![10]), + }, + TypeDeclaration { + type_id: 22, + type_field: "struct StructWTupleGeneric".to_string(), + components: Some(vec![TypeApplication { + name: "a".to_string(), + type_id: 1, + type_arguments: None, + }]), + type_parameters: Some(vec![11]), + }, + TypeDeclaration { + type_id: 23, + type_field: "struct std::vec::Vec".to_string(), + components: Some(vec![ + TypeApplication { + name: "buf".to_string(), + type_id: 19, + type_arguments: Some(vec![TypeApplication { + name: "".to_string(), + type_id: 13, + type_arguments: None, + }]), + }, + TypeApplication { + name: "len".to_string(), + type_id: 25, + type_arguments: None, + }, + ]), + type_parameters: Some(vec![13]), + }, + TypeDeclaration { + type_id: 24, + type_field: "u32".to_string(), + components: None, + type_parameters: None, + }, + TypeDeclaration { + type_id: 25, + type_field: "u64".to_string(), + components: None, + type_parameters: None, + }, + ]; + + let type_lookup = declarations + .into_iter() + .map(|decl| (decl.type_id, decl)) + .collect::>(); + + let type_application = TypeApplication { + name: "arg1".to_string(), + type_id: 17, + type_arguments: Some(vec![ + TypeApplication { + name: "".to_string(), + type_id: 16, + type_arguments: None, + }, + TypeApplication { + name: "".to_string(), + type_id: 7, + type_arguments: None, + }, + ]), + }; + + // when + let result = ParamType::try_from_type_application(&type_application, &type_lookup)?; + + // then + let expected_param_type = { + let fields = vec![( + "one".to_string(), + ParamType::Struct { + name: "SimpleGeneric".to_string(), + fields: vec![( + "single_generic_param".to_string(), + ParamType::StringArray(2), + )], + generics: vec![ParamType::StringArray(2)], + }, + )]; + let pass_the_generic_on = ParamType::Struct { + name: "PassTheGenericOn".to_string(), + fields, + generics: vec![ParamType::StringArray(2)], + }; + + let fields = vec![( + "a".to_string(), + ParamType::Array(Box::from(pass_the_generic_on.clone()), 2), + )]; + let struct_w_array_generic = ParamType::Struct { + name: "StructWArrayGeneric".to_string(), + fields, + generics: vec![pass_the_generic_on], + }; + + let fields = vec![( + "a".to_string(), + ParamType::Tuple(vec![ + struct_w_array_generic.clone(), + struct_w_array_generic.clone(), + ]), + )]; + let struct_w_tuple_generic = ParamType::Struct { + name: "StructWTupleGeneric".to_string(), + fields, + generics: vec![struct_w_array_generic], + }; + + let types = vec![ + ("A".to_string(), ParamType::U64), + ("B".to_string(), struct_w_tuple_generic.clone()), + ]; + let fields = vec![ + ( + "a".to_string(), + ParamType::Tuple(vec![ + ParamType::Array(Box::from(ParamType::B256), 2), + ParamType::StringArray(2), + ]), + ), + ( + "b".to_string(), + ParamType::Vector(Box::from(ParamType::Tuple(vec![ + ParamType::Array( + Box::from(ParamType::Enum { + name: "EnumWGeneric".to_string(), + enum_variants: EnumVariants::new(types).unwrap(), + generics: vec![struct_w_tuple_generic], + }), + 1, + ), + ParamType::U32, + ]))), + ), + ]; + ParamType::Struct { + name: "MegaExample".to_string(), + fields, + generics: vec![ParamType::StringArray(2), ParamType::B256], + } + }; + + assert_eq!(result, expected_param_type); + + Ok(()) + } + + #[test] + fn try_vector_is_type_path_backward_compatible() { + // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. + let the_type = given_generic_type_with_path("Vec"); + + let param_type = try_vector(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); + } + + #[test] + fn try_vector_correctly_resolves_param_type() { + let the_type = given_generic_type_with_path("std::vec::Vec"); + + let param_type = try_vector(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Vector(Box::new(ParamType::U8))); + } + + #[test] + fn try_bytes_is_type_path_backward_compatible() { + // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. + let the_type = given_type_with_path("Bytes"); + + let param_type = try_bytes(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Bytes); + } + + #[test] + fn try_bytes_correctly_resolves_param_type() { + let the_type = given_type_with_path("std::bytes::Bytes"); + + let param_type = try_bytes(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::Bytes); + } + + #[test] + fn try_raw_slice_correctly_resolves_param_type() { + let the_type = Type { + name: "".to_string(), + type_field: "raw untyped slice".to_string(), + generic_params: vec![], + components: vec![], + }; + + let param_type = try_raw_slice(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::RawSlice); + } + + #[test] + fn try_std_string_correctly_resolves_param_type() { + let the_type = given_type_with_path("std::string::String"); + + let param_type = try_std_string(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::String); + } + + #[test] + fn try_std_string_is_type_path_backward_compatible() { + // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. + let the_type = given_type_with_path("String"); + + let param_type = try_std_string(&the_type).unwrap().unwrap(); + + assert_eq!(param_type, ParamType::String); + } + + fn given_type_with_path(path: &str) -> Type { + Type { + name: "".to_string(), + type_field: format!("struct {path}"), + generic_params: vec![], + components: vec![], + } + } + + fn given_generic_type_with_path(path: &str) -> Type { + Type { + name: "".to_string(), + type_field: format!("struct {path}"), + generic_params: vec![Type { + name: "".to_string(), + type_field: "u8".to_string(), + generic_params: vec![], + components: vec![], + }], + components: vec![], + } + } +} diff --git a/packages/fuels-core/src/types/param_types/param_type.rs b/packages/fuels-core/src/types/param_types/param_type.rs new file mode 100644 index 0000000000..9d7a2238a3 --- /dev/null +++ b/packages/fuels-core/src/types/param_types/param_type.rs @@ -0,0 +1,608 @@ +use itertools::chain; + +use crate::{ + checked_round_up_to_word_alignment, + types::{ + errors::{error, Result}, + param_types::{debug_with_depth::DebugWithDepth, EnumVariants}, + }, +}; + +pub type NamedParamType = (String, ParamType); + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum ParamType { + Unit, + Bool, + U8, + U16, + U32, + U64, + U128, + U256, + B256, + Bytes, + String, + RawSlice, + StringArray(usize), + StringSlice, + Tuple(Vec), + Array(Box, usize), + Vector(Box), + Struct { + name: String, + fields: Vec, + generics: Vec, + }, + Enum { + name: String, + enum_variants: EnumVariants, + generics: Vec, + }, +} + +pub enum ReturnLocation { + Return, + ReturnData, +} + +impl ParamType { + // Depending on the type, the returned value will be stored + // either in `Return` or `ReturnData`. + pub fn get_return_location(&self) -> ReturnLocation { + match self { + Self::Unit | Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::Bool => { + ReturnLocation::Return + } + + _ => ReturnLocation::ReturnData, + } + } + + /// Given a [ParamType], return the number of elements of that [ParamType] that can fit in + /// `available_bytes`: it is the length of the corresponding heap type. + pub fn calculate_num_of_elements( + param_type: &ParamType, + available_bytes: usize, + ) -> Result { + let memory_size = param_type.compute_encoding_in_bytes()?; + if memory_size == 0 { + return Err(error!( + Codec, + "cannot calculate the number of elements because the type is zero-sized" + )); + } + + let remainder = available_bytes % memory_size; + if remainder != 0 { + return Err(error!( + Codec, + "{remainder} extra bytes detected while decoding heap type" + )); + } + let num_of_elements = available_bytes + .checked_div(memory_size) + .ok_or_else(|| error!(Codec, "type {param_type:?} has a memory_size of 0"))?; + + Ok(num_of_elements) + } + + pub fn children_need_extra_receipts(&self) -> bool { + match self { + ParamType::Array(inner, _) | ParamType::Vector(inner) => { + inner.is_extra_receipt_needed(false) + } + ParamType::Struct { fields, .. } => fields + .iter() + .any(|(_, param_type)| param_type.is_extra_receipt_needed(false)), + ParamType::Enum { enum_variants, .. } => enum_variants + .param_types() + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::Tuple(inner_types) => inner_types + .iter() + .any(|param_type| param_type.is_extra_receipt_needed(false)), + _ => false, + } + } + + pub fn validate_is_decodable(&self, max_depth: usize) -> Result<()> { + if let ParamType::Enum { enum_variants, .. } = self { + let grandchildren_need_receipts = enum_variants + .param_types() + .any(|child| child.children_need_extra_receipts()); + if grandchildren_need_receipts { + return Err(error!( + Codec, + "enums currently support only one level deep heap types" + )); + } + + let num_of_children_needing_receipts = enum_variants + .param_types() + .filter(|param_type| param_type.is_extra_receipt_needed(false)) + .count(); + if num_of_children_needing_receipts > 1 { + return Err(error!( + Codec, + "enums currently support only one heap-type variant. Found: \ + {num_of_children_needing_receipts}" + )); + } + } else if self.children_need_extra_receipts() { + return Err(error!( + Codec, + "type `{:?}` is not decodable: nested heap types are currently not \ + supported except in enums", + DebugWithDepth::new(self, max_depth) + )); + } + self.compute_encoding_in_bytes()?; + + Ok(()) + } + + pub fn is_extra_receipt_needed(&self, top_level_type: bool) -> bool { + match self { + ParamType::Vector(_) | ParamType::Bytes | ParamType::String => true, + ParamType::Array(inner, _) => inner.is_extra_receipt_needed(false), + ParamType::Struct { + fields, generics, .. + } => chain!(fields.iter().map(|(_, param_type)| param_type), generics,) + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::Enum { + enum_variants, + generics, + .. + } => chain!(enum_variants.param_types(), generics) + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::Tuple(elements) => elements + .iter() + .any(|param_type| param_type.is_extra_receipt_needed(false)), + ParamType::RawSlice | ParamType::StringSlice => !top_level_type, + _ => false, + } + } + + /// Compute the inner memory size of a containing heap type (`Bytes` or `Vec`s). + pub fn heap_inner_element_size(&self, top_level_type: bool) -> Result> { + let heap_bytes_size = match &self { + ParamType::Vector(inner_param_type) => { + Some(inner_param_type.compute_encoding_in_bytes()?) + } + // `Bytes` type is byte-packed in the VM, so it's the size of an u8 + ParamType::Bytes | ParamType::String => Some(std::mem::size_of::()), + ParamType::StringSlice if !top_level_type => { + Some(ParamType::U8.compute_encoding_in_bytes()?) + } + ParamType::RawSlice if !top_level_type => { + Some(ParamType::U64.compute_encoding_in_bytes()?) + } + _ => None, + }; + Ok(heap_bytes_size) + } + + /// Calculates the number of bytes the VM expects this parameter to be encoded in. + pub fn compute_encoding_in_bytes(&self) -> Result { + let overflow_error = || { + error!( + Codec, + "reached overflow while computing encoding size for {:?}", self + ) + }; + match &self { + ParamType::Unit | ParamType::U8 | ParamType::Bool => Ok(1), + ParamType::U16 | ParamType::U32 | ParamType::U64 => Ok(8), + ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => Ok(16), + ParamType::U256 | ParamType::B256 => Ok(32), + ParamType::Vector(_) | ParamType::Bytes | ParamType::String => Ok(24), + ParamType::Array(param, count) => param + .compute_encoding_in_bytes()? + .checked_mul(*count) + .ok_or_else(overflow_error), + ParamType::StringArray(len) => { + checked_round_up_to_word_alignment(*len).map_err(|_| overflow_error()) + } + ParamType::Tuple(fields) => fields.iter().try_fold(0, |a: usize, param_type| { + let size = + checked_round_up_to_word_alignment(param_type.compute_encoding_in_bytes()?)?; + a.checked_add(size).ok_or_else(overflow_error) + }), + ParamType::Struct { fields, .. } => fields + .iter() + .map(|(_, param_type)| param_type) + .try_fold(0, |a: usize, param_type| { + let size = checked_round_up_to_word_alignment( + param_type.compute_encoding_in_bytes()?, + )?; + a.checked_add(size).ok_or_else(overflow_error) + }), + ParamType::Enum { enum_variants, .. } => enum_variants + .compute_enum_width_in_bytes() + .map_err(|_| overflow_error()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + checked_round_up_to_word_alignment, codec::DecoderConfig, constants::WORD_SIZE, to_named, + types::param_types::ParamType, + }; + + const WIDTH_OF_B256: usize = 32; + const WIDTH_OF_U32: usize = 8; + const WIDTH_OF_BOOL: usize = 1; + + #[test] + fn calculate_num_of_elements() -> Result<()> { + let failing_param_type = ParamType::Array(Box::new(ParamType::U16), usize::MAX); + assert!(ParamType::calculate_num_of_elements(&failing_param_type, 0) + .unwrap_err() + .to_string() + .contains("reached overflow")); + + let zero_sized_type = ParamType::Array(Box::new(ParamType::StringArray(0)), 1000); + assert!(ParamType::calculate_num_of_elements(&zero_sized_type, 0) + .unwrap_err() + .to_string() + .contains("the type is zero-sized")); + + assert!(ParamType::calculate_num_of_elements(&ParamType::U16, 9) + .unwrap_err() + .to_string() + .contains("1 extra bytes detected while decoding heap type")); + + Ok(()) + } + + #[test] + fn array_size_dependent_on_num_of_elements() { + const NUM_ELEMENTS: usize = 11; + let param = ParamType::Array(Box::new(ParamType::B256), NUM_ELEMENTS); + + let width = param.compute_encoding_in_bytes().unwrap(); + + let expected = NUM_ELEMENTS * WIDTH_OF_B256; + assert_eq!(expected, width); + } + + #[test] + fn string_size_dependent_on_num_of_elements() { + const NUM_ASCII_CHARS: usize = 9; + let param = ParamType::StringArray(NUM_ASCII_CHARS); + + let width = param.compute_encoding_in_bytes().unwrap(); + + assert_eq!(16, width); + } + + #[test] + fn structs_are_all_elements_combined_with_padding() -> Result<()> { + let inner_struct = ParamType::Struct { + name: "".to_string(), + fields: to_named(&[ParamType::U32, ParamType::U32]), + generics: vec![], + }; + + let a_struct = ParamType::Struct { + name: "".to_string(), + fields: to_named(&[ParamType::B256, ParamType::Bool, inner_struct]), + generics: vec![], + }; + + let width = a_struct.compute_encoding_in_bytes().unwrap(); + + const INNER_STRUCT_WIDTH: usize = WIDTH_OF_U32 * 2; + let expected_width: usize = + WIDTH_OF_B256 + checked_round_up_to_word_alignment(WIDTH_OF_BOOL)? + INNER_STRUCT_WIDTH; + assert_eq!(expected_width, width); + Ok(()) + } + + #[test] + fn enums_are_as_big_as_their_biggest_variant_plus_a_word() -> Result<()> { + let fields = to_named(&[ParamType::B256]); + let inner_struct = ParamType::Struct { + name: "".to_string(), + fields, + generics: vec![], + }; + let types = to_named(&[ParamType::U32, inner_struct]); + let param = ParamType::Enum { + name: "".to_string(), + enum_variants: EnumVariants::new(types)?, + generics: vec![], + }; + + let width = param.compute_encoding_in_bytes().unwrap(); + + const INNER_STRUCT_SIZE: usize = WIDTH_OF_B256; + const EXPECTED_WIDTH: usize = INNER_STRUCT_SIZE + WORD_SIZE; + assert_eq!(EXPECTED_WIDTH, width); + Ok(()) + } + + #[test] + fn tuples_are_just_all_elements_combined() { + let inner_tuple = ParamType::Tuple(vec![ParamType::B256]); + let param = ParamType::Tuple(vec![ParamType::U32, inner_tuple]); + + let width = param.compute_encoding_in_bytes().unwrap(); + + const INNER_TUPLE_WIDTH: usize = WIDTH_OF_B256; + const EXPECTED_WIDTH: usize = WIDTH_OF_U32 + INNER_TUPLE_WIDTH; + assert_eq!(EXPECTED_WIDTH, width); + } + + #[test] + fn test_compute_encoding_in_bytes_overflows() -> Result<()> { + let overflows = |p: ParamType| { + let error = p.compute_encoding_in_bytes().unwrap_err(); + let overflow_error = error!( + Codec, + "reached overflow while computing encoding size for {:?}", p + ); + assert_eq!(error.to_string(), overflow_error.to_string()); + }; + let tuple_with_fields_too_wide = ParamType::Tuple(vec![ + ParamType::StringArray(12514849900987264429), + ParamType::StringArray(7017071859781709229), + ]); + overflows(tuple_with_fields_too_wide); + + let struct_with_fields_too_wide = ParamType::Struct { + name: "".to_string(), + fields: to_named(&[ + ParamType::StringArray(12514849900987264429), + ParamType::StringArray(7017071859781709229), + ]), + generics: vec![], + }; + overflows(struct_with_fields_too_wide); + + let enum_with_variants_too_wide = ParamType::Enum { + name: "".to_string(), + enum_variants: EnumVariants::new(to_named(&[ParamType::StringArray(usize::MAX - 8)]))?, + generics: vec![], + }; + overflows(enum_with_variants_too_wide); + + let array_too_big = ParamType::Array(Box::new(ParamType::U64), usize::MAX); + overflows(array_too_big); + + let string_array_too_big = ParamType::StringArray(usize::MAX); + overflows(string_array_too_big); + Ok(()) + } + + #[test] + fn validate_is_decodable_simple_types() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + assert!(ParamType::U8.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U16.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U32.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U64.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U128.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::U256.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::Bool.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::B256.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::Unit.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::StringSlice + .validate_is_decodable(max_depth) + .is_ok()); + assert!(ParamType::StringArray(10) + .validate_is_decodable(max_depth) + .is_ok()); + assert!(ParamType::RawSlice.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::Bytes.validate_is_decodable(max_depth).is_ok()); + assert!(ParamType::String.validate_is_decodable(max_depth).is_ok()); + Ok(()) + } + + #[test] + fn validate_is_decodable_enum_containing_bytes() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + let param_types_containing_bytes = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; + let param_types_no_bytes = vec![ParamType::U64, ParamType::U32]; + let variants_no_bytes_type = EnumVariants::new(to_named(¶m_types_no_bytes))?; + let variants_one_bytes_type = EnumVariants::new(to_named(¶m_types_containing_bytes))?; + let variants_two_bytes_type = + EnumVariants::new(to_named(&[ParamType::Bytes, ParamType::Bytes]))?; + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_bytes_type.clone(), + generics: param_types_no_bytes.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_bytes_type.clone(), + generics: param_types_no_bytes.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_bytes_type.clone(), + generics: param_types_no_bytes.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_bytes_type, + generics: param_types_containing_bytes.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_bytes_type, + generics: param_types_containing_bytes.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_bytes_type.clone(), + generics: param_types_containing_bytes.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + Ok(()) + } + + #[test] + fn validate_is_decodable_enum_containing_string() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + let param_types_containing_string = vec![ParamType::Bytes, ParamType::U64, ParamType::Bool]; + let param_types_no_string = vec![ParamType::U64, ParamType::U32]; + let variants_no_string_type = EnumVariants::new(to_named(¶m_types_no_string))?; + let variants_one_string_type = EnumVariants::new(to_named(¶m_types_containing_string))?; + let variants_two_string_type = + EnumVariants::new(to_named(&[ParamType::Bytes, ParamType::Bytes]))?; + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_string_type.clone(), + generics: param_types_no_string.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_string_type.clone(), + generics: param_types_no_string.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_string_type.clone(), + generics: param_types_no_string.clone(), + } + .validate_is_decodable(1) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_string_type, + generics: param_types_containing_string.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_string_type, + generics: param_types_containing_string.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_string_type.clone(), + generics: param_types_containing_string.clone(), + } + .validate_is_decodable(1) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + Ok(()) + } + + #[test] + fn validate_is_decodable_enum_containing_vector() -> Result<()> { + let max_depth = DecoderConfig::default().max_depth; + let can_be_decoded = |p: ParamType| p.validate_is_decodable(max_depth).is_ok(); + let param_types_containing_vector = vec![ + ParamType::Vector(Box::new(ParamType::Bool)), + ParamType::U64, + ParamType::Bool, + ]; + let param_types_no_vector = vec![ParamType::U64, ParamType::U32]; + let variants_no_vector_type = EnumVariants::new(to_named(¶m_types_no_vector))?; + let variants_one_vector_type = EnumVariants::new(to_named(¶m_types_containing_vector))?; + let variants_two_vector_type = EnumVariants::new(to_named(&[ + ParamType::Vector(Box::new(ParamType::U8)), + ParamType::Vector(Box::new(ParamType::U16)), + ]))?; + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_vector_type.clone(), + generics: param_types_no_vector.clone(), + }); + + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_vector_type.clone(), + generics: param_types_no_vector.clone(), + }); + + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_vector_type.clone(), + generics: param_types_no_vector.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_no_vector_type, + generics: param_types_containing_vector.clone(), + }); + can_be_decoded(ParamType::Enum { + name: "".to_string(), + enum_variants: variants_one_vector_type, + generics: param_types_containing_vector.clone(), + }); + let expected = + "codec: enums currently support only one heap-type variant. Found: 2".to_string(); + assert_eq!( + ParamType::Enum { + name: "".to_string(), + enum_variants: variants_two_vector_type.clone(), + generics: param_types_containing_vector.clone(), + } + .validate_is_decodable(max_depth) + .expect_err("should not be decodable") + .to_string(), + expected + ); + + Ok(()) + } +} diff --git a/packages/fuels-core/src/utils.rs b/packages/fuels-core/src/utils.rs index bb1f07df0f..2bf3304e2f 100644 --- a/packages/fuels-core/src/utils.rs +++ b/packages/fuels-core/src/utils.rs @@ -38,3 +38,13 @@ pub(crate) fn calculate_witnesses_size<'a, I: IntoIterator>( pub(crate) mod sealed { pub trait Sealed {} } + +#[cfg(test)] +pub(crate) fn to_named<'a, I: IntoIterator>( + param_types: I, +) -> Vec<(String, crate::types::param_types::ParamType)> { + param_types + .into_iter() + .map(|pt| ("".to_string(), pt.clone())) + .collect() +} diff --git a/packages/fuels-macros/src/derive/parameterize.rs b/packages/fuels-macros/src/derive/parameterize.rs index 7b50401291..35dd8e1f73 100644 --- a/packages/fuels-macros/src/derive/parameterize.rs +++ b/packages/fuels-macros/src/derive/parameterize.rs @@ -44,7 +44,9 @@ fn parameterize_for_struct( no_std: bool, ) -> Result { let (impl_gen, type_gen, where_clause) = generics.split_for_impl(); + let name_stringified = name.to_string(); let members = Members::from_struct(contents, fuels_core_path.clone())?; + let field_names = members.names_as_strings(); let param_type_calls = members.param_type_calls(); let generic_param_types = parameterize_generic_params(&generics, &fuels_core_path)?; @@ -54,7 +56,8 @@ fn parameterize_for_struct( impl #impl_gen #fuels_core_path::traits::Parameterize for #name #type_gen #where_clause { fn param_type() -> #fuels_types_path::param_types::ParamType { #fuels_types_path::param_types::ParamType::Struct{ - fields: #std_lib::vec![#(#param_type_calls),*], + name: #std_lib::string::String::from(#name_stringified), + fields: #std_lib::vec![#((#field_names, #param_type_calls)),*], generics: #std_lib::vec![#(#generic_param_types),*], } } @@ -89,6 +92,7 @@ fn parameterize_for_enum( let enum_name_str = name.to_string(); let members = Members::from_enum(contents, fuels_core_path.clone())?; + let variant_names = members.names_as_strings(); let variant_param_types = members.param_type_calls(); let generic_param_types = parameterize_generic_params(&generics, &fuels_core_path)?; @@ -97,17 +101,17 @@ fn parameterize_for_enum( Ok(quote! { impl #impl_gen #fuels_core_path::traits::Parameterize for #name #type_gen #where_clause { fn param_type() -> #fuels_types_path::param_types::ParamType { - let variants = #std_lib::vec![#(#variant_param_types),*]; - - let variants = #fuels_types_path::enum_variants::EnumVariants::new(variants) + let variants = #std_lib::vec![#((#variant_names, #variant_param_types)),*]; + let enum_variants = #fuels_types_path::param_types::EnumVariants::new(variants) .unwrap_or_else(|_| ::std::panic!( - "{} has no variants which isn't allowed.", + "{} has no variants which isn't allowed", #enum_name_str ) ); #fuels_types_path::param_types::ParamType::Enum { - variants, + name: #std_lib::string::String::from(#enum_name_str), + enum_variants, generics: #std_lib::vec![#(#generic_param_types),*] } } diff --git a/packages/fuels-macros/src/derive/tokenizable.rs b/packages/fuels-macros/src/derive/tokenizable.rs index 4425efaa14..bf43546468 100644 --- a/packages/fuels-macros/src/derive/tokenizable.rs +++ b/packages/fuels-macros/src/derive/tokenizable.rs @@ -125,8 +125,8 @@ fn tokenizable_for_enum( fn into_token(self) -> #fuels_types_path::Token { let (discriminant, token) = #discriminant_and_token; - let variants = match ::param_type() { - #fuels_types_path::param_types::ParamType::Enum{variants, ..} => variants, + let enum_variants = match ::param_type() { + #fuels_types_path::param_types::ParamType::Enum{enum_variants, ..} => enum_variants, other => ::std::panic!( "calling {}::param_type() must return a `ParamType::Enum` but instead it returned: `{:?}`", #name_stringified, @@ -134,7 +134,7 @@ fn tokenizable_for_enum( ) }; - #fuels_types_path::Token::Enum(#std_lib::boxed::Box::new((discriminant, token, variants))) + #fuels_types_path::Token::Enum(#std_lib::boxed::Box::new((discriminant, token, enum_variants))) } fn from_token(token: #fuels_types_path::Token) -> #fuels_types_path::errors::Result diff --git a/packages/fuels-macros/src/parse_utils.rs b/packages/fuels-macros/src/parse_utils.rs index fa71cd3484..da07b320f3 100644 --- a/packages/fuels-macros/src/parse_utils.rs +++ b/packages/fuels-macros/src/parse_utils.rs @@ -198,6 +198,13 @@ impl Members { }) } + pub(crate) fn names_as_strings(&self) -> impl Iterator + '_ { + self.names().map(|ident| { + let name = ident.to_string(); + quote! {#name.to_string()} + }) + } + pub(crate) fn ignored_names(&self) -> impl Iterator + '_ { self.members.iter().filter_map(|member| { if let Member::Ignored { name } = member { diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index fbe9f54878..a6318e722e 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -383,8 +383,8 @@ pub(crate) fn get_single_call_instructions( fn extract_heap_data(param_type: &ParamType) -> Result> { match param_type { - ParamType::Enum { variants, .. } => { - let Some((discriminant, heap_type)) = variants.heap_type_variant() else { + ParamType::Enum { enum_variants, .. } => { + let Some((discriminant, heap_type)) = enum_variants.heap_type_variant() else { return Ok(vec![]); }; @@ -946,7 +946,7 @@ mod test { mod compute_calls_instructions_len { use fuel_asm::Instruction; - use fuels_core::types::{enum_variants::EnumVariants, param_types::ParamType}; + use fuels_core::types::param_types::{EnumVariants, ParamType}; use crate::{call_utils::compute_calls_instructions_len, contract::ContractCall}; @@ -1022,7 +1022,14 @@ mod test { for variant_set in variant_sets { let mut call = ContractCall::new_with_random_id(); call.output_param = ParamType::Enum { - variants: EnumVariants::new(variant_set).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new( + variant_set + .into_iter() + .map(|pt| ("".to_string(), pt)) + .collect(), + ) + .unwrap(), generics: Vec::new(), }; let instructions_len = compute_calls_instructions_len(&[call]).unwrap(); @@ -1040,7 +1047,12 @@ mod test { fn test_with_enum_with_only_non_heap_variants() { let mut call = ContractCall::new_with_random_id(); call.output_param = ParamType::Enum { - variants: EnumVariants::new(vec![ParamType::Bool, ParamType::U8]).unwrap(), + name: "".to_string(), + enum_variants: EnumVariants::new(vec![ + ("".to_string(), ParamType::Bool), + ("".to_string(), ParamType::U8), + ]) + .unwrap(), generics: Vec::new(), }; let instructions_len = compute_calls_instructions_len(&[call]).unwrap(); diff --git a/packages/fuels/tests/types/contracts/generics/src/main.sw b/packages/fuels/tests/types/contracts/generics/src/main.sw index 69491e40cf..714a0cc7db 100644 --- a/packages/fuels/tests/types/contracts/generics/src/main.sw +++ b/packages/fuels/tests/types/contracts/generics/src/main.sw @@ -44,8 +44,8 @@ struct StructWTupleGeneric { #[allow(dead_code)] enum EnumWGeneric { - a: u64, - b: N, + A: u64, + B: N, } #[allow(dead_code)] @@ -88,15 +88,18 @@ impl MyContract for Contract { _arg_1: StructOneUnusedGenericParam, _arg_2: EnumOneUnusedGenericParam, ) {} + fn two_unused_generic_args( _arg_1: StructTwoUnusedGenericParams, _arg_2: EnumTwoUnusedGenericParams, ) {} + fn used_and_unused_generic_args( arg_1: StructUsedAndUnusedGenericParams, arg_2: EnumUsedAndUnusedGenericParams, ) -> (StructUsedAndUnusedGenericParams, EnumUsedAndUnusedGenericParams) { assert_eq(arg_1.field, 10u8); + if let EnumUsedAndUnusedGenericParams::Two(val) = arg_2 { assert_eq(val, 11u8); } else { @@ -110,6 +113,7 @@ impl MyContract for Contract { EnumUsedAndUnusedGenericParams::Two(13u8), ) } + fn struct_w_generic(arg1: SimpleGeneric) -> SimpleGeneric { let expected = SimpleGeneric { single_generic_param: 123u64, @@ -157,14 +161,14 @@ impl MyContract for Contract { fn enum_w_generic(arg1: EnumWGeneric) -> EnumWGeneric { match arg1 { - EnumWGeneric::b(value) => { + EnumWGeneric::B(value) => { assert(value == 10u64); } _ => { assert(false) } } - EnumWGeneric::b(10) + EnumWGeneric::B(10) } fn complex_test(_arg: MegaExample) {} diff --git a/packages/fuels/tests/types_contracts.rs b/packages/fuels/tests/types_contracts.rs index 0e83147caa..0f181ae561 100644 --- a/packages/fuels/tests/types_contracts.rs +++ b/packages/fuels/tests/types_contracts.rs @@ -1479,8 +1479,8 @@ async fn generics_test() -> Result<()> { assert_eq!(result, arg1); } { - // struct with generic in variant - let arg1 = EnumWGeneric::b(10); + // enum with generic in variant + let arg1 = EnumWGeneric::B(10); let result = contract_methods .enum_w_generic(arg1.clone()) .call() @@ -1528,7 +1528,7 @@ async fn generics_test() -> Result<()> { let arg1 = MegaExample { a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?), b: vec![( - [EnumWGeneric::b(StructWTupleGeneric { + [EnumWGeneric::B(StructWTupleGeneric { a: (w_arr_generic.clone(), w_arr_generic), })], 10u32,