diff --git a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs index 8fc9ad61ee..052d9e9990 100644 --- a/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs +++ b/packages/fuels-code-gen/src/program_bindings/abigen/bindings/function_generator.rs @@ -4,10 +4,10 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use crate::{ - error::{error, Result}, + error::Result, program_bindings::{ abi_types::{FullABIFunction, FullTypeApplication, FullTypeDeclaration}, - resolved_type::{resolve_type, ResolvedType}, + resolved_type::resolve_type, utils::{param_type_calls, Component}, }, utils::safe_ident, @@ -26,8 +26,9 @@ impl FunctionGenerator { pub fn new(fun: &FullABIFunction, shared_types: &HashSet) -> Result { let args = function_arguments(fun.inputs(), shared_types)?; - let output_type = resolve_fn_output_type(fun, shared_types)?; - + // We are not checking that the ABI contains non-SDK supported types so that the user can + // still interact with an ABI even if some methods will fail at runtime. + let output_type = resolve_type(fun.output(), shared_types)?; Ok(Self { name: fun.name().to_string(), args, @@ -83,21 +84,6 @@ fn function_arguments( .collect::>() } -fn resolve_fn_output_type( - function: &FullABIFunction, - shared_types: &HashSet, -) -> Result { - let output_type = resolve_type(function.output(), shared_types)?; - if output_type.uses_vectors() { - Err(error!( - "function '{}' contains a vector in its return type. This currently isn't supported.", - function.name() - )) - } else { - Ok(output_type) - } -} - impl From<&FunctionGenerator> for TokenStream { fn from(fun: &FunctionGenerator) -> Self { let name = safe_ident(&fun.name); 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 2d415e6c14..6fc85bfe33 100644 --- a/packages/fuels-code-gen/src/program_bindings/resolved_type.rs +++ b/packages/fuels-code-gen/src/program_bindings/resolved_type.rs @@ -7,10 +7,9 @@ use fuel_abi_types::utils::{ extract_array_len, extract_custom_type_name, extract_generic_name, extract_str_len, has_tuple_format, }; -use lazy_static::lazy_static; + use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use regex::Regex; use crate::{ error::{error, Result}, @@ -33,16 +32,6 @@ impl ResolvedType { pub fn is_unit(&self) -> bool { self.type_name.to_string() == "()" } - // Used to prevent returning vectors until we get - // the compiler support for it. - #[must_use] - pub fn uses_vectors(&self) -> bool { - lazy_static! { - static ref RE: Regex = Regex::new(r"\bVec\b").unwrap(); - } - RE.is_match(&self.type_name.to_string()) - || self.generic_params.iter().any(ResolvedType::uses_vectors) - } } impl ToTokens for ResolvedType { diff --git a/packages/fuels-core/src/abi_decoder.rs b/packages/fuels-core/src/abi_decoder.rs index 0841a862ba..2b4176c38b 100644 --- a/packages/fuels-core/src/abi_decoder.rs +++ b/packages/fuels-core/src/abi_decoder.rs @@ -51,6 +51,12 @@ impl ABIDecoder { } fn decode_param(param_type: &ParamType, bytes: &[u8]) -> Result { + if param_type.contains_nested_vectors() { + return Err(error!( + InvalidData, + "Type {param_type:?} contains nested vectors, this is not supported." + )); + } match param_type { ParamType::Unit => Self::decode_unit(bytes), ParamType::U8 => Self::decode_u8(bytes), @@ -60,18 +66,24 @@ impl ABIDecoder { ParamType::Bool => Self::decode_bool(bytes), ParamType::Byte => Self::decode_byte(bytes), ParamType::B256 => Self::decode_b256(bytes), + ParamType::RawSlice => Self::decode_raw_slice(bytes), ParamType::String(length) => Self::decode_string(bytes, *length), ParamType::Array(ref t, length) => Self::decode_array(t, bytes, *length), ParamType::Struct { fields, .. } => Self::decode_struct(fields, bytes), 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::RawSlice => Self::decode_raw_slice(bytes), } } - fn decode_vector(_param_type: &ParamType, _bytes: &[u8]) -> Result { - unimplemented!("Cannot decode Vectors until we get support from the compiler.") + fn decode_vector(param_type: &ParamType, bytes: &[u8]) -> Result { + let num_of_elements = calculate_num_of_elements(param_type, bytes)?; + let (tokens, bytes_read) = Self::decode_multiple(vec![param_type; num_of_elements], bytes)?; + + Ok(DecodeResult { + token: Token::Vector(tokens), + bytes_read, + }) } fn decode_tuple(param_types: &[ParamType], bytes: &[u8]) -> Result { @@ -93,7 +105,10 @@ impl ABIDecoder { }) } - fn decode_multiple(param_types: &[ParamType], bytes: &[u8]) -> Result<(Vec, usize)> { + fn decode_multiple<'a>( + param_types: impl IntoIterator, + bytes: &[u8], + ) -> Result<(Vec, usize)> { let mut results = vec![]; let mut bytes_read = 0; @@ -117,17 +132,9 @@ impl ABIDecoder { } fn decode_raw_slice(bytes: &[u8]) -> Result { - // A raw slice is actually an array of u64. - let u64_size = std::mem::size_of::(); - if bytes.len() % u64_size != 0 { - return Err(error!( - InvalidData, - "The bytes provided do not correspond to a raw slice with u64 numbers, got: {:?}", - bytes - )); - } - let u64_length = bytes.len() / u64_size; - let (tokens, bytes_read) = Self::decode_multiple(&vec![ParamType::U64; u64_length], bytes)?; + let num_of_elements = calculate_num_of_elements(&ParamType::U64, bytes)?; + let (tokens, bytes_read) = + Self::decode_multiple(&vec![ParamType::U64; num_of_elements], bytes)?; let elements = tokens .into_iter() .map(u64::from_token) @@ -325,6 +332,17 @@ 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-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index faa049b972..8f614f2920 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -1,3 +1,4 @@ +use itertools::{chain, Itertools}; use std::{collections::HashSet, iter, vec}; use fuel_tx::{ @@ -5,17 +6,18 @@ use fuel_tx::{ }; use fuel_types::Word; use fuel_vm::fuel_asm::{op, RegId}; + use fuels_core::offsets::call_script_data_offset; use fuels_signers::{provider::Provider, Signer, WalletUnlocked}; use fuels_types::{ bech32::Bech32Address, constants::{BASE_ASSET_ID, WORD_SIZE}, errors::{Error, Result}, + param_types::ParamType, parameters::TxParameters, resource::Resource, transaction::{ScriptTransaction, Transaction}, }; -use itertools::{chain, Itertools}; use crate::contract::ContractCall; @@ -39,11 +41,7 @@ pub async fn build_tx_from_contract_calls( ) -> Result { let consensus_parameters = wallet.get_provider()?.consensus_parameters().await?; - // Calculate instructions length for call instructions - // Use placeholder for call param offsets, we only care about the length - let calls_instructions_len = - get_single_call_instructions(&CallOpcodeParamsOffset::default()).len() * calls.len(); - + 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) = @@ -79,6 +77,25 @@ pub async fn build_tx_from_contract_calls( Ok(tx) } +/// Compute the length of the calling scripts for the two types of contract calls. +/// 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. +fn compute_calls_instructions_len(calls: &[ContractCall]) -> usize { + let n_vectors_calls = calls.iter().filter(|c| c.output_param.is_vector()).count(); + + let calls_instructions_len_no_vectors = + get_single_call_instructions(&CallOpcodeParamsOffset::default(), &ParamType::U64).len() + * (calls.len() - n_vectors_calls); + let calls_instructions_len_vectors = 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 +} + /// Compute how much of each asset is required based on all `CallParameters` of the `ContractCalls` pub(crate) fn calculate_required_asset_amounts(calls: &[ContractCall]) -> Vec<(AssetId, u64)> { let call_param_assets = calls @@ -124,16 +141,12 @@ pub(crate) fn get_instructions( calls: &[ContractCall], offsets: Vec, ) -> Vec { - let num_calls = calls.len(); - - let mut instructions = vec![]; - for (_, call_offsets) in (0..num_calls).zip(offsets.iter()) { - instructions.extend(get_single_call_instructions(call_offsets)); - } - - instructions.extend(op::ret(RegId::ONE).to_bytes()); - - instructions + calls + .iter() + .zip(&offsets) + .flat_map(|(call, offset)| get_single_call_instructions(offset, &call.output_param)) + .chain(op::ret(RegId::ONE).to_bytes().into_iter()) + .collect() } /// Returns script data, consisting of the following items in the given order: @@ -213,7 +226,10 @@ pub(crate) fn build_script_data_from_contract_calls( /// /// Note that these are soft rules as we're picking this addresses simply because they /// non-reserved register. -pub(crate) fn get_single_call_instructions(offsets: &CallOpcodeParamsOffset) -> Vec { +pub(crate) fn get_single_call_instructions( + offsets: &CallOpcodeParamsOffset, + output_param_type: &ParamType, +) -> Vec { let call_data_offset = offsets .call_data_offset .try_into() @@ -230,7 +246,8 @@ pub(crate) fn get_single_call_instructions(offsets: &CallOpcodeParamsOffset) -> .asset_id_offset .try_into() .expect("asset_id_offset out of range"); - let instructions = [ + + let mut instructions = [ op::movi(0x10, call_data_offset), op::movi(0x11, gas_forwarded_offset), op::lw(0x11, 0x11, 0), @@ -238,7 +255,29 @@ pub(crate) fn get_single_call_instructions(offsets: &CallOpcodeParamsOffset) -> op::lw(0x12, 0x12, 0), op::movi(0x13, asset_id_offset), op::call(0x10, 0x12, 0x13, 0x11), - ]; + ] + .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; + 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 + // 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. + 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), + op::retd(0x15, 0x16), + ]); + } #[allow(clippy::iter_cloned_collect)] instructions.into_iter().collect::>() diff --git a/packages/fuels-programs/src/contract.rs b/packages/fuels-programs/src/contract.rs index 4ce18eab9a..cdd2c085ef 100644 --- a/packages/fuels-programs/src/contract.rs +++ b/packages/fuels-programs/src/contract.rs @@ -31,6 +31,7 @@ use fuels_types::{ transaction::{CreateTransaction, ScriptTransaction, Transaction}, Selector, Token, }; +use itertools::Itertools; use crate::{ call_response::FuelCallResponse, @@ -477,13 +478,33 @@ pub fn get_decoded_output( contract_id: Option<&Bech32ContractId>, output_param: &ParamType, ) -> Result { + let null_contract_id = ContractId::new([0u8; 32]); // Multiple returns are handled as one `Tuple` (which has its own `ParamType`) let contract_id: ContractId = match contract_id { Some(contract_id) => contract_id.into(), // During a script execution, the script's contract id is the **null** contract id - None => ContractId::new([0u8; 32]), + None => null_contract_id, }; let encoded_value = match output_param.get_return_location() { + ReturnLocation::ReturnData if output_param.is_vector() => { + // 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 + // points to). + // We ensure to take the right "first" ReturnData receipt by checking for the + // contract_id. There are no receipts in between the two ReturnData receipts because of + // the way the scripts are built (the calling script adds a RETD just after the CALL + // opcode, see `get_single_call_instructions`). + let vector_data = receipts + .iter() + .tuple_windows() + .find_map(|(current_receipt, next_receipt)| { + extract_vec_data(current_receipt, next_receipt, contract_id) + }) + .cloned() + .expect("Could not extract vector data"); + Some(vector_data) + } ReturnLocation::ReturnData => receipts .iter() .find(|receipt| { @@ -516,6 +537,33 @@ pub fn get_decoded_output( Ok(decoded_value) } +fn extract_vec_data<'a>( + current_receipt: &Receipt, + next_receipt: &'a Receipt, + contract_id: ContractId, +) -> Option<&'a Vec> { + match (current_receipt, next_receipt) { + ( + Receipt::ReturnData { + id: first_id, + data: first_data, + .. + }, + Receipt::ReturnData { + id: second_id, + data: vec_data, + .. + }, + ) if *first_id == contract_id + && !first_data.is_empty() + && *second_id == ContractId::zeroed() => + { + Some(vec_data) + } + _ => None, + } +} + #[derive(Debug)] #[must_use = "contract calls do nothing unless you `call` them"] /// Helper that handles submitting a call to a client and formatting the response diff --git a/packages/fuels-signers/src/provider.rs b/packages/fuels-signers/src/provider.rs index f045433d67..5352a223a3 100644 --- a/packages/fuels-signers/src/provider.rs +++ b/packages/fuels-signers/src/provider.rs @@ -174,23 +174,6 @@ impl Provider { } /// Sends a transaction to the underlying Provider's client. - /// # Examples - /// - /// ## Sending a transaction - /// - /// ``` - /// use fuels::prelude::*; - /// async fn foo() -> std::result::Result<(), Box> { - /// // Setup local test node - /// let (provider, _) = setup_test_provider(vec![], vec![], None, None).await; - /// let tx = ScriptTransaction::default(); - /// - /// let receipts = provider.send_transaction(&tx).await?; - /// dbg!(receipts); - /// - /// Ok(()) - /// } - /// ``` pub async fn send_transaction(&self, tx: &T) -> Result> { let tolerance = 0.0; let TransactionCost { @@ -270,23 +253,6 @@ impl Provider { } /// Connects to an existing node at the given address. - /// # Examples - /// - /// ## Connect to a node - /// ``` - /// async fn connect_to_fuel_node() { - /// use fuels::prelude::*; - /// - /// // This is the address of a running node. - /// let server_address = "127.0.0.1:4000"; - /// - /// // Create the provider using the client. - /// let provider = Provider::connect(server_address).await.unwrap(); - /// - /// // Create the wallet. - /// let _wallet = WalletUnlocked::new_random(Some(provider)); - /// } - /// ``` pub async fn connect(url: impl AsRef) -> Result { let client = FuelClient::new(url).map_err(|err| error!(InfrastructureError, "{err}"))?; Ok(Provider::new(client)) diff --git a/packages/fuels-signers/src/wallet.rs b/packages/fuels-signers/src/wallet.rs index 9afe3cad8e..01d19dea27 100644 --- a/packages/fuels-signers/src/wallet.rs +++ b/packages/fuels-signers/src/wallet.rs @@ -52,41 +52,6 @@ pub struct Wallet { /// A `WalletUnlocked` is equivalent to a [`Wallet`] whose private key is known and stored /// alongside in-memory. Knowing the private key allows a `WalletUlocked` to sign operations, send /// transactions, and more. -/// -/// # Examples -/// -/// ## Signing and Verifying a message -/// -/// The wallet can be used to produce ECDSA [`Signature`] objects, which can be then verified. -/// -/// ``` -/// use fuel_crypto::Message; -/// use fuels::prelude::*; -/// -/// async fn foo() -> Result<()> { -/// // Setup local test node -/// let (provider, _) = setup_test_provider(vec![], vec![], None, None).await; -/// -/// // Create a new local wallet with the newly generated key -/// let wallet = WalletUnlocked::new_random(Some(provider)); -/// -/// let message = "my message"; -/// let signature = wallet.sign_message(message.as_bytes()).await?; -/// -/// // Lock the wallet when we're done, dropping the private key from memory. -/// let wallet = wallet.lock(); -/// -/// // Recover address that signed the message -/// let message = Message::new(message); -/// let recovered_address = signature.recover(&message).expect("Failed to recover address"); -/// -/// assert_eq!(wallet.address().hash(), recovered_address.hash()); -/// -/// // Verify signature -/// signature.verify(&recovered_address, &message).unwrap(); -/// Ok(()) -/// } -/// ``` #[derive(Clone, Debug)] pub struct WalletUnlocked { wallet: Wallet, diff --git a/packages/fuels-types/src/param_types.rs b/packages/fuels-types/src/param_types.rs index 5c30add482..6189c3ddba 100644 --- a/packages/fuels-types/src/param_types.rs +++ b/packages/fuels-types/src/param_types.rs @@ -7,6 +7,7 @@ use fuel_abi_types::{ has_tuple_format, }, }; +use itertools::chain; use strum_macros::EnumString; use crate::{ @@ -67,6 +68,44 @@ impl ParamType { } } + pub fn contains_nested_vectors(&self) -> bool { + match &self { + ParamType::Vector(param_type) => param_type.uses_vectors(), + _ => self.uses_vectors(), + } + } + + fn uses_vectors(&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::Enum { + generics, variants, .. + } => { + let variants_types = variants.param_types(); + Self::any_nested_vectors(chain!(generics, &variants_types)) + } + ParamType::Struct { + fields, generics, .. + } => { + let fields = fields.iter().map(|(_, param_type)| param_type); + Self::any_nested_vectors(chain!(fields, generics)) + } + _ => false, + } + } + + fn any_nested_vectors<'a>(param_types: impl IntoIterator) -> bool { + param_types + .into_iter() + .any(|param_type| param_type.uses_vectors()) + } + + pub fn is_vector(&self) -> bool { + matches!(self, Self::Vector(..)) + } + /// Calculates the number of `WORD`s the VM expects this parameter to be encoded in. pub fn compute_encoding_width(&self) -> usize { const fn count_words(bytes: usize) -> usize { @@ -1229,4 +1268,85 @@ mod tests { Ok(()) } + + #[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::Byte.contains_nested_vectors()); + assert!(!ParamType::B256.contains_nested_vectors()); + assert!(!ParamType::String(10).contains_nested_vectors()); + assert!(!ParamType::RawSlice.contains_nested_vectors()); + Ok(()) + } + + #[test] + fn contains_nested_vectors_complex_types() -> Result<()> { + let base_vector = ParamType::Vector(Box::from(ParamType::U8)); + let tuples_no_nested_vec = vec![ + ("Bim".to_string(), ParamType::U16), + ("Bam".to_string(), ParamType::Bool), + ]; + let tuples_with_nested_vec = vec![ + ("Zim".to_string(), ParamType::U64), + ("Zam".to_string(), ParamType::U32), + ("Boum".to_string(), base_vector.clone()), + ]; + let param_types_no_nested_vec = vec![ParamType::U64, ParamType::U32]; + let param_types_nested_vec = vec![ParamType::Unit, ParamType::Byte, base_vector.clone()]; + + assert!(!base_vector.contains_nested_vectors()); + assert!(ParamType::Vector(Box::from(base_vector.clone())).contains_nested_vectors()); + + assert!(!ParamType::Array(Box::from(ParamType::U8), 10).contains_nested_vectors()); + assert!(ParamType::Array(Box::from(base_vector), 10).contains_nested_vectors()); + + assert!(!ParamType::Tuple(param_types_no_nested_vec.clone()).contains_nested_vectors()); + assert!(ParamType::Tuple(param_types_nested_vec.clone()).contains_nested_vectors()); + + assert!(!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 { + name: "StructName".to_string(), + generics: param_types_nested_vec.clone(), + fields: tuples_no_nested_vec.clone() + } + .contains_nested_vectors()); + assert!(ParamType::Struct { + name: "StructName".to_string(), + generics: param_types_no_nested_vec.clone(), + fields: tuples_with_nested_vec.clone() + } + .contains_nested_vectors()); + + assert!(!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 { + name: "EnumName".to_string(), + variants: EnumVariants::new(tuples_with_nested_vec)?, + generics: param_types_no_nested_vec + } + .contains_nested_vectors()); + assert!(ParamType::Enum { + name: "EnumName".to_string(), + variants: EnumVariants::new(tuples_no_nested_vec)?, + generics: param_types_nested_vec + } + .contains_nested_vectors()); + + Ok(()) + } } diff --git a/packages/fuels/Forc.toml b/packages/fuels/Forc.toml index ca70bb5abb..2e8d88eca6 100644 --- a/packages/fuels/Forc.toml +++ b/packages/fuels/Forc.toml @@ -72,5 +72,6 @@ members = [ 'tests/types/tuples', 'tests/types/two_structs', 'tests/types/type_inside_enum', + 'tests/types/vector_output', 'tests/types/vectors', ] diff --git a/packages/fuels/tests/types.rs b/packages/fuels/tests/types.rs index b267a80dc8..ee7def9578 100644 --- a/packages/fuels/tests/types.rs +++ b/packages/fuels/tests/types.rs @@ -4,6 +4,7 @@ use fuels::{ prelude::*, types::{Bits256, EvmAddress, Identity, B512}, }; +use fuels_types::SizedAsciiString; pub fn null_contract_id() -> Bech32ContractId { // a bech32 contract address that decodes to [0u8;32] @@ -1677,3 +1678,130 @@ async fn test_b512() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_base_type_in_vec_output() -> Result<()> { + setup_contract_test!( + Wallets("wallet"), + Abigen( + name = "VectorOutputContract", + abi = "packages/fuels/tests/types/vector_output" + ), + Deploy( + name = "contract_instance", + contract = "VectorOutputContract", + wallet = "wallet" + ), + ); + let contract_methods = contract_instance.methods(); + + let response = contract_methods.u8_in_vec(10).call().await?; + assert_eq!(response.value, (0..10).collect::>()); + + let response = contract_methods.u16_in_vec(11).call().await?; + assert_eq!(response.value, (0..11).collect::>()); + + let response = contract_methods.u32_in_vec(12).call().await?; + assert_eq!(response.value, (0..12).collect::>()); + + let response = contract_methods.u64_in_vec(13).call().await?; + assert_eq!(response.value, (0..13).collect::>()); + + let response = contract_methods.bool_in_vec().call().await?; + assert_eq!(response.value, [true, false, true, false].to_vec()); + + Ok(()) +} +#[tokio::test] +async fn test_composite_types_in_vec_output() -> Result<()> { + setup_contract_test!( + Wallets("wallet"), + Abigen( + name = "VectorOutputContract", + abi = "packages/fuels/tests/types/vector_output" + ), + Deploy( + name = "contract_instance", + contract = "VectorOutputContract", + wallet = "wallet" + ), + ); + let contract_methods = contract_instance.methods(); + + { + let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]; + let response = contract_methods.array_in_vec().call().await?.value; + assert_eq!(response, expected); + } + { + let expected: Vec = vec![ + Pasta::Tortelini(Bimbam { + bim: 1111, + bam: 2222_u32, + }), + Pasta::Rigatoni(1987), + Pasta::Spaghetti(true), + ]; + + let response = contract_methods.enum_in_vec().call().await?.value; + assert_eq!(response, expected); + } + + { + let expected: Vec = vec![ + Bimbam { + bim: 1111, + bam: 2222_u32, + }, + Bimbam { + bim: 3333, + bam: 4444_u32, + }, + Bimbam { + bim: 5555, + bam: 6666_u32, + }, + ]; + let response = contract_methods.struct_in_vec().call().await?.value; + assert_eq!(response, expected); + } + + { + let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)]; + let response = contract_methods.tuple_in_vec().call().await?.value; + assert_eq!(response, expected); + } + + { + let expected: Vec> = + vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?]; + let response = contract_methods.str_in_vec().call().await?.value; + assert_eq!(response, expected); + } + Ok(()) +} + +#[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 + // not exhaustive, but its unit tests are. + setup_contract_test!( + Wallets("wallet"), + Abigen( + name = "VectorOutputContract", + abi = "packages/fuels/tests/types/vector_output" + ), + Deploy( + name = "contract_instance", + contract = "VectorOutputContract", + wallet = "wallet" + ), + ); + let contract_methods = contract_instance.methods(); + contract_methods + .vec_inside_type() + .call() + .await + .expect_err("Should fail because nested vectors are not supported"); + Ok(()) +} diff --git a/packages/fuels/tests/types/vector_output/Forc.toml b/packages/fuels/tests/types/vector_output/Forc.toml new file mode 100644 index 0000000000..0d7f08dad1 --- /dev/null +++ b/packages/fuels/tests/types/vector_output/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "vector_output" + +[dependencies] diff --git a/packages/fuels/tests/types/vector_output/src/main.sw b/packages/fuels/tests/types/vector_output/src/main.sw new file mode 100644 index 0000000000..639f5ee7ab --- /dev/null +++ b/packages/fuels/tests/types/vector_output/src/main.sw @@ -0,0 +1,149 @@ +contract; + +struct Bimbam { + bim: u64, + bam: u32, +} + +enum Pasta { + Rigatoni: u64, + Spaghetti: bool, + Tortelini: Bimbam, +} + +struct ZimZam { + vec_component: Vec, +} + +abi VectorsOutputContract { + fn vec_inside_type() -> ZimZam; + fn array_in_vec() -> Vec<[u64; 4]>; + fn bool_in_vec() -> Vec; + fn enum_in_vec() -> Vec; + fn str_in_vec() -> Vec; + fn struct_in_vec() -> Vec; + fn tuple_in_vec() -> Vec<(u64, u32)>; + fn u16_in_vec(len: u16) -> Vec; + fn u32_in_vec(len: u32) -> Vec; + fn u64_in_vec(len: u64) -> Vec; + fn u64_in_vec(len: u64) -> Vec; + fn u8_in_vec(len: u8) -> Vec; +} + +impl VectorsOutputContract for Contract { + fn vec_inside_type() -> ZimZam { + let mut b = Vec::new(); + b.push(255); + b.push(255); + b.push(255); + b.push(255); + ZimZam { + vec_component: b, + } + } + + fn array_in_vec() -> Vec<[u64; 4]> { + let mut vec: Vec<[u64; 4]> = Vec::new(); + vec.push([1, 1, 1, 1]); + vec.push([2, 2, 2, 2]); + vec.push([3, 3, 3, 3]); + vec.push([4, 4, 4, 4]); + vec + } + + fn bool_in_vec() -> Vec { + let mut vec: Vec = Vec::new(); + vec.push(true); + vec.push(false); + vec.push(true); + vec.push(false); + vec + } + + fn enum_in_vec() -> Vec { + let mut vec: Vec = Vec::new(); + vec.push(Pasta::Tortelini(Bimbam { + bim: 1111, + bam: 2222_u32, + })); + vec.push(Pasta::Rigatoni(1987)); + vec.push(Pasta::Spaghetti(true)); + vec + } + + fn str_in_vec() -> Vec { + let mut vec: Vec = Vec::new(); + vec.push("hell"); + vec.push("ello"); + vec.push("lloh"); + vec + } + + fn struct_in_vec() -> Vec { + let mut vec: Vec = Vec::new(); + let a = Bimbam { + bim: 1111, + bam: 2222_u32, + }; + vec.push(a); + let b = Bimbam { + bim: 3333, + bam: 4444_u32, + }; + vec.push(b); + let c = Bimbam { + bim: 5555, + bam: 6666_u32, + }; + vec.push(c); + vec + } + + fn tuple_in_vec() -> Vec<(u64, u32)> { + let mut vec: Vec<(u64, u32)> = Vec::new(); + vec.push((1111, 2222_u32)); + vec.push((3333, 4444_u32)); + vec.push((5555, 6666_u32)); + vec + } + + fn u8_in_vec(len: u8) -> Vec { + let mut vec: Vec = Vec::new(); + let mut i: u8 = 0; + while i < len { + vec.push(i); + i += 1u8; + } + vec + } + + fn u16_in_vec(len: u16) -> Vec { + let mut vec: Vec = Vec::new(); + let mut i: u16 = 0; + while i < len { + vec.push(i); + i += 1_u16; + } + vec + } + + fn u32_in_vec(len: u32) -> Vec { + let mut vec: Vec = Vec::new(); + let mut i: u32 = 0; + while i < len { + vec.push(i); + i += 1_u32; + } + vec + } + + fn u64_in_vec(len: u64) -> Vec { + let mut vec: Vec = Vec::new(); + let mut i: u64 = 0; + while i < len { + vec.push(i); + i += 1_u64; + } + vec + } +}