diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index c274bf09a3..e7a888a76c 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -28,7 +28,7 @@ impl Default for DecoderConfig { #[derive(Default)] pub struct ABIDecoder { - config: DecoderConfig, + pub config: DecoderConfig, } impl ABIDecoder { @@ -86,8 +86,10 @@ mod tests { use super::*; use crate::{ constants::WORD_SIZE, + traits::Parameterize, types::{enum_variants::EnumVariants, errors::Error, StaticStringToken, U256}, }; + use ParamType::*; #[test] fn decode_int() -> Result<()> { @@ -520,6 +522,87 @@ mod tests { Ok(()) } + #[test] + pub fn division_by_zero() { + let param_type = Vec::<[u16; 0]>::param_type(); + let result = ABIDecoder::default().decode(¶m_type, &[]); + assert!(matches!(result, Err(Error::InvalidType(_)))); + } + + #[test] + pub fn multiply_overflow_enum() { + let result = ABIDecoder::default().decode( + &Enum { + variants: EnumVariants::new(vec![ + Array(Box::new(Array(Box::new(RawSlice), 8)), usize::MAX), + B256, + B256, + B256, + B256, + B256, + B256, + B256, + B256, + B256, + B256, + ]) + .unwrap(), + generics: vec![U16], + }, + &[], + ); + assert!(matches!(result, Err(Error::InvalidType(_)))); + } + + #[test] + pub fn multiply_overflow_vector() { + let param_type = Vec::<[(); usize::MAX]>::param_type(); + let result = ABIDecoder::default().decode(¶m_type, &[]); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + pub fn multiply_overflow_arith() { + let mut param_type: ParamType = U16; + for _ in 0..50 { + param_type = Array(Box::new(param_type), 8); + } + let result = ABIDecoder::default().decode( + &Enum { + variants: EnumVariants::new(vec![param_type]).unwrap(), + generics: vec![U16], + }, + &[], + ); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + pub fn capacity_overflow() { + let result = ABIDecoder::default().decode( + &Array(Box::new(Array(Box::new(Tuple(vec![])), usize::MAX)), 1), + &[], + ); + assert!(matches!(result, Err(Error::InvalidType(_)))); + } + + #[test] + pub fn stack_overflow() { + let mut param_type: ParamType = U16; + for _ in 0..13500 { + param_type = Vector(Box::new(param_type)); + } + let result = ABIDecoder::default().decode(¶m_type, &[]); + assert!(matches!(result, Err(Error::InvalidType(_)))); + } + + #[test] + pub fn capacity_maloc() { + let param_type = Array(Box::new(U8), usize::MAX); + let result = ABIDecoder::default().decode(¶m_type, &[]); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + #[test] fn decoding_enum_with_more_than_one_heap_type_variant_fails() -> Result<()> { let mut param_types = 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 893609555c..788b7f780c 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -19,6 +19,7 @@ use crate::{ pub(crate) struct BoundedDecoder { depth_tracker: CounterWithLimit, token_tracker: CounterWithLimit, + config: DecoderConfig, } const U128_BYTES_SIZE: usize = 2 * WORD_SIZE; @@ -32,11 +33,12 @@ impl BoundedDecoder { Self { depth_tracker, token_tracker, + config, } } pub(crate) fn decode(&mut self, param_type: &ParamType, bytes: &[u8]) -> Result { - param_type.validate_is_decodable()?; + param_type.validate_is_decodable(self.config.max_depth)?; Ok(self.decode_param(param_type, bytes)?.token) } @@ -46,7 +48,7 @@ impl BoundedDecoder { bytes: &[u8], ) -> Result> { for param_type in param_types { - param_type.validate_is_decodable()?; + param_type.validate_is_decodable(self.config.max_depth)?; } let (tokens, _) = self.decode_params(param_types, bytes)?; @@ -297,19 +299,27 @@ 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 = variants.compute_encoding_width_of_enum(); + let enum_width = variants.compute_encoding_width_of_enum()?; let discriminant = peek_u32(bytes)? as u8; let selected_variant = variants.param_type_of_variant(discriminant)?; + let skip_extra = variants .heap_type_variant() .and_then(|(heap_discriminant, heap_type)| { (heap_discriminant == discriminant).then_some(heap_type.compute_encoding_width()) }) + .transpose()? .unwrap_or_default(); - let words_to_skip = enum_width - selected_variant.compute_encoding_width() + skip_extra; - let enum_content_bytes = skip(bytes, words_to_skip * WORD_SIZE)?; + let words_to_skip = enum_width - selected_variant.compute_encoding_width()? + skip_extra; + let bytes_to_skip = words_to_skip.checked_mul(WORD_SIZE).ok_or_else(|| { + error!( + InvalidData, + "Overflow error while decoding enum {variants:?}" + ) + })?; + let enum_content_bytes = skip(bytes, bytes_to_skip)?; let result = self.decode_token_in_enum(enum_content_bytes, variants, selected_variant)?; let selector = Box::new((discriminant, result.token, variants.clone())); diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index ae317cf5f5..b37369c000 100644 --- a/packages/fuels-core/src/codec/abi_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder.rs @@ -115,7 +115,7 @@ impl ABIEncoder { // 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 padding_amount = variants.compute_padding_amount(variant_param_type); + let padding_amount = variants.compute_padding_amount(variant_param_type)?; encoded_enum.push(Data::Inline(vec![0; padding_amount])); diff --git a/packages/fuels-core/src/types/enum_variants.rs b/packages/fuels-core/src/types/enum_variants.rs index a709c11d15..b083b1abaa 100644 --- a/packages/fuels-core/src/types/enum_variants.rs +++ b/packages/fuels-core/src/types/enum_variants.rs @@ -47,27 +47,32 @@ impl EnumVariants { } /// Calculates how many WORDs are needed to encode an enum. - pub fn compute_encoding_width_of_enum(&self) -> usize { + pub fn compute_encoding_width_of_enum(&self) -> Result { if self.only_units_inside() { - return ENUM_DISCRIMINANT_WORD_WIDTH; + return Ok(ENUM_DISCRIMINANT_WORD_WIDTH); } self.param_types() .iter() .map(|p| p.compute_encoding_width()) + .collect::>>()? + .iter() .max() .map(|width| width + ENUM_DISCRIMINANT_WORD_WIDTH) - .expect( - "Will never panic because EnumVariants must have at least one variant inside it!", - ) + .ok_or_else(|| { + error!( + InvalidData, + "EnumVariants was empty, must have at least one variant" + ) + }) } /// Determines the padding needed for the provided enum variant (based on the width of the /// biggest variant) and returns it. - pub fn compute_padding_amount(&self, variant_param_type: &ParamType) -> usize { + pub fn compute_padding_amount(&self, variant_param_type: &ParamType) -> Result { let biggest_variant_width = - self.compute_encoding_width_of_enum() - ENUM_DISCRIMINANT_WORD_WIDTH; - let variant_width = variant_param_type.compute_encoding_width(); - (biggest_variant_width - variant_width) * WORD_SIZE + self.compute_encoding_width_of_enum()? - ENUM_DISCRIMINANT_WORD_WIDTH; + let variant_width = variant_param_type.compute_encoding_width()?; + Ok((biggest_variant_width - variant_width) * WORD_SIZE) } } diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index 83eb5886a5..c00c9dff74 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, iter::zip}; +use std::{collections::HashMap, fmt, iter::zip}; 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 itertools::{chain, Itertools}; use crate::{ constants::WORD_SIZE, @@ -67,7 +67,10 @@ impl ParamType { param_type: &ParamType, available_bytes: usize, ) -> Result { - let memory_size = param_type.compute_encoding_width() * WORD_SIZE; + let encoding_width = param_type.compute_encoding_width()?; + let memory_size = encoding_width + .checked_mul(WORD_SIZE) + .ok_or_else(|| error!(InvalidData, "Overflow error while encoding {param_type:?}"))?; if memory_size == 0 { return Err(error!( InvalidType, @@ -81,7 +84,10 @@ impl ParamType { "{remainder} extra bytes detected while decoding heap type" )); } - Ok(available_bytes / memory_size) + let num_of_elements = available_bytes + .checked_div(memory_size) + .ok_or_else(|| error!(InvalidData, "Type {param_type:?} has a memory_size of 0"))?; + Ok(num_of_elements) } pub fn children_need_extra_receipts(&self) -> bool { @@ -103,7 +109,7 @@ impl ParamType { } } - pub fn validate_is_decodable(&self) -> Result<()> { + pub fn validate_is_decodable(&self, max_depth: usize) -> Result<()> { match self { ParamType::Enum { variants, .. } => { let all_param_types = variants.param_types(); @@ -133,7 +139,8 @@ impl ParamType { } _ if self.children_need_extra_receipts() => Err(error!( InvalidType, - "Nested heap types are currently not supported except in Enums." + "type {:?} is not decodable: nested heap types are currently not supported except in Enums.", + DebugWithDepth::new(self, max_depth) )), _ => Ok(()), } @@ -157,21 +164,27 @@ impl ParamType { } /// 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) -> Option { + pub fn heap_inner_element_size(&self, top_level_type: bool) -> Result> { match &self { ParamType::Vector(inner_param_type) => { - Some(inner_param_type.compute_encoding_width() * WORD_SIZE) + let width = inner_param_type.compute_encoding_width()?; + width + .checked_mul(WORD_SIZE) + .map(Some) + .ok_or_else(|| error!(InvalidData, "overflow while multiplying")) } // `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::RawSlice if !top_level_type => Some(ParamType::U64.compute_encoding_width()), - ParamType::StringSlice if !top_level_type => Some(std::mem::size_of::()), - _ => None, + ParamType::Bytes | ParamType::String => Ok(Some(std::mem::size_of::())), + ParamType::RawSlice if !top_level_type => { + ParamType::U64.compute_encoding_width().map(Some) + } + ParamType::StringSlice if !top_level_type => Ok(Some(std::mem::size_of::())), + _ => Ok(None), } } /// Calculates the number of `WORD`s the VM expects this parameter to be encoded in. - pub fn compute_encoding_width(&self) -> usize { + pub fn compute_encoding_width(&self) -> Result { const fn count_words(bytes: usize) -> usize { let q = bytes / WORD_SIZE; let r = bytes % WORD_SIZE; @@ -187,18 +200,29 @@ impl ParamType { | ParamType::U16 | ParamType::U32 | ParamType::U64 - | ParamType::Bool => 1, - ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => 2, - ParamType::Vector(_) | ParamType::Bytes | ParamType::String => 3, - ParamType::U256 | ParamType::B256 => 4, - ParamType::Array(param, count) => param.compute_encoding_width() * count, - ParamType::StringArray(len) => count_words(*len), + | ParamType::Bool => Ok(1), + ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => Ok(2), + ParamType::Vector(_) | ParamType::Bytes | ParamType::String => Ok(3), + ParamType::U256 | ParamType::B256 => Ok(4), + ParamType::Array(param, count) => param + .compute_encoding_width()? + .checked_mul(*count) + .ok_or_else(|| { + error!( + InvalidData, + "overflow while calculating encoding width for Array({param:?}, {count})" + ) + }), + ParamType::StringArray(len) => Ok(count_words(*len)), ParamType::Struct { fields, .. } => fields .iter() .map(|param_type| param_type.compute_encoding_width()) - .sum(), + .process_results(|iter| iter.sum()), ParamType::Enum { variants, .. } => variants.compute_encoding_width_of_enum(), - ParamType::Tuple(params) => params.iter().map(|p| p.compute_encoding_width()).sum(), + ParamType::Tuple(params) => params + .iter() + .map(|param_type| param_type.compute_encoding_width()) + .process_results(|iter| iter.sum()), } } @@ -535,11 +559,98 @@ fn try_primitive(the_type: &Type) -> Result> { 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::types::param_types::ParamType; + use crate::{codec::DecoderConfig, types::param_types::ParamType}; const WIDTH_OF_B256: usize = 4; const WIDTH_OF_U32: usize = 1; @@ -550,7 +661,7 @@ mod tests { const NUM_ELEMENTS: usize = 11; let param = ParamType::Array(Box::new(ParamType::B256), NUM_ELEMENTS); - let width = param.compute_encoding_width(); + let width = param.compute_encoding_width().unwrap(); let expected = NUM_ELEMENTS * WIDTH_OF_B256; assert_eq!(expected, width); @@ -561,7 +672,7 @@ mod tests { const NUM_ASCII_CHARS: usize = 9; let param = ParamType::StringArray(NUM_ASCII_CHARS); - let width = param.compute_encoding_width(); + let width = param.compute_encoding_width().unwrap(); // 2 WORDS or 16 B are enough to fit 9 ascii chars assert_eq!(2, width); @@ -579,7 +690,7 @@ mod tests { generics: vec![], }; - let width = a_struct.compute_encoding_width(); + let width = a_struct.compute_encoding_width().unwrap(); const INNER_STRUCT_WIDTH: usize = WIDTH_OF_U32 * 2; const EXPECTED_WIDTH: usize = WIDTH_OF_B256 + WIDTH_OF_BOOL + INNER_STRUCT_WIDTH; @@ -599,7 +710,7 @@ mod tests { generics: vec![], }; - let width = param.compute_encoding_width(); + let width = param.compute_encoding_width().unwrap(); const INNER_STRUCT_SIZE: usize = WIDTH_OF_B256; const EXPECTED_WIDTH: usize = INNER_STRUCT_SIZE + 1; @@ -612,7 +723,7 @@ mod tests { let inner_tuple = ParamType::Tuple(vec![ParamType::B256]); let param = ParamType::Tuple(vec![ParamType::U32, inner_tuple]); - let width = param.compute_encoding_width(); + let width = param.compute_encoding_width().unwrap(); const INNER_TUPLE_WIDTH: usize = WIDTH_OF_B256; const EXPECTED_WIDTH: usize = WIDTH_OF_U32 + INNER_TUPLE_WIDTH; @@ -1333,20 +1444,25 @@ mod tests { #[test] fn validate_is_decodable_simple_types() -> Result<()> { - assert!(ParamType::U8.validate_is_decodable().is_ok()); - assert!(ParamType::U16.validate_is_decodable().is_ok()); - assert!(ParamType::U32.validate_is_decodable().is_ok()); - assert!(ParamType::U64.validate_is_decodable().is_ok()); - assert!(ParamType::U128.validate_is_decodable().is_ok()); - assert!(ParamType::U256.validate_is_decodable().is_ok()); - assert!(ParamType::Bool.validate_is_decodable().is_ok()); - assert!(ParamType::B256.validate_is_decodable().is_ok()); - assert!(ParamType::Unit.validate_is_decodable().is_ok()); - assert!(ParamType::StringSlice.validate_is_decodable().is_ok()); - assert!(ParamType::StringArray(10).validate_is_decodable().is_ok()); - assert!(ParamType::RawSlice.validate_is_decodable().is_ok()); - assert!(ParamType::Bytes.validate_is_decodable().is_ok()); - assert!(ParamType::String.validate_is_decodable().is_ok()); + 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(()) } @@ -1354,18 +1470,23 @@ mod tests { 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 nested_heap_type_error_message = "Invalid type: Nested heap types are currently not \ - supported except in Enums." - .to_string(); + let max_depth = DecoderConfig::default().max_depth; + let nested_heap_type_error_message = |p: ParamType| { + format!( + "Invalid type: 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() + p.validate_is_decodable(max_depth) .expect_err(&format!("Should not be decodable: {:?}", p)) .to_string(), - nested_heap_type_error_message + nested_heap_type_error_message(p) ) }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable().is_ok(); + 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)); @@ -1390,7 +1511,8 @@ mod tests { #[test] fn validate_is_decodable_enum_containing_bytes() -> Result<()> { - let can_be_decoded = |p: ParamType| p.validate_is_decodable().is_ok(); + 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())?; @@ -1411,7 +1533,7 @@ mod tests { variants: variants_two_bytes_type.clone(), generics: param_types_no_bytes.clone(), } - .validate_is_decodable() + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected @@ -1431,7 +1553,7 @@ mod tests { variants: variants_two_bytes_type.clone(), generics: param_types_containing_bytes.clone(), } - .validate_is_decodable() + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected @@ -1442,24 +1564,29 @@ mod tests { #[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 = "Invalid type: Nested heap types are currently not \ - supported except in Enums." - .to_string(); + let nested_heap_type_error_message = |p: ParamType| { + format!( + "Invalid type: 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() + p.validate_is_decodable(max_depth) .expect_err(&format!("Should not be decodable: {:?}", p)) .to_string(), - nested_heap_type_error_message + nested_heap_type_error_message(p) ) }; - let can_be_decoded = |p: ParamType| p.validate_is_decodable().is_ok(); + 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( @@ -1489,7 +1616,8 @@ mod tests { #[test] fn validate_is_decodable_enum_containing_vector() -> Result<()> { - let can_be_decoded = |p: ParamType| p.validate_is_decodable().is_ok(); + 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, @@ -1517,7 +1645,7 @@ mod tests { variants: variants_two_vector_type.clone(), generics: param_types_no_vector.clone(), } - .validate_is_decodable() + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected @@ -1537,7 +1665,7 @@ mod tests { variants: variants_two_vector_type.clone(), generics: param_types_containing_vector.clone(), } - .validate_is_decodable() + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index 2cdcacc342..5c358c8a6f 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -106,13 +106,13 @@ pub(crate) async fn build_tx_from_contract_calls( ) -> Result { let consensus_parameters = account.try_provider()?.consensus_parameters(); - let calls_instructions_len = compute_calls_instructions_len(calls); + let calls_instructions_len = compute_calls_instructions_len(calls)?; let data_offset = call_script_data_offset(&consensus_parameters, calls_instructions_len); let (script_data, call_param_offsets) = build_script_data_from_contract_calls(calls, data_offset); - let script = get_instructions(calls, call_param_offsets); + let script = get_instructions(calls, call_param_offsets)?; let required_asset_amounts = calculate_required_asset_amounts(calls); @@ -144,7 +144,7 @@ pub(crate) async fn build_tx_from_contract_calls( /// Compute the length of the calling scripts for the two types of contract calls: those that return /// a heap type, and those that don't. -fn compute_calls_instructions_len(calls: &[ContractCall]) -> usize { +fn compute_calls_instructions_len(calls: &[ContractCall]) -> Result { calls .iter() .map(|c| { @@ -158,9 +158,10 @@ fn compute_calls_instructions_len(calls: &[ContractCall]) -> usize { call_opcode_params.gas_forwarded_offset = Some(0); } - get_single_call_instructions(&call_opcode_params, &c.output_param).len() + get_single_call_instructions(&call_opcode_params, &c.output_param) + .map(|instructions| instructions.len()) }) - .sum() + .process_results(|c| c.sum()) } /// Compute how much of each asset is required based on all `CallParameters` of the `ContractCalls` @@ -212,13 +213,16 @@ fn sum_up_amounts_for_each_asset_id( pub(crate) fn get_instructions( calls: &[ContractCall], offsets: Vec, -) -> Vec { +) -> Result> { calls .iter() .zip(&offsets) - .flat_map(|(call, offset)| get_single_call_instructions(offset, &call.output_param)) - .chain(op::ret(RegId::ONE).to_bytes()) - .collect() + .map(|(call, offset)| get_single_call_instructions(offset, &call.output_param)) + .process_results(|iter| iter.flatten().collect::>()) + .map(|mut bytes| { + bytes.extend(op::ret(RegId::ONE).to_bytes()); + bytes + }) } /// Returns script data, consisting of the following items in the given order: @@ -310,7 +314,7 @@ pub(crate) fn build_script_data_from_contract_calls( pub(crate) fn get_single_call_instructions( offsets: &CallOpcodeParamsOffset, output_param_type: &ParamType, -) -> Vec { +) -> Result> { let call_data_offset = offsets .call_data_offset .try_into() @@ -348,23 +352,23 @@ pub(crate) fn get_single_call_instructions( None => instructions.push(op::call(0x10, 0x11, 0x12, RegId::CGAS)), }; - instructions.extend(extract_heap_data(output_param_type)); + instructions.extend(extract_heap_data(output_param_type)?); #[allow(clippy::iter_cloned_collect)] - instructions.into_iter().collect::>() + Ok(instructions.into_iter().collect::>()) } -fn extract_heap_data(param_type: &ParamType) -> Vec { +fn extract_heap_data(param_type: &ParamType) -> Result> { match param_type { ParamType::Enum { variants, .. } => { let Some((discriminant, heap_type)) = variants.heap_type_variant() else { - return vec![]; + return Ok(vec![]); }; let ptr_offset = - (param_type.compute_encoding_width() - heap_type.compute_encoding_width()) as u16; + (param_type.compute_encoding_width()? - heap_type.compute_encoding_width()?) as u16; - [ + Ok([ vec![ // All the registers 0x15-0x18 are free // Load the selected discriminant to a free register @@ -378,11 +382,11 @@ fn extract_heap_data(param_type: &ParamType) -> Vec { op::jnef(0x17, 0x18, RegId::ZERO, 3), ], // ================= EXECUTED IF THE DISCRIMINANT POINTS TO A HEAP TYPE - extract_data_receipt(ptr_offset, false, heap_type), + extract_data_receipt(ptr_offset, false, heap_type)?, // ================= EXECUTED IF THE DISCRIMINANT DOESN'T POINT TO A HEAP TYPE vec![op::retd(0x15, RegId::ZERO)], ] - .concat() + .concat()) } _ => extract_data_receipt(0, true, param_type), } @@ -392,9 +396,9 @@ fn extract_data_receipt( ptr_offset: u16, top_level_type: bool, param_type: &ParamType, -) -> Vec { - let Some(inner_type_byte_size) = param_type.heap_inner_element_size(top_level_type) else { - return vec![]; +) -> Result> { + let Some(inner_type_byte_size) = param_type.heap_inner_element_size(top_level_type)? else { + return Ok(vec![]); }; let len_offset = match (top_level_type, param_type) { @@ -405,12 +409,12 @@ fn extract_data_receipt( _ => 2, }; - vec![ + Ok(vec![ op::lw(0x15, RegId::RET, ptr_offset), op::lw(0x16, RegId::RET, ptr_offset + len_offset), op::muli(0x16, 0x16, inner_type_byte_size as u16), op::retd(0x15, 0x16), - ] + ]) } /// Returns the assets and contracts that will be consumed ([`Input`]s) diff --git a/packages/fuels-programs/src/receipt_parser.rs b/packages/fuels-programs/src/receipt_parser.rs index 7f7393764b..3d8a6cdbcc 100644 --- a/packages/fuels-programs/src/receipt_parser.rs +++ b/packages/fuels-programs/src/receipt_parser.rs @@ -43,7 +43,7 @@ impl ReceiptParser { // During a script execution, the script's contract id is the **null** contract id .unwrap_or_else(ContractId::zeroed); - output_param.validate_is_decodable()?; + output_param.validate_is_decodable(self.decoder.config.max_depth)?; let data = self .extract_raw_data(output_param, &contract_id)