From 697933cff04cc08a992e5cd6630438da51a588c6 Mon Sep 17 00:00:00 2001 From: hal3e Date: Mon, 9 Oct 2023 17:08:57 +0200 Subject: [PATCH] feat: return `str` from contracts --- .../src/codec/abi_decoder/bounded_decoder.rs | 10 +++-- packages/fuels-core/src/codec/abi_encoder.rs | 9 +++-- packages/fuels-core/src/types/param_types.rs | 3 +- packages/fuels-programs/src/call_utils.rs | 8 ++-- packages/fuels/Forc.toml | 1 + .../contracts/heap_type_in_enums/src/main.sw | 18 +++++++++ .../types/contracts/string_slice/Forc.toml | 5 +++ .../types/contracts/string_slice/src/main.sw | 11 ++++++ packages/fuels/tests/types_contracts.rs | 38 +++++++++++++++++++ 9 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 packages/fuels/tests/types/contracts/string_slice/Forc.toml create mode 100644 packages/fuels/tests/types/contracts/string_slice/src/main.sw 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 b7b434093e..893609555c 100644 --- a/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs +++ b/packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs @@ -303,10 +303,12 @@ impl BoundedDecoder { let selected_variant = variants.param_type_of_variant(discriminant)?; let skip_extra = variants .heap_type_variant() - .is_some_and(|(heap_discriminant, _)| heap_discriminant == discriminant) - .then_some(3); - let words_to_skip = - enum_width - selected_variant.compute_encoding_width() + skip_extra.unwrap_or_default(); + .and_then(|(heap_discriminant, heap_type)| { + (heap_discriminant == discriminant).then_some(heap_type.compute_encoding_width()) + }) + .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 result = self.decode_token_in_enum(enum_content_bytes, variants, selected_variant)?; diff --git a/packages/fuels-core/src/codec/abi_encoder.rs b/packages/fuels-core/src/codec/abi_encoder.rs index 0ed6957a72..ae317cf5f5 100644 --- a/packages/fuels-core/src/codec/abi_encoder.rs +++ b/packages/fuels-core/src/codec/abi_encoder.rs @@ -154,16 +154,17 @@ impl ABIEncoder { .collect::>(); let num_bytes = data.len() * WORD_SIZE; - let len = Self::encode_u64(num_bytes as u64); + Ok(vec![Data::Dynamic(encoded_data), len]) } fn encode_string_slice(arg_string: &StaticStringToken) -> Result> { - let encoded_data = Data::Inline(arg_string.get_encodable_str()?.as_bytes().to_vec()); + let encodable_str = arg_string.get_encodable_str()?; + + let encoded_data = Data::Inline(encodable_str.as_bytes().to_vec()); + let len = Self::encode_u64(encodable_str.len() as u64); - let num_bytes = arg_string.get_encodable_str()?.len(); - let len = Self::encode_u64(num_bytes as u64); Ok(vec![Data::Dynamic(vec![encoded_data]), len]) } diff --git a/packages/fuels-core/src/types/param_types.rs b/packages/fuels-core/src/types/param_types.rs index 7818e19449..c81decf5f3 100644 --- a/packages/fuels-core/src/types/param_types.rs +++ b/packages/fuels-core/src/types/param_types.rs @@ -151,7 +151,7 @@ impl ParamType { ParamType::Tuple(elements) => elements .iter() .any(|param_type| param_type.is_extra_receipt_needed(false)), - ParamType::RawSlice => !top_level_type, + ParamType::RawSlice | ParamType::StringSlice => !top_level_type, _ => false, } } @@ -165,6 +165,7 @@ impl ParamType { // `Bytes` type is byte-packed in the VM, so it's the size of an u8 ParamType::Bytes | ParamType::String => Some(std::mem::size_of::()), ParamType::RawSlice if !top_level_type => Some(ParamType::U64.compute_encoding_width()), + ParamType::StringSlice if !top_level_type => Some(std::mem::size_of::()), _ => None, } } diff --git a/packages/fuels-programs/src/call_utils.rs b/packages/fuels-programs/src/call_utils.rs index 49ce6be2da..2cdcacc342 100644 --- a/packages/fuels-programs/src/call_utils.rs +++ b/packages/fuels-programs/src/call_utils.rs @@ -361,7 +361,8 @@ fn extract_heap_data(param_type: &ParamType) -> Vec { return vec![]; }; - let ptr_offset = (param_type.compute_encoding_width() - 3) as u16; + let ptr_offset = + (param_type.compute_encoding_width() - heap_type.compute_encoding_width()) as u16; [ vec![ @@ -397,9 +398,10 @@ fn extract_data_receipt( }; let len_offset = match (top_level_type, param_type) { - // A nested RawSlice shows up as ptr,len + // Nested `RawSlice` or `str` show up as ptr, len (false, ParamType::RawSlice) => 1, - // Every other heap type (currently) shows up as ptr,cap,len + (false, ParamType::StringSlice) => 1, + // Every other heap type (currently) shows up as ptr, cap, len _ => 2, }; diff --git a/packages/fuels/Forc.toml b/packages/fuels/Forc.toml index f415f67974..a7b81926e8 100644 --- a/packages/fuels/Forc.toml +++ b/packages/fuels/Forc.toml @@ -67,6 +67,7 @@ members = [ 'tests/types/contracts/results', 'tests/types/contracts/std_lib_string', 'tests/types/contracts/str_in_array', + 'tests/types/contracts/string_slice', 'tests/types/contracts/tuples', 'tests/types/contracts/two_structs', 'tests/types/contracts/type_inside_enum', diff --git a/packages/fuels/tests/types/contracts/heap_type_in_enums/src/main.sw b/packages/fuels/tests/types/contracts/heap_type_in_enums/src/main.sw index 92f32c8d80..941d637f97 100644 --- a/packages/fuels/tests/types/contracts/heap_type_in_enums/src/main.sw +++ b/packages/fuels/tests/types/contracts/heap_type_in_enums/src/main.sw @@ -16,9 +16,11 @@ abi MyContract { fn returns_bytes_result(return_ok: bool) -> Result; fn returns_vec_result(return_ok: bool) -> Result, TestError>; fn returns_string_result(return_ok: bool) -> Result; + fn returns_str_result(return_ok: bool) -> Result; fn returns_bytes_option(return_some: bool) -> Option; fn returns_vec_option(return_some: bool) -> Option>; fn returns_string_option(return_some: bool) -> Option; + fn returns_str_option(return_some: bool) -> Option; fn would_raise_a_memory_overflow() -> Result; fn returns_a_heap_type_too_deep() -> Result; } @@ -60,6 +62,14 @@ impl MyContract for Contract { } } + fn returns_str_result(return_ok: bool) -> Result { + return if return_ok { + Result::Ok("Hello World") + } else { + Result::Err(TestError::Else(3333)) + } + } + fn returns_bytes_option(return_some: bool) -> Option { return if return_some { let mut b = Bytes::new(); @@ -96,6 +106,14 @@ impl MyContract for Contract { } } + fn returns_str_option(return_some: bool) -> Option { + return if return_some { + Option::Some("Hello World") + } else { + None + } + } + fn would_raise_a_memory_overflow() -> Result { Result::Err(0x1111111111111111111111111111111111111111111111111111111111111111) } diff --git a/packages/fuels/tests/types/contracts/string_slice/Forc.toml b/packages/fuels/tests/types/contracts/string_slice/Forc.toml new file mode 100644 index 0000000000..26957b6887 --- /dev/null +++ b/packages/fuels/tests/types/contracts/string_slice/Forc.toml @@ -0,0 +1,5 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "string_slice" diff --git a/packages/fuels/tests/types/contracts/string_slice/src/main.sw b/packages/fuels/tests/types/contracts/string_slice/src/main.sw new file mode 100644 index 0000000000..3271275c7e --- /dev/null +++ b/packages/fuels/tests/types/contracts/string_slice/src/main.sw @@ -0,0 +1,11 @@ +contract; + +abi RawSliceContract { + fn return_str() -> str; +} + +impl RawSliceContract for Contract { + fn return_str() -> str { + "contract-return" + } +} diff --git a/packages/fuels/tests/types_contracts.rs b/packages/fuels/tests/types_contracts.rs index a50ea9c913..be7563d83e 100644 --- a/packages/fuels/tests/types_contracts.rs +++ b/packages/fuels/tests/types_contracts.rs @@ -1966,6 +1966,31 @@ async fn test_contract_raw_slice() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_contract_returning_string_slice() -> Result<()> { + let wallet = launch_provider_and_get_wallet().await; + setup_program_test!( + Abigen(Contract( + name = "StringSliceContract", + project = "packages/fuels/tests/types/contracts/string_slice" + )), + Deploy( + name = "contract_instance", + contract = "StringSliceContract", + wallet = "wallet" + ), + ); + + let contract_methods = contract_instance.methods(); + + { + let response = contract_methods.return_str().call().await?; + assert_eq!(response.value, "contract-return"); + } + + Ok(()) +} + #[tokio::test] async fn test_contract_std_lib_string() -> Result<()> { let wallet = launch_provider_and_get_wallet().await; @@ -2033,6 +2058,13 @@ async fn test_heap_type_in_enums() -> Result<()> { let expected = Err(TestError::Else(3333)); assert_eq!(resp.value, expected); + let resp = contract_methods.returns_str_result(true).call().await?; + let expected = Ok("Hello World".try_into()?); + assert_eq!(resp.value, expected); + let resp = contract_methods.returns_string_result(false).call().await?; + let expected = Err(TestError::Else(3333)); + assert_eq!(resp.value, expected); + let resp = contract_methods.returns_bytes_option(true).call().await?; let expected = Some(Bytes(vec![1, 1, 1, 1])); assert_eq!(resp.value, expected); @@ -2051,6 +2083,12 @@ async fn test_heap_type_in_enums() -> Result<()> { let resp = contract_methods.returns_string_option(false).call().await?; assert!(resp.value.is_none()); + let resp = contract_methods.returns_str_option(true).call().await?; + let expected = Some("Hello World".try_into()?); + assert_eq!(resp.value, expected); + let resp = contract_methods.returns_string_option(false).call().await?; + assert!(resp.value.is_none()); + // If the LW(RET) instruction was not executed only conditionally, then the FuelVM would OOM. let _ = contract_methods .would_raise_a_memory_overflow()