From a2611041ce91ac1eb64caf3fbba7698342cc7d70 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Tue, 5 Sep 2023 12:00:11 +0300 Subject: [PATCH 1/9] fix: handle division by zero --- packages/fuels-core/src/types/param_types.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index efb20c420e..a10f07f250 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -70,14 +70,19 @@ impl ParamType { available_bytes: usize, ) -> Result { let memory_size = param_type.compute_encoding_width() * WORD_SIZE; - let remainder = available_bytes % memory_size; + let remainder = available_bytes + .checked_rem(memory_size) + .ok_or_else(|| error!(InvalidData, "memory_size of 0"))?; if remainder != 0 { return Err(error!( InvalidData, "{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, "memory_size of 0"))?; + Ok(num_of_elements) } pub fn contains_nested_heap_types(&self) -> bool { From a11359654bab9f0a079fb20b43d3f1eb61dfe437 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Tue, 5 Sep 2023 12:34:52 +0300 Subject: [PATCH 2/9] fix: handle multiplication with overflow --- packages/fuels-core/src/types/param_types.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index a10f07f250..74400ea607 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -69,7 +69,10 @@ impl ParamType { param_type: &ParamType, available_bytes: usize, ) -> Result { - let memory_size = param_type.compute_encoding_width() * WORD_SIZE; + let memory_size = param_type + .compute_encoding_width() + .checked_mul(WORD_SIZE) + .ok_or_else(|| error!(InvalidData, "overflow while calculating memory_size"))?; let remainder = available_bytes .checked_rem(memory_size) .ok_or_else(|| error!(InvalidData, "memory_size of 0"))?; From c25bd99720dfef1c506c2dd4fde42e9ad8026525 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Tue, 5 Sep 2023 13:09:03 +0300 Subject: [PATCH 3/9] fix: handle Results throughout decoder, add regressions to tests --- packages/fuels-core/src/codec/abi_decoder.rs | 109 +++++++++++++++++- packages/fuels-core/src/codec/abi_encoder.rs | 2 +- .../fuels-core/src/types/enum_variants.rs | 25 ++-- packages/fuels-core/src/types/param_types.rs | 57 +++++---- packages/fuels-programs/src/call_utils.rs | 29 ++--- 5 files changed, 174 insertions(+), 48 deletions(-) diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index ecacff9e90..125b2666c6 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -272,13 +272,18 @@ impl ABIDecoder { /// * `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(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 words_to_skip = enum_width - selected_variant.compute_encoding_width(); - let enum_content_bytes = skip(bytes, words_to_skip * WORD_SIZE)?; + let words_to_skip = enum_width - selected_variant.compute_encoding_width()?; + let enum_content_bytes = skip( + bytes, + words_to_skip + .checked_mul(WORD_SIZE) + .ok_or_else(|| error!(InvalidData, "multiplication overflow"))?, + )?; let result = Self::decode_token_in_enum(enum_content_bytes, variants, selected_variant)?; let selector = Box::new((discriminant, result.token, variants.clone())); @@ -807,4 +812,102 @@ mod tests { assert!(matches!(error, Error::InvalidData(str) if str.starts_with(expected_msg))); Ok(()) } + + mod regressions { + use super::*; + use ParamType::*; + + #[test] + pub fn division_by_zero() { + let result = ABIDecoder::decode_single(&Vector(Box::new(Array(Box::new(U16), 0))), &[]); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + pub fn multiply_overflow_enum() { + let result = ABIDecoder::decode_single( + &Enum { + variants: EnumVariants { + param_types: vec![ + Array(Box::new(Array(Box::new(RawSlice), 8)), 576469587185895432), + B256, + B256, + B256, + B256, + B256, + B256, + B256, + B256, + B256, + B256, + ], + }, + generics: vec![U16], + }, + &[0, 8, 8, 8, 9, 8, 0, 8, 8, 8, 8, 8, 15, 8, 8, 8], + ); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + pub fn multiply_overflow_vector() { + let result = ABIDecoder::decode_single( + &Vector(Box::new(Array(Box::new(Unit), 2308103648053880071))), + &[8, 8, 10, 7, 229, 8, 8, 8], + ); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + pub fn multiply_overflow_arith() { + let mut typ: ParamType = U16; + for _ in 0..50 { + typ = Array(Box::new(typ), 8); + } + let result = ABIDecoder::decode_single( + &Enum { + variants: EnumVariants { + param_types: vec![typ], + }, + generics: vec![U16], + }, + &[0, 8, 8, 51, 51, 51, 51, 51, 51, 51, 3, 8, 15, 8, 8, 8], + ); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + #[ignore] + pub fn capacity_overflow() { + let result = ABIDecoder::decode_single( + &Array( + Box::new(Array(Box::new(Tuple(vec![])), 7638104972323651592)), + 242, + ), + &[13, 0, 1, 0, 0, 106, 242, 8], + ); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + #[ignore] + pub fn stack_overflow() { + let mut typ: ParamType = U16; + for _ in 0..13500 { + typ = Vector(Box::new(typ)); + } + let result = ABIDecoder::decode_single(&typ, &[8, 9, 9, 9, 9, 9, 9, 9]); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + + #[test] + #[ignore] + pub fn capacity_maloc() { + let result = ABIDecoder::decode_single( + &Array(Box::new(U8), 72340198607880449), + &[8, 8, 7, 252, 201, 8, 8, 8], + ); + assert!(matches!(result, Err(Error::InvalidData(_)))); + } + } } diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index 6d53e049bd..e62d1a65a6 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 ef532bd876..2d09cd3f8c 100644 --- a/packages/fuels-core/src/types/enum_variants.rs +++ b/packages/fuels-core/src/types/enum_variants.rs @@ -8,7 +8,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct EnumVariants { - param_types: Vec, + pub param_types: Vec, } impl EnumVariants { @@ -41,26 +41,31 @@ 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 74400ea607..ad2a40bfed 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -4,7 +4,7 @@ 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, @@ -69,8 +69,8 @@ impl ParamType { param_type: &ParamType, available_bytes: usize, ) -> Result { - let memory_size = param_type - .compute_encoding_width() + let width = param_type.compute_encoding_width()?; + let memory_size = width .checked_mul(WORD_SIZE) .ok_or_else(|| error!(InvalidData, "overflow while calculating memory_size"))?; let remainder = available_bytes @@ -132,19 +132,23 @@ impl ParamType { } /// Compute the inner memory size of a containing heap type (`Bytes` or `Vec`s). - pub fn heap_inner_element_size(&self) -> Option { + pub fn heap_inner_element_size(&self) -> 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::()), - _ => None, + ParamType::Bytes | ParamType::String => 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; @@ -160,18 +164,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()), } } @@ -522,7 +537,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); @@ -533,7 +548,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); @@ -551,7 +566,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; @@ -571,7 +586,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; @@ -584,7 +599,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; diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index 198907ca73..5f9b813992 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, tx_parameters.gas_limit()); - 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); @@ -142,7 +142,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 { let n_heap_type_calls = calls .iter() .filter(|c| c.output_param.is_vm_heap_type()) @@ -153,17 +153,17 @@ fn compute_calls_instructions_len(calls: &[ContractCall]) -> usize { // Use placeholder for `call_param_offsets` and `output_param_type`, because the length of // the calling script doesn't depend on the underlying type, just on whether or not the // contract call output type is a heap type. - get_single_call_instructions(&CallOpcodeParamsOffset::default(), &ParamType::U64).len() + get_single_call_instructions(&CallOpcodeParamsOffset::default(), &ParamType::U64)?.len() * n_stack_type_calls; let total_instructions_len_heap_data = get_single_call_instructions( &CallOpcodeParamsOffset::default(), &ParamType::Vector(Box::from(ParamType::U64)), - ) + )? .len() * n_heap_type_calls; - total_instructions_len_stack_data + total_instructions_len_heap_data + Ok(total_instructions_len_stack_data + total_instructions_len_heap_data) } /// Compute how much of each asset is required based on all `CallParameters` of the `ContractCalls` @@ -215,13 +215,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: @@ -304,7 +307,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() @@ -333,7 +336,7 @@ pub(crate) fn get_single_call_instructions( ] .to_vec(); // The instructions are different if you want to return data that was on the heap - if let Some(inner_type_byte_size) = output_param_type.heap_inner_element_size() { + if let Some(inner_type_byte_size) = output_param_type.heap_inner_element_size()? { instructions.extend([ // The RET register contains the pointer address of the `CALL` return (a stack // address). @@ -355,7 +358,7 @@ pub(crate) fn get_single_call_instructions( } #[allow(clippy::iter_cloned_collect)] - instructions.into_iter().collect::>() + Ok(instructions.into_iter().collect::>()) } /// Returns the assets and contracts that will be consumed ([`Input`]s) From 89533221200f259f51dbc66d3b4ac11a5c05f195 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Mon, 25 Sep 2023 17:28:14 +0300 Subject: [PATCH 4/9] fix: add DebugWithDepth to Debug-format nested types --- .../src/codec/abi_decoder/bounded_decoder.rs | 100 +++++++++++++++++- 1 file changed, 95 insertions(+), 5 deletions(-) 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 7f41dcc90a..09168a2d5f 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -1,4 +1,4 @@ -use std::{convert::TryInto, str}; +use std::{convert::TryInto, fmt, str}; use fuel_types::bytes::padded_len_usize; @@ -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 { - Self::is_type_decodable(param_type)?; + self.is_type_decodable(param_type)?; Ok(self.decode_param(param_type, bytes)?.token) } @@ -46,18 +48,19 @@ impl BoundedDecoder { bytes: &[u8], ) -> Result> { for param_type in param_types { - Self::is_type_decodable(param_type)?; + self.is_type_decodable(param_type)?; } let (tokens, _) = self.decode_params(param_types, bytes)?; Ok(tokens) } - fn is_type_decodable(param_type: &ParamType) -> Result<()> { + fn is_type_decodable(&self, param_type: &ParamType) -> Result<()> { if param_type.contains_nested_heap_types() { Err(error!( InvalidType, - "Type {param_type:?} contains nested heap types (`Vec` or `Bytes`), this is not supported." + "Type {:?} contains nested heap types (`Vec` or `Bytes`), this is not supported.", + DebugWithDepth::new(param_type, self.config.max_depth) )) } else { Ok(()) @@ -458,3 +461,90 @@ fn skip(slice: &[u8], num_bytes: usize) -> Result<&[u8]> { Ok(&slice[num_bytes..]) } } + +/// 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), + } + } +} From 7a128b250a7569337b8468ad94667318987d3b54 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Thu, 5 Oct 2023 13:12:08 +0300 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Ahmed Sagdati <37515857+segfault-magnet@users.noreply.github.com> Co-authored-by: iqdecay --- packages/fuels-core/src/codec/abi_decoder.rs | 5 ++++- packages/fuels-core/src/types/param_types.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index 8d68107aea..d23d46624e 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -524,7 +524,10 @@ mod tests { #[test] pub fn division_by_zero() { - let result = ABIDecoder::default().decode(&Vector(Box::new(Array(Box::new(U16), 0))), &[]); + let param_type = Vec::<[u16; 0]>::param_type(); + + let result = ABIDecoder::default().decode(¶m_type, &[]); + assert!(matches!(result, Err(Error::InvalidType(_)))); } diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index d74ee2655a..437c1741ba 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -84,7 +84,7 @@ impl ParamType { } let num_of_elements = available_bytes .checked_div(memory_size) - .ok_or_else(|| error!(InvalidData, "memory_size of 0"))?; + .ok_or_else(|| error!(InvalidData, "Type {param_type} has a memory_size of 0"))?; Ok(num_of_elements) } From 13381c44a178c89d6f76921156495e40198ca5ba Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Thu, 5 Oct 2023 13:40:50 +0300 Subject: [PATCH 6/9] Apply rest of the suggestions, renaming, cargo fmt --- packages/fuels-core/src/codec/abi_decoder.rs | 70 ++++++++----------- .../src/codec/abi_decoder/bounded_decoder.rs | 9 ++- .../fuels-core/src/types/enum_variants.rs | 2 +- packages/fuels-core/src/types/param_types.rs | 6 +- 4 files changed, 41 insertions(+), 46 deletions(-) diff --git a/packages/fuels-core/src/codec/abi_decoder.rs b/packages/fuels-core/src/codec/abi_decoder.rs index d23d46624e..8a47c98e82 100644 --- a/packages/fuels-core/src/codec/abi_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder.rs @@ -84,6 +84,7 @@ mod tests { use std::vec; use super::*; + use crate::traits::Parameterize; use crate::types::U256; use crate::{ constants::WORD_SIZE, @@ -525,9 +526,7 @@ mod tests { #[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(_)))); } @@ -535,51 +534,46 @@ mod tests { pub fn multiply_overflow_enum() { let result = ABIDecoder::default().decode( &Enum { - variants: EnumVariants { - param_types: vec![ - Array(Box::new(Array(Box::new(RawSlice), 8)), 576469587185895432), - B256, - B256, - B256, - B256, - B256, - B256, - B256, - B256, - B256, - B256, - ], - }, + 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], }, - &[0, 8, 8, 8, 9, 8, 0, 8, 8, 8, 8, 8, 15, 8, 8, 8], + &[], ); assert!(matches!(result, Err(Error::InvalidData(_)))); } #[test] pub fn multiply_overflow_vector() { - let result = ABIDecoder::default().decode( - &Vector(Box::new(Array(Box::new(Unit), 2308103648053880071))), - &[8, 8, 10, 7, 229, 8, 8, 8], - ); + 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 typ: ParamType = U16; + let mut param_type: ParamType = U16; for _ in 0..50 { - typ = Array(Box::new(typ), 8); + param_type = Array(Box::new(param_type), 8); } let result = ABIDecoder::default().decode( &Enum { - variants: EnumVariants { - param_types: vec![typ], - }, + variants: EnumVariants::new(vec![param_type]).unwrap(), generics: vec![U16], }, - &[0, 8, 8, 51, 51, 51, 51, 51, 51, 51, 3, 8, 15, 8, 8, 8], + &[], ); assert!(matches!(result, Err(Error::InvalidData(_)))); } @@ -587,32 +581,26 @@ mod tests { #[test] pub fn capacity_overflow() { let result = ABIDecoder::default().decode( - &Array( - Box::new(Array(Box::new(Tuple(vec![])), 7638104972323651592)), - 242, - ), - &[13, 0, 1, 0, 0, 106, 242, 8], + &Array(Box::new(Array(Box::new(Tuple(vec![])), usize::MAX)), 1), + &[], ); - dbg!(&result); assert!(matches!(result, Err(Error::InvalidType(_)))); } #[test] pub fn stack_overflow() { - let mut typ: ParamType = U16; + let mut param_type: ParamType = U16; for _ in 0..13500 { - typ = Vector(Box::new(typ)); + param_type = Vector(Box::new(param_type)); } - let result = ABIDecoder::default().decode(&typ, &[8, 9, 9, 9, 9, 9, 9, 9]); + let result = ABIDecoder::default().decode(¶m_type, &[]); assert!(matches!(result, Err(Error::InvalidType(_)))); } #[test] pub fn capacity_maloc() { - let result = ABIDecoder::default().decode( - &Array(Box::new(U8), 72340198607880449), - &[8, 8, 7, 252, 201, 8, 8, 8], - ); + let param_type = Array(Box::new(U8), usize::MAX); + let result = ABIDecoder::default().decode(¶m_type, &[]); assert!(matches!(result, Err(Error::InvalidData(_)))); } 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 09168a2d5f..5c6b632b5d 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -317,7 +317,12 @@ impl BoundedDecoder { let selected_variant = variants.param_type_of_variant(discriminant)?; let words_to_skip = enum_width - selected_variant.compute_encoding_width()?; - let bytes_to_skip = words_to_skip .checked_mul(WORD_SIZE).ok_or_else(|| error!(InvalidData, "attempt to multiply words_to_skip ({words_to_skip:?}) by WORD_SIZE ({WORD_SIZE:?}) with overflow"))?; + 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)?; @@ -519,7 +524,7 @@ impl<'a> fmt::Debug for DebugWithDepth<'a> { .field( "variants", &variants - .param_types + .param_types() .iter() .map(|variant| self.descend(variant)) .collect::>(), diff --git a/packages/fuels-core/src/types/enum_variants.rs b/packages/fuels-core/src/types/enum_variants.rs index 2d09cd3f8c..e6f74bddde 100644 --- a/packages/fuels-core/src/types/enum_variants.rs +++ b/packages/fuels-core/src/types/enum_variants.rs @@ -8,7 +8,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct EnumVariants { - pub param_types: Vec, + param_types: Vec, } impl EnumVariants { diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index 437c1741ba..94ee5b92d2 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -68,7 +68,9 @@ impl ParamType { available_bytes: usize, ) -> Result { let encoding_width = param_type.compute_encoding_width()?; - let memory_size = encoding_width.checked_mul(WORD_SIZE).ok_or_else(|| error!(InvalidData, "attempt to multiply encoding_width ({encoding_width:?}) by WORD_SIZE ({WORD_SIZE:?}) with overflow"))?; + 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, @@ -84,7 +86,7 @@ impl ParamType { } 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_or_else(|| error!(InvalidData, "Type {param_type:?} has a memory_size of 0"))?; Ok(num_of_elements) } From 3da2abc6e931325a2f6523e3bbf789d37f124cc1 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko Date: Fri, 6 Oct 2023 19:54:48 +0300 Subject: [PATCH 7/9] Move `DebugWithDepth` to `param_types.rs` --- packages/fuels-core/src/types/param_types.rs | 90 ++++++++++++++++++- packages/fuels-core/src/utils.rs | 3 - .../fuels-core/src/utils/debug_with_depth.rs | 90 ------------------- 3 files changed, 88 insertions(+), 95 deletions(-) delete mode 100644 packages/fuels-core/src/utils/debug_with_depth.rs diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index 21d3f6569d..cd66785d84 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, iter::zip}; +use std::{collections::HashMap, fmt, iter::zip}; use fuel_abi_types::{ abi::program::{TypeApplication, TypeDeclaration}, @@ -12,7 +12,6 @@ use crate::{ enum_variants::EnumVariants, errors::{error, Error, Result}, }, - utils::DebugWithDepth, }; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -559,6 +558,93 @@ 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 { diff --git a/packages/fuels-core/src/utils.rs b/packages/fuels-core/src/utils.rs index 600d3e65bb..3c6f9d3a61 100644 --- a/packages/fuels-core/src/utils.rs +++ b/packages/fuels-core/src/utils.rs @@ -1,5 +1,2 @@ -mod debug_with_depth; -pub(crate) use debug_with_depth::DebugWithDepth; - pub mod constants; pub mod offsets; diff --git a/packages/fuels-core/src/utils/debug_with_depth.rs b/packages/fuels-core/src/utils/debug_with_depth.rs deleted file mode 100644 index c4b6bdc989..0000000000 --- a/packages/fuels-core/src/utils/debug_with_depth.rs +++ /dev/null @@ -1,90 +0,0 @@ -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 } => 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), - } - } -} From 3742a4e3513072237cdb9b51d8f550e5951eeb71 Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko <12615679+Br1ght0ne@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:27:53 +0300 Subject: [PATCH 8/9] Use full type names in test messages --- packages/fuels-core/src/types/param_types.rs | 85 ++++++++++++-------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index 7da9da2326..c00c9dff74 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -650,7 +650,7 @@ impl<'a> fmt::Debug for DebugWithDepth<'a> { 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; @@ -1444,20 +1444,25 @@ mod tests { #[test] fn validate_is_decodable_simple_types() -> Result<()> { - assert!(ParamType::U8.validate_is_decodable(0).is_ok()); - assert!(ParamType::U16.validate_is_decodable(0).is_ok()); - assert!(ParamType::U32.validate_is_decodable(0).is_ok()); - assert!(ParamType::U64.validate_is_decodable(0).is_ok()); - assert!(ParamType::U128.validate_is_decodable(0).is_ok()); - assert!(ParamType::U256.validate_is_decodable(0).is_ok()); - assert!(ParamType::Bool.validate_is_decodable(0).is_ok()); - assert!(ParamType::B256.validate_is_decodable(0).is_ok()); - assert!(ParamType::Unit.validate_is_decodable(0).is_ok()); - assert!(ParamType::StringSlice.validate_is_decodable(0).is_ok()); - assert!(ParamType::StringArray(10).validate_is_decodable(0).is_ok()); - assert!(ParamType::RawSlice.validate_is_decodable(0).is_ok()); - assert!(ParamType::Bytes.validate_is_decodable(0).is_ok()); - assert!(ParamType::String.validate_is_decodable(0).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(()) } @@ -1465,19 +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: type ... is not decodable: 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(0) + 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(0).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)); @@ -1502,7 +1511,8 @@ mod tests { #[test] fn validate_is_decodable_enum_containing_bytes() -> Result<()> { - let can_be_decoded = |p: ParamType| p.validate_is_decodable(0).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())?; @@ -1523,7 +1533,7 @@ mod tests { variants: variants_two_bytes_type.clone(), generics: param_types_no_bytes.clone(), } - .validate_is_decodable(0) + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected @@ -1543,7 +1553,7 @@ mod tests { variants: variants_two_bytes_type.clone(), generics: param_types_containing_bytes.clone(), } - .validate_is_decodable(0) + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected @@ -1554,25 +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: type ... is not decodable: 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(0) + 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(0).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( @@ -1602,7 +1616,8 @@ mod tests { #[test] fn validate_is_decodable_enum_containing_vector() -> Result<()> { - let can_be_decoded = |p: ParamType| p.validate_is_decodable(0).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, @@ -1630,7 +1645,7 @@ mod tests { variants: variants_two_vector_type.clone(), generics: param_types_no_vector.clone(), } - .validate_is_decodable(0) + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected @@ -1650,7 +1665,7 @@ mod tests { variants: variants_two_vector_type.clone(), generics: param_types_containing_vector.clone(), } - .validate_is_decodable(0) + .validate_is_decodable(max_depth) .expect_err("Should not be decodable") .to_string(), expected From 66e577e4ecf3ce00928a14cb03e545e6ce81cdcd Mon Sep 17 00:00:00 2001 From: Oleksii Filonenko <12615679+Br1ght0ne@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:31:07 +0300 Subject: [PATCH 9/9] Re-wrap multiplication of `bytes_to_skip` Meaningful change I lost in a merge conflict battle. --- .../fuels-core/src/codec/abi_decoder/bounded_decoder.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 94676d0d5a..788b7f780c 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -313,7 +313,13 @@ impl BoundedDecoder { .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 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()));