Skip to content

Commit

Permalink
feat: return str from contracts (#1159)
Browse files Browse the repository at this point in the history
adds support for `str` returned from contract calls (either
directly, or inside `enums`)

Full support will be added once `sway` is ready. Issue to track:
#1158
  • Loading branch information
hal3e authored Oct 10, 2023
1 parent 2625f90 commit 7a94e0b
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 12 deletions.
10 changes: 6 additions & 4 deletions packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;

Expand Down
9 changes: 5 additions & 4 deletions packages/fuels-core/src/codec/abi_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,17 @@ impl ABIEncoder {
.collect::<Vec<_>>();

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<Vec<Data>> {
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])
}

Expand Down
3 changes: 2 additions & 1 deletion packages/fuels-core/src/types/param_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand All @@ -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::<u8>()),
ParamType::RawSlice if !top_level_type => Some(ParamType::U64.compute_encoding_width()),
ParamType::StringSlice if !top_level_type => Some(std::mem::size_of::<u8>()),
_ => None,
}
}
Expand Down
8 changes: 5 additions & 3 deletions packages/fuels-programs/src/call_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ fn extract_heap_data(param_type: &ParamType) -> Vec<fuel_asm::Instruction> {
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![
Expand Down Expand Up @@ -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,
};

Expand Down
1 change: 1 addition & 0 deletions packages/fuels/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ abi MyContract {
fn returns_bytes_result(return_ok: bool) -> Result<Bytes, TestError>;
fn returns_vec_result(return_ok: bool) -> Result<Vec<u64>, TestError>;
fn returns_string_result(return_ok: bool) -> Result<String, TestError>;
fn returns_str_result(return_ok: bool) -> Result<str, TestError>;
fn returns_bytes_option(return_some: bool) -> Option<Bytes>;
fn returns_vec_option(return_some: bool) -> Option<Vec<u64>>;
fn returns_string_option(return_some: bool) -> Option<String>;
fn returns_str_option(return_some: bool) -> Option<str>;
fn would_raise_a_memory_overflow() -> Result<Bytes, b256>;
fn returns_a_heap_type_too_deep() -> Result<Bimbam, u64>;
}
Expand Down Expand Up @@ -60,6 +62,14 @@ impl MyContract for Contract {
}
}

fn returns_str_result(return_ok: bool) -> Result<str, TestError> {
return if return_ok {
Result::Ok("Hello World")
} else {
Result::Err(TestError::Else(3333))
}
}

fn returns_bytes_option(return_some: bool) -> Option<Bytes> {
return if return_some {
let mut b = Bytes::new();
Expand Down Expand Up @@ -96,6 +106,14 @@ impl MyContract for Contract {
}
}

fn returns_str_option(return_some: bool) -> Option<str> {
return if return_some {
Option::Some("Hello World")
} else {
None
}
}

fn would_raise_a_memory_overflow() -> Result<Bytes, b256> {
Result::Err(0x1111111111111111111111111111111111111111111111111111111111111111)
}
Expand Down
5 changes: 5 additions & 0 deletions packages/fuels/tests/types/contracts/string_slice/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "string_slice"
11 changes: 11 additions & 0 deletions packages/fuels/tests/types/contracts/string_slice/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
contract;

abi RawSliceContract {
fn return_str() -> str;
}

impl RawSliceContract for Contract {
fn return_str() -> str {
"contract-return"
}
}
38 changes: 38 additions & 0 deletions packages/fuels/tests/types_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,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;
Expand Down Expand Up @@ -2058,6 +2083,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);
Expand All @@ -2076,6 +2108,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()
Expand Down

0 comments on commit 7a94e0b

Please sign in to comment.