diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index 336183999c..6b7434af87 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -554,7 +554,7 @@ mod tests { }, &[], ); - assert!(matches!(result, Err(Error::InvalidData(_)))); + assert!(matches!(result, Err(Error::InvalidType(_)))); } #[test] 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 f0e8b63b43..3ad13d0cc9 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -1,9 +1,9 @@ use std::{convert::TryInto, str}; use crate::{ + checked_round_up_to_word_alignment, codec::DecoderConfig, constants::WORD_SIZE, - round_up_to_word_alignment, traits::Tokenizable, types::{ enum_variants::EnumVariants, @@ -151,7 +151,7 @@ impl BoundedDecoder { for param_type in param_types.iter() { // padding has to be taken into account - bytes_read = round_up_to_word_alignment(bytes_read); + bytes_read = checked_round_up_to_word_alignment(bytes_read)?; let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?; bytes_read += res.bytes_read; tokens.push(res.token); @@ -170,7 +170,7 @@ impl BoundedDecoder { for param_type in param_types.iter() { // padding has to be taken into account - bytes_read = round_up_to_word_alignment(bytes_read); + bytes_read = checked_round_up_to_word_alignment(bytes_read)?; let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?; bytes_read += res.bytes_read; tokens.push(res.token); @@ -249,7 +249,7 @@ impl BoundedDecoder { let decoded = str::from_utf8(encoded_str)?; let result = Decoded { token: Token::StringArray(StaticStringToken::new(decoded.into(), Some(length))), - bytes_read: round_up_to_word_alignment(length), + bytes_read: checked_round_up_to_word_alignment(length)?, }; Ok(result) } @@ -333,24 +333,19 @@ 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() - .ok_or(error!(InvalidData, "Error calculating enum width in bytes"))?; + let enum_width_in_bytes = variants.compute_enum_width_in_bytes()?; let discriminant = peek_u64(bytes)?; let selected_variant = variants.param_type_of_variant(discriminant)?; - let skip_extra_in_bytes = variants - .heap_type_variant() - .and_then(|(heap_discriminant, heap_type)| { - (heap_discriminant == discriminant).then_some(heap_type.compute_encoding_in_bytes()) - }) - .unwrap_or_default() - .unwrap_or_default(); - let bytes_to_skip = enum_width_in_bytes - - selected_variant - .compute_encoding_in_bytes() - .ok_or(error!(InvalidData, "Error calculating enum width in bytes"))? + let skip_extra_in_bytes = match variants.heap_type_variant() { + Some((heap_type_discriminant, heap_type)) if heap_type_discriminant == discriminant => { + heap_type.compute_encoding_in_bytes()? + } + _ => 0, + }; + + let bytes_to_skip = enum_width_in_bytes - selected_variant.compute_encoding_in_bytes()? + skip_extra_in_bytes; let enum_content_bytes = skip(bytes, bytes_to_skip)?; diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index b4eb411cd4..3585a52499 100644 --- a/packages/fuels-core/src/codec/abi_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder.rs @@ -1,8 +1,8 @@ use fuel_types::bytes::padded_len_usize; use crate::{ + checked_round_up_to_word_alignment, constants::WORD_SIZE, - round_up_to_word_alignment, types::{ errors::Result, pad_u16, pad_u32, @@ -50,8 +50,11 @@ impl ABIEncoder { data.append(&mut new_data); if word_aligned { - let padding = - vec![0u8; round_up_to_word_alignment(offset_in_bytes) - offset_in_bytes]; + let padding = vec![ + 0u8; + checked_round_up_to_word_alignment(offset_in_bytes)? + - offset_in_bytes + ]; if !padding.is_empty() { offset_in_bytes += padding.len(); data.push(Data::Inline(padding)); diff --git a/packages/fuels-core/src/types/enum_variants.rs b/packages/fuels-core/src/types/enum_variants.rs index 9e6e6cd0c1..fc2f9d0a58 100644 --- a/packages/fuels-core/src/types/enum_variants.rs +++ b/packages/fuels-core/src/types/enum_variants.rs @@ -4,7 +4,7 @@ use crate::{ errors::{error, Result}, param_types::ParamType, }, - utils::round_up_to_word_alignment, + utils::checked_round_up_to_word_alignment, }; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -48,32 +48,27 @@ impl EnumVariants { } /// Calculates how many bytes are needed to encode an enum. - pub fn compute_enum_width_in_bytes(&self) -> Option { + pub fn compute_enum_width_in_bytes(&self) -> Result { if self.only_units_inside() { - return Some(ENUM_DISCRIMINANT_BYTE_WIDTH); + return Ok(ENUM_DISCRIMINANT_BYTE_WIDTH); } - let width = self.param_types().iter().try_fold(0, |a, p| { + let width = self.param_types().iter().try_fold(0, |a, p| -> Result<_> { let size = p.compute_encoding_in_bytes()?; - Some(a.max(size)) + Ok(a.max(size)) })?; - Some(round_up_to_word_alignment(width) + ENUM_DISCRIMINANT_BYTE_WIDTH) + checked_round_up_to_word_alignment(width)? + .checked_add(ENUM_DISCRIMINANT_BYTE_WIDTH) + .ok_or_else(|| error!(InvalidType, "Enum variants are too wide")) } /// 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_in_bytes(&self, variant_param_type: &ParamType) -> Result { - let enum_width = self - .compute_enum_width_in_bytes() - .ok_or(error!(InvalidData, "Error calculating enum width in bytes"))?; + let enum_width = self.compute_enum_width_in_bytes()?; let biggest_variant_width = enum_width - ENUM_DISCRIMINANT_BYTE_WIDTH; - let variant_width = variant_param_type - .compute_encoding_in_bytes() - .ok_or(error!( - InvalidData, - "Error calculating padding amount in bytes" - ))?; + let variant_width = variant_param_type.compute_encoding_in_bytes()?; Ok(biggest_variant_width - variant_width) } } diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index b6cbde50b3..e23b13203e 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -7,7 +7,7 @@ use fuel_abi_types::{ use itertools::chain; use crate::{ - round_up_to_word_alignment, + checked_round_up_to_word_alignment, types::{ enum_variants::EnumVariants, errors::{error, Error, Result}, @@ -67,10 +67,7 @@ impl ParamType { param_type: &ParamType, available_bytes: usize, ) -> Result { - let memory_size = param_type.compute_encoding_in_bytes().ok_or(error!( - InvalidType, - "Cannot calculate the number of elements." - ))?; + let memory_size = param_type.compute_encoding_in_bytes()?; if memory_size == 0 { return Err(error!( InvalidType, @@ -110,40 +107,38 @@ impl ParamType { } pub fn validate_is_decodable(&self, max_depth: usize) -> Result<()> { - match self { - ParamType::Enum { variants, .. } => { - 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!( - InvalidType, - "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 { - Err(error!( - InvalidType, - "Enums currently support only one heap-type variant. Found: \ + 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!( + InvalidType, + "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!( + InvalidType, + "Enums currently support only one heap-type variant. Found: \ {num_of_children_needing_receipts}" - )) - } else { - Ok(()) - } + )); } - _ if self.children_need_extra_receipts() => Err(error!( + } else if self.children_need_extra_receipts() { + return Err(error!( InvalidType, "type {:?} is not decodable: nested heap types are currently not supported except in Enums.", DebugWithDepth::new(self, max_depth) - )), - _ => Ok(()), + )); } + self.compute_encoding_in_bytes()?; + Ok(()) } pub fn is_extra_receipt_needed(&self, top_level_type: bool) -> bool { @@ -164,36 +159,56 @@ 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 { - match &self { - ParamType::Vector(inner_param_type) => inner_param_type.compute_encoding_in_bytes(), + 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 => ParamType::U8.compute_encoding_in_bytes(), - ParamType::RawSlice if !top_level_type => ParamType::U64.compute_encoding_in_bytes(), + 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) -> Option { + pub fn compute_encoding_in_bytes(&self) -> Result { + let overflow_error = || { + error!( + InvalidType, + "Reached overflow while computing encoding size for {:?}", self + ) + }; match &self { - ParamType::Unit | ParamType::U8 | ParamType::Bool => Some(1), - ParamType::U16 | ParamType::U32 | ParamType::U64 => Some(8), - ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => Some(16), - ParamType::U256 | ParamType::B256 => Some(32), - ParamType::Vector(_) | ParamType::Bytes | ParamType::String => Some(24), - ParamType::Array(param, count) => { - param.compute_encoding_in_bytes()?.checked_mul(*count) + 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::StringArray(len) => Some(round_up_to_word_alignment(*len)), ParamType::Tuple(fields) | ParamType::Struct { fields, .. } => { - fields.iter().try_fold(0, |a, param_type| { - let size = round_up_to_word_alignment(param_type.compute_encoding_in_bytes()?); - Some(a + size) + 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(), + ParamType::Enum { variants, .. } => variants + .compute_enum_width_in_bytes() + .map_err(|_| overflow_error()), } } @@ -648,7 +663,7 @@ mod tests { } #[test] - fn structs_are_all_elements_combined_with_padding() { + fn structs_are_all_elements_combined_with_padding() -> Result<()> { let inner_struct = ParamType::Struct { fields: vec![ParamType::U32, ParamType::U32], generics: vec![], @@ -662,9 +677,10 @@ mod tests { let width = a_struct.compute_encoding_in_bytes().unwrap(); const INNER_STRUCT_WIDTH: usize = WIDTH_OF_U32 * 2; - const EXPECTED_WIDTH: usize = - WIDTH_OF_B256 + round_up_to_word_alignment(WIDTH_OF_BOOL) + INNER_STRUCT_WIDTH; - assert_eq!(EXPECTED_WIDTH, width); + 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] @@ -1814,13 +1830,52 @@ mod tests { 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!( + InvalidType, + "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("Cannot calculate the number of elements")); + .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() diff --git a/packages/fuels-core/src/utils.rs b/packages/fuels-core/src/utils.rs index 02cf32ccc0..83b513c790 100644 --- a/packages/fuels-core/src/utils.rs +++ b/packages/fuels-core/src/utils.rs @@ -1,13 +1,31 @@ pub mod constants; pub mod offsets; +use crate::error; +use crate::types::errors::Result; use constants::{WITNESS_STATIC_SIZE, WORD_SIZE}; use fuel_tx::Witness; -pub const fn round_up_to_word_alignment(bytes_len: usize) -> usize { - (bytes_len + (WORD_SIZE - 1)) - ((bytes_len + (WORD_SIZE - 1)) % WORD_SIZE) +pub fn checked_round_up_to_word_alignment(bytes_len: usize) -> Result { + let lhs = bytes_len.checked_add(WORD_SIZE - 1).ok_or_else(|| { + error!( + InvalidType, + "Addition overflow while rounding up {bytes_len} bytes to word alignment" + ) + })?; + let rhs = lhs.checked_rem(WORD_SIZE).ok_or_else(|| { + error!( + InvalidType, + "Remainder overflow while rounding up {bytes_len} bytes to word alignment" + ) + })?; + lhs.checked_sub(rhs).ok_or_else(|| { + error!( + InvalidType, + "Substraction overflow while rounding up {bytes_len} bytes to word alignment" + ) + }) } - pub(crate) fn calculate_witnesses_size<'a, I: IntoIterator>( witnesses: I, ) -> usize { diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index 28aa12259e..6f80549638 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -379,21 +379,8 @@ fn extract_heap_data(param_type: &ParamType) -> Result Result> { - let Some(inner_type_byte_size) = param_type.heap_inner_element_size(top_level_type) else { + let Some(inner_type_byte_size) = param_type.heap_inner_element_size(top_level_type)? else { return Ok(vec![]); };