diff --git a/packages/fuels-code-gen/src/program_bindings/custom_types.rs b/packages/fuels-code-gen/src/program_bindings/custom_types.rs index 88af247e7f..0f11ca9986 100644 --- a/packages/fuels-code-gen/src/program_bindings/custom_types.rs +++ b/packages/fuels-code-gen/src/program_bindings/custom_types.rs @@ -101,7 +101,10 @@ fn is_type_sdk_provided(type_path: &TypePath) -> bool { fn is_type_unused(type_path: &TypePath) -> bool { let msg = "Known to be correct"; [ + // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. + TypePath::new("RawBytes").expect(msg), TypePath::new("std::vec::RawVec").expect(msg), + TypePath::new("std::bytes::RawBytes").expect(msg), // TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked. TypePath::new("RawVec").expect(msg), ] diff --git a/packages/fuels-code-gen/src/program_bindings/resolved_type.rs b/packages/fuels-code-gen/src/program_bindings/resolved_type.rs index f329b8795e..8f9cea6aee 100644 --- a/packages/fuels-code-gen/src/program_bindings/resolved_type.rs +++ b/packages/fuels-code-gen/src/program_bindings/resolved_type.rs @@ -74,6 +74,7 @@ impl TypeResolver { Self::to_array, Self::to_sized_ascii_string, Self::to_tuple, + Self::to_bytes, Self::to_raw_slice, Self::to_custom_type, ]; @@ -110,6 +111,18 @@ impl TypeResolver { })) } + fn to_bytes(&self, type_application: &FullTypeApplication) -> Result> { + if &type_application.type_decl.type_field == "struct Bytes" { + let type_name = quote! {::fuels::types::Bytes}; + Ok(Some(ResolvedType { + type_name, + generic_params: vec![], + })) + } else { + Ok(None) + } + } + fn to_array(&self, type_application: &FullTypeApplication) -> Result> { let type_decl = &type_application.type_decl; let Some(len) = extract_array_len(&type_decl.type_field) else { diff --git a/packages/fuels-code-gen/src/program_bindings/utils.rs b/packages/fuels-code-gen/src/program_bindings/utils.rs index 2037078ca9..a989f0fe79 100644 --- a/packages/fuels-code-gen/src/program_bindings/utils.rs +++ b/packages/fuels-code-gen/src/program_bindings/utils.rs @@ -131,6 +131,7 @@ pub(crate) fn sdk_provided_custom_types_lookup() -> HashMap ("std::vec::Vec", "::std::vec::Vec"), ("std::result::Result", "::core::result::Result"), ("std::option::Option", "::core::option::Option"), + ("std::bytes::Bytes", "::fuels::types::Bytes"), ] .into_iter() .map(|(original_type_path, provided_type_path)| { diff --git a/packages/fuels-core/src/abi_decoder.rs b/packages/fuels-core/src/abi_decoder.rs index 1680583286..180297959d 100644 --- a/packages/fuels-core/src/abi_decoder.rs +++ b/packages/fuels-core/src/abi_decoder.rs @@ -51,10 +51,10 @@ impl ABIDecoder { } fn decode_param(param_type: &ParamType, bytes: &[u8]) -> Result { - if param_type.contains_nested_vectors() { + if param_type.contains_nested_heap_types() { return Err(error!( InvalidData, - "Type {param_type:?} contains nested vectors, this is not supported." + "Type {param_type:?} contains nested heap types (`Vec` or `Bytes`), this is not supported." )); } match param_type { @@ -72,11 +72,19 @@ impl ABIDecoder { ParamType::Enum { variants, .. } => Self::decode_enum(bytes, variants), ParamType::Tuple(types) => Self::decode_tuple(types, bytes), ParamType::Vector(param_type) => Self::decode_vector(param_type, bytes), + ParamType::Bytes => Self::decode_bytes(bytes), } } + fn decode_bytes(bytes: &[u8]) -> Result { + Ok(DecodeResult { + token: Token::Bytes(bytes.to_vec()), + bytes_read: bytes.len(), + }) + } + fn decode_vector(param_type: &ParamType, bytes: &[u8]) -> Result { - let num_of_elements = calculate_num_of_elements(param_type, bytes)?; + let num_of_elements = ParamType::calculate_num_of_elements(param_type, bytes.len())?; let (tokens, bytes_read) = Self::decode_multiple(vec![param_type; num_of_elements], bytes)?; Ok(DecodeResult { @@ -131,7 +139,9 @@ impl ABIDecoder { } fn decode_raw_slice(bytes: &[u8]) -> Result { - let num_of_elements = calculate_num_of_elements(&ParamType::U64, bytes)?; + let raw_slice_element = ParamType::U64; + let num_of_elements = + ParamType::calculate_num_of_elements(&raw_slice_element, bytes.len())?; let (tokens, bytes_read) = Self::decode_multiple(&vec![ParamType::U64; num_of_elements], bytes)?; let elements = tokens @@ -324,17 +334,6 @@ fn skip(slice: &[u8], num_bytes: usize) -> Result<&[u8]> { } } -fn calculate_num_of_elements(param_type: &ParamType, bytes: &[u8]) -> Result { - let memory_size = param_type.compute_encoding_width() * WORD_SIZE; - if bytes.len() % memory_size != 0 { - return Err(error!( - InvalidData, - "The bytes provided do not correspond to a Vec<{:?}> got: {:?}", param_type, bytes - )); - } - Ok(bytes.len() / memory_size) -} - #[cfg(test)] mod tests { use std::vec; diff --git a/packages/fuels-core/src/abi_encoder.rs b/packages/fuels-core/src/abi_encoder.rs index bd2544814a..f58d5ba610 100644 --- a/packages/fuels-core/src/abi_encoder.rs +++ b/packages/fuels-core/src/abi_encoder.rs @@ -122,7 +122,14 @@ impl ABIEncoder { Token::Tuple(arg_tuple) => Self::encode_tuple(arg_tuple)?, Token::Unit => vec![Self::encode_unit()], Token::RawSlice(_) => { - unimplemented!("Encoding a raw_slice is currently not supported by the Fuel spec.") + unimplemented!( + "Encoding a `RawSlice` type is currently not supported by the Fuel spec." + ) + } + Token::Bytes(_) => { + unimplemented!( + "Encoding a `Bytes` type is currently not supported by the Fuel spec." + ) } }; diff --git a/packages/fuels-core/src/function_selector.rs b/packages/fuels-core/src/function_selector.rs index 29e0c1fe68..56c4b3b123 100644 --- a/packages/fuels-core/src/function_selector.rs +++ b/packages/fuels-core/src/function_selector.rs @@ -71,7 +71,8 @@ fn resolve_arg(arg: &ParamType) -> String { let inner = resolve_arg(el_type); format!("s<{inner}>(s<{inner}>(rawptr,u64),u64)") } - ParamType::RawSlice => unimplemented!("Raw slices are not supported as arguments"), + ParamType::RawSlice => unimplemented!("`RawSlice` type is not supported as arguments"), + ParamType::Bytes => unimplemented!("`Bytes` type is not supported as arguments"), } } diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index baf1758d60..b6c2929726 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -75,23 +75,28 @@ pub(crate) async fn build_tx_from_contract_calls( Ok(tx) } -/// Compute the length of the calling scripts for the two types of 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. /// 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 is a vector. +/// output type is a heap type. fn compute_calls_instructions_len(calls: &[ContractCall]) -> usize { - let n_vectors_calls = calls.iter().filter(|c| c.output_param.is_vector()).count(); + let n_heap_data_calls = calls + .iter() + .filter(|c| c.output_param.is_vm_heap_type()) + .count(); - let calls_instructions_len_no_vectors = + let calls_instructions_len_stack_data = get_single_call_instructions(&CallOpcodeParamsOffset::default(), &ParamType::U64).len() - * (calls.len() - n_vectors_calls); - let calls_instructions_len_vectors = get_single_call_instructions( + * (calls.len() - n_heap_data_calls); + let calls_instructions_len_heap_data = get_single_call_instructions( &CallOpcodeParamsOffset::default(), &ParamType::Vector(Box::from(ParamType::U64)), ) .len() - * n_vectors_calls; - calls_instructions_len_no_vectors + calls_instructions_len_vectors + * n_heap_data_calls; + + calls_instructions_len_stack_data + calls_instructions_len_heap_data } /// Compute how much of each asset is required based on all `CallParameters` of the `ContractCalls` @@ -261,23 +266,21 @@ 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 ParamType::Vector(inner_param_type) = output_param_type { - let inner_type_byte_size: u16 = - (inner_param_type.compute_encoding_width() * WORD_SIZE) as u16; + 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). - // The RETL register contains the length of the `CALL` return (=24 because the vec - // struct takes 3 WORDs). We don't actually need it unless the vec struct encoding + // The RETL register contains the length of the `CALL` return (=24 because the Vec/Bytes + // struct takes 3 WORDs). We don't actually need it unless the Vec/Bytes struct encoding // changes in the compiler. // Load the word located at the address contained in RET, it's a word that // translates to a heap address. 0x15 is a free register. op::lw(0x15, RegId::RET, 0), - // We know a vector struct has its third byte contain the length of the vector, so - // use a 2 offset to store the vector length in 0x16, which is a free register. + // We know a Vec/Bytes struct has its third WORD contain the length of the underlying + // vector, so use a 2 offset to store the length in 0x16, which is a free register. op::lw(0x16, RegId::RET, 2), - // The in-memory size of the vector is (in-memory size of the inner type) * length - op::muli(0x16, 0x16, inner_type_byte_size), + // The in-memory size of the type is (in-memory size of the inner type) * length + op::muli(0x16, 0x16, inner_type_byte_size as u16), op::retd(0x15, 0x16), ]); } diff --git a/packages/fuels-programs/src/contract.rs b/packages/fuels-programs/src/contract.rs index d0718a4557..4a91dbbf8c 100644 --- a/packages/fuels-programs/src/contract.rs +++ b/packages/fuels-programs/src/contract.rs @@ -546,7 +546,7 @@ pub fn get_decoded_output( None => null_contract_id, }; let encoded_value = match output_param.get_return_location() { - ReturnLocation::ReturnData if output_param.is_vector() => { + ReturnLocation::ReturnData if output_param.is_vm_heap_type() => { // If the output of the function is a vector, then there are 2 consecutive ReturnData // receipts. The first one is the one that returns the pointer to the vec struct in the // VM memory, the second one contains the actual vector bytes (that the previous receipt diff --git a/packages/fuels-types/src/core.rs b/packages/fuels-types/src/core.rs index 0793207f02..9a6f7e5742 100644 --- a/packages/fuels-types/src/core.rs +++ b/packages/fuels-types/src/core.rs @@ -10,11 +10,14 @@ use crate::{ }; mod bits; +mod bytes; mod native; mod raw_slice; mod sized_ascii_string; -pub use crate::core::{bits::*, native::*, raw_slice::RawSlice, sized_ascii_string::*}; +pub use crate::core::{ + bits::*, bytes::Bytes, native::*, raw_slice::RawSlice, sized_ascii_string::*, +}; pub type ByteArray = [u8; 8]; pub type Selector = ByteArray; @@ -85,6 +88,7 @@ pub enum Token { Enum(Box), Tuple(Vec), RawSlice(Vec), + Bytes(Vec), } impl fmt::Display for Token { diff --git a/packages/fuels-types/src/core/bytes.rs b/packages/fuels-types/src/core/bytes.rs new file mode 100644 index 0000000000..10f0cb9a2b --- /dev/null +++ b/packages/fuels-types/src/core/bytes.rs @@ -0,0 +1,20 @@ +#[derive(Debug, PartialEq, Clone, Eq)] +pub struct Bytes(pub Vec); + +impl From for Vec { + fn from(raw_slice: Bytes) -> Vec { + raw_slice.0 + } +} + +impl PartialEq> for Bytes { + fn eq(&self, other: &Vec) -> bool { + self.0 == *other + } +} + +impl PartialEq for Vec { + fn eq(&self, other: &Bytes) -> bool { + *self == other.0 + } +} diff --git a/packages/fuels-types/src/param_types.rs b/packages/fuels-types/src/param_types.rs index b46693d4af..916e693591 100644 --- a/packages/fuels-types/src/param_types.rs +++ b/packages/fuels-types/src/param_types.rs @@ -44,6 +44,7 @@ pub enum ParamType { }, Tuple(Vec), RawSlice, + Bytes, } pub enum ReturnLocation { @@ -64,42 +65,72 @@ impl ParamType { } } - pub fn contains_nested_vectors(&self) -> bool { + /// 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_width() * WORD_SIZE; + let remainder = available_bytes % memory_size; + if remainder != 0 { + return Err(error!( + InvalidData, + "{remainder} extra bytes detected while decoding heap type" + )); + } + Ok(available_bytes / memory_size) + } + + pub fn contains_nested_heap_types(&self) -> bool { match &self { - ParamType::Vector(param_type) => param_type.uses_vectors(), - _ => self.uses_vectors(), + ParamType::Vector(param_type) => param_type.uses_heap_types(), + ParamType::Bytes => false, + _ => self.uses_heap_types(), } } - fn uses_vectors(&self) -> bool { + fn uses_heap_types(&self) -> bool { match &self { - ParamType::Vector(..) => true, - ParamType::Array(param_type, ..) => param_type.uses_vectors(), - ParamType::Tuple(param_types, ..) => Self::any_nested_vectors(param_types), + ParamType::Vector(..) | ParamType::Bytes => true, + ParamType::Array(param_type, ..) => param_type.uses_heap_types(), + ParamType::Tuple(param_types, ..) => Self::any_nested_heap_types(param_types), ParamType::Enum { generics, variants, .. } => { let variants_types = variants.param_types(); - Self::any_nested_vectors(chain!(generics, &variants_types)) + Self::any_nested_heap_types(chain!(generics, &variants_types)) } ParamType::Struct { fields, generics, .. } => { let fields = fields.iter().map(|(_, param_type)| param_type); - Self::any_nested_vectors(chain!(fields, generics)) + Self::any_nested_heap_types(chain!(fields, generics)) } _ => false, } } - fn any_nested_vectors<'a>(param_types: impl IntoIterator) -> bool { + fn any_nested_heap_types<'a>(param_types: impl IntoIterator) -> bool { param_types .into_iter() - .any(|param_type| param_type.uses_vectors()) + .any(|param_type| param_type.uses_heap_types()) } - pub fn is_vector(&self) -> bool { - matches!(self, Self::Vector(..)) + pub fn is_vm_heap_type(&self) -> bool { + matches!(self, ParamType::Vector(..) | ParamType::Bytes) + } + + /// Compute the inner memory size of a containing heap type (`Bytes` or `Vec`s). + pub fn heap_inner_element_size(&self) -> Option { + match &self { + ParamType::Vector(inner_param_type) => { + Some(inner_param_type.compute_encoding_width() * WORD_SIZE) + } + // `Bytes` type is byte-packed in the VM, so it's the size of an u8 + ParamType::Bytes => Some(std::mem::size_of::()), + _ => None, + } } /// Calculates the number of `WORD`s the VM expects this parameter to be encoded in. @@ -133,8 +164,12 @@ impl ParamType { ParamType::RawSlice => unimplemented!( "Raw slices are not supported as inputs, so needing the encoding width of a RawSlice should not happen." ), + ParamType::Bytes => unimplemented!( + "Bytes are not supported as inputs, so needing the encoding width of a Bytes should not happen." + ), } } + /// For when you need to convert a ABI JSON's TypeApplication into a ParamType. /// /// # Arguments @@ -1255,22 +1290,23 @@ mod tests { } #[test] - fn contains_nested_vectors_false_on_simple_types() -> Result<()> { - // Simple types cannot have nested vectors - assert!(!ParamType::Unit.contains_nested_vectors()); - assert!(!ParamType::U8.contains_nested_vectors()); - assert!(!ParamType::U16.contains_nested_vectors()); - assert!(!ParamType::U32.contains_nested_vectors()); - assert!(!ParamType::U64.contains_nested_vectors()); - assert!(!ParamType::Bool.contains_nested_vectors()); - assert!(!ParamType::B256.contains_nested_vectors()); - assert!(!ParamType::String(10).contains_nested_vectors()); - assert!(!ParamType::RawSlice.contains_nested_vectors()); + fn contains_nested_heap_types_false_on_simple_types() -> Result<()> { + // Simple types cannot have nested heap types + assert!(!ParamType::Unit.contains_nested_heap_types()); + assert!(!ParamType::U8.contains_nested_heap_types()); + assert!(!ParamType::U16.contains_nested_heap_types()); + assert!(!ParamType::U32.contains_nested_heap_types()); + assert!(!ParamType::U64.contains_nested_heap_types()); + assert!(!ParamType::Bool.contains_nested_heap_types()); + assert!(!ParamType::B256.contains_nested_heap_types()); + assert!(!ParamType::String(10).contains_nested_heap_types()); + assert!(!ParamType::RawSlice.contains_nested_heap_types()); + assert!(!ParamType::Bytes.contains_nested_heap_types()); Ok(()) } #[test] - fn contains_nested_vectors_complex_types() -> Result<()> { + fn test_complex_types_for_nested_heap_types_containing_vectors() -> Result<()> { let base_vector = ParamType::Vector(Box::from(ParamType::U8)); let tuples_no_nested_vec = vec![ ("Bim".to_string(), ParamType::U16), @@ -1284,53 +1320,120 @@ mod tests { let param_types_no_nested_vec = vec![ParamType::U64, ParamType::U32]; let param_types_nested_vec = vec![ParamType::Unit, ParamType::Bool, base_vector.clone()]; - assert!(!base_vector.contains_nested_vectors()); - assert!(ParamType::Vector(Box::from(base_vector.clone())).contains_nested_vectors()); + let is_nested = |param_type: ParamType| assert!(param_type.contains_nested_heap_types()); + let not_nested = |param_type: ParamType| assert!(!param_type.contains_nested_heap_types()); + + not_nested(base_vector.clone()); + is_nested(ParamType::Vector(Box::from(base_vector.clone()))); - assert!(!ParamType::Array(Box::from(ParamType::U8), 10).contains_nested_vectors()); - assert!(ParamType::Array(Box::from(base_vector), 10).contains_nested_vectors()); + not_nested(ParamType::Array(Box::from(ParamType::U8), 10)); + is_nested(ParamType::Array(Box::from(base_vector), 10)); - assert!(!ParamType::Tuple(param_types_no_nested_vec.clone()).contains_nested_vectors()); - assert!(ParamType::Tuple(param_types_nested_vec.clone()).contains_nested_vectors()); + not_nested(ParamType::Tuple(param_types_no_nested_vec.clone())); + is_nested(ParamType::Tuple(param_types_nested_vec.clone())); - assert!(!ParamType::Struct { + not_nested(ParamType::Struct { name: "StructName".to_string(), generics: param_types_no_nested_vec.clone(), fields: tuples_no_nested_vec.clone(), - } - .contains_nested_vectors()); - assert!(ParamType::Struct { + }); + is_nested(ParamType::Struct { name: "StructName".to_string(), generics: param_types_nested_vec.clone(), - fields: tuples_no_nested_vec.clone() - } - .contains_nested_vectors()); - assert!(ParamType::Struct { + fields: tuples_no_nested_vec.clone(), + }); + is_nested(ParamType::Struct { name: "StructName".to_string(), generics: param_types_no_nested_vec.clone(), - fields: tuples_with_nested_vec.clone() - } - .contains_nested_vectors()); + fields: tuples_with_nested_vec.clone(), + }); - assert!(!ParamType::Enum { + not_nested(ParamType::Enum { name: "EnumName".to_string(), variants: EnumVariants::new(tuples_no_nested_vec.clone())?, - generics: param_types_no_nested_vec.clone() - } - .contains_nested_vectors()); - assert!(ParamType::Enum { + generics: param_types_no_nested_vec.clone(), + }); + is_nested(ParamType::Enum { name: "EnumName".to_string(), variants: EnumVariants::new(tuples_with_nested_vec)?, - generics: param_types_no_nested_vec - } - .contains_nested_vectors()); - assert!(ParamType::Enum { + generics: param_types_no_nested_vec, + }); + is_nested(ParamType::Enum { name: "EnumName".to_string(), variants: EnumVariants::new(tuples_no_nested_vec)?, - generics: param_types_nested_vec - } - .contains_nested_vectors()); + generics: param_types_nested_vec, + }); + Ok(()) + } + #[test] + fn test_complex_types_for_nested_heap_types_containing_bytes() -> Result<()> { + let base_bytes = ParamType::Bytes; + let tuples_no_nested_bytes = vec![ + ("Bim".to_string(), ParamType::U16), + ("Bam".to_string(), ParamType::Bool), + ]; + let tuples_with_nested_bytes = vec![ + ("Zim".to_string(), ParamType::U64), + ("Zam".to_string(), ParamType::U32), + ("Boum".to_string(), base_bytes.clone()), + ]; + let param_types_no_nested_bytes = vec![ParamType::U64, ParamType::U32]; + let param_types_nested_bytes = vec![ParamType::Unit, ParamType::Bool, base_bytes.clone()]; + + let is_nested = |param_type: ParamType| assert!(param_type.contains_nested_heap_types()); + let not_nested = |param_type: ParamType| assert!(!param_type.contains_nested_heap_types()); + + not_nested(base_bytes.clone()); + is_nested(ParamType::Vector(Box::from(base_bytes.clone()))); + + not_nested(ParamType::Array(Box::from(ParamType::U8), 10)); + is_nested(ParamType::Array(Box::from(base_bytes), 10)); + + not_nested(ParamType::Tuple(param_types_no_nested_bytes.clone())); + is_nested(ParamType::Tuple(param_types_nested_bytes.clone())); + + let not_nested_struct = ParamType::Struct { + name: "StructName".to_string(), + generics: param_types_no_nested_bytes.clone(), + fields: tuples_no_nested_bytes.clone(), + }; + not_nested(not_nested_struct); + + let nested_struct = ParamType::Struct { + name: "StructName".to_string(), + generics: param_types_nested_bytes.clone(), + fields: tuples_no_nested_bytes.clone(), + }; + is_nested(nested_struct); + + let nested_struct = ParamType::Struct { + name: "StructName".to_string(), + generics: param_types_no_nested_bytes.clone(), + fields: tuples_with_nested_bytes.clone(), + }; + is_nested(nested_struct); + + let not_nested_enum = ParamType::Enum { + name: "EnumName".to_string(), + variants: EnumVariants::new(tuples_no_nested_bytes.clone())?, + generics: param_types_no_nested_bytes.clone(), + }; + not_nested(not_nested_enum); + + let nested_enum = ParamType::Enum { + name: "EnumName".to_string(), + variants: EnumVariants::new(tuples_with_nested_bytes)?, + generics: param_types_no_nested_bytes, + }; + is_nested(nested_enum); + + let nested_enum = ParamType::Enum { + name: "EnumName".to_string(), + variants: EnumVariants::new(tuples_no_nested_bytes)?, + generics: param_types_nested_bytes, + }; + is_nested(nested_enum); Ok(()) } #[test] diff --git a/packages/fuels-types/src/traits/parameterize.rs b/packages/fuels-types/src/traits/parameterize.rs index 84facc11be..3690064695 100644 --- a/packages/fuels-types/src/traits/parameterize.rs +++ b/packages/fuels-types/src/traits/parameterize.rs @@ -6,6 +6,7 @@ use crate::{ core::{Bits256, RawSlice, SizedAsciiString}, enum_variants::EnumVariants, param_types::ParamType, + Bytes, }; /// `abigen` requires `Parameterized` to construct nested types. It is also used by `try_from_bytes` @@ -38,6 +39,12 @@ impl Parameterize for Vec { } } +impl Parameterize for Bytes { + fn param_type() -> ParamType { + ParamType::Bytes + } +} + impl Parameterize for Address { fn param_type() -> ParamType { ParamType::Struct { diff --git a/packages/fuels-types/src/traits/tokenizable.rs b/packages/fuels-types/src/traits/tokenizable.rs index 680c0ceaa7..2ea3bf0ab7 100644 --- a/packages/fuels-types/src/traits/tokenizable.rs +++ b/packages/fuels-types/src/traits/tokenizable.rs @@ -5,6 +5,7 @@ use crate::{ errors::{error, Error, Result}, param_types::ParamType, traits::Parameterize, + Bytes, }; pub trait Tokenizable { @@ -174,6 +175,25 @@ impl Tokenizable for RawSlice { } } +impl Tokenizable for Bytes { + fn from_token(token: Token) -> Result + where + Self: Sized, + { + match token { + Token::Bytes(contents) => Ok(Self(contents)), + _ => Err(error!( + InvalidData, + "Bytes::from_token expected a token of the variant Token::Bytes, got: {token}" + )), + } + } + + fn into_token(self) -> Token { + Token::Bytes(Vec::from(self)) + } +} + // Here we implement `Tokenizable` for a given tuple of a given length. // This is done this way because we can't use `impl Tokenizable for (T,)`. // So we implement `Tokenizable` for each tuple length, covering diff --git a/packages/fuels/Forc.toml b/packages/fuels/Forc.toml index 640265a703..6d4067bbf5 100644 --- a/packages/fuels/Forc.toml +++ b/packages/fuels/Forc.toml @@ -55,6 +55,7 @@ members = [ 'tests/scripts/script_with_arguments', 'tests/storage/contract_storage_test', 'tests/types/b512', + 'tests/types/bytes', 'tests/types/call_empty_return', 'tests/types/complex_types_contract', 'tests/types/contract_output_test', diff --git a/packages/fuels/tests/types.rs b/packages/fuels/tests/types.rs index ea3735aaba..f420caa5d2 100644 --- a/packages/fuels/tests/types.rs +++ b/packages/fuels/tests/types.rs @@ -1784,7 +1784,7 @@ async fn test_composite_types_in_vec_output() -> Result<()> { #[tokio::test] async fn test_nested_vector_methods_fail() -> Result<()> { - // This is just an E2E test of the method `ParamType::contains_nested_vectors`, hence it's + // This is just an E2E test of the method `ParamType::contains_nested_heap_types`, hence it's // not exhaustive, but its unit tests are. setup_contract_test!( Wallets("wallet"), @@ -1806,3 +1806,23 @@ async fn test_nested_vector_methods_fail() -> Result<()> { .expect_err("Should fail because nested vectors are not supported"); Ok(()) } + +#[tokio::test] +async fn test_bytes_output() -> Result<()> { + setup_contract_test!( + Wallets("wallet"), + Abigen( + name = "BytesOutputContract", + abi = "packages/fuels/tests/types/bytes" + ), + Deploy( + name = "contract_instance", + contract = "BytesOutputContract", + wallet = "wallet" + ), + ); + let contract_methods = contract_instance.methods(); + let response = contract_methods.return_bytes(10).call().await?; + assert_eq!(response.value, (0..10).collect::>()); + Ok(()) +} diff --git a/packages/fuels/tests/types/bytes/Forc.toml b/packages/fuels/tests/types/bytes/Forc.toml new file mode 100644 index 0000000000..57958b02dd --- /dev/null +++ b/packages/fuels/tests/types/bytes/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "bytes" + +[dependencies] diff --git a/packages/fuels/tests/types/bytes/src/main.sw b/packages/fuels/tests/types/bytes/src/main.sw new file mode 100644 index 0000000000..6bb8f95122 --- /dev/null +++ b/packages/fuels/tests/types/bytes/src/main.sw @@ -0,0 +1,19 @@ +contract; + +use std::bytes::Bytes; + +abi MyContract { + fn return_bytes(len: u8) -> Bytes; +} + +impl MyContract for Contract { + fn return_bytes(len: u8) -> Bytes { + let mut bytes = Bytes::new(); + let mut i: u8 = 0; + while i < len { + bytes.push(i); + i += 1u8; + } + bytes + } +}