Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add String support #1042

Merged
merged 20 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ env:
RUSTFLAGS: "-D warnings"
FUEL_CORE_VERSION: 0.19.1
RUST_VERSION: 1.70.0
FORC_VERSION: 0.40.1
FORC_VERSION: 0.42.1
FORC_PATCH_BRANCH: ""
FORC_PATCH_REVISION: ""

Expand Down
4 changes: 2 additions & 2 deletions examples/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ mod tests {
.await?;
// ANCHOR_END: contract_call_cost_estimation

assert_eq!(transaction_cost.gas_used, 498);
assert_eq!(transaction_cost.gas_used, 499);

Ok(())
}
Expand Down Expand Up @@ -628,7 +628,7 @@ mod tests {
.await?;
// ANCHOR_END: multi_call_cost_estimation

assert_eq!(transaction_cost.gas_used, 783);
assert_eq!(transaction_cost.gas_used, 786);

Ok(())
}
Expand Down
78 changes: 64 additions & 14 deletions packages/fuels-code-gen/src/program_bindings/resolved_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ impl TypeResolver {
Self::to_sized_ascii_string,
Self::to_ascii_string,
Self::to_tuple,
Self::to_bytes,
Self::to_raw_slice,
Self::to_custom_type,
];
Expand Down Expand Up @@ -113,18 +112,6 @@ impl TypeResolver {
}))
}

fn to_bytes(&self, type_application: &FullTypeApplication) -> Result<Option<ResolvedType>> {
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<Option<ResolvedType>> {
let type_decl = &type_application.type_decl;
let Some(len) = extract_array_len(&type_decl.type_field) else {
Expand Down Expand Up @@ -497,7 +484,70 @@ mod tests {
}

#[test]
fn test_resolve_string() -> Result<()> {
fn test_resolve_std_string() -> Result<()> {
test_resolve_first_type(
":: std :: string :: String",
&[
TypeDeclaration {
type_id: 0,
type_field: "struct String".to_string(),
components: Some(vec![TypeApplication {
name: "bytes".to_string(),
type_id: 1,
..Default::default()
}]),
..Default::default()
},
TypeDeclaration {
type_id: 1,
type_field: "struct std::bytes::Bytes".to_string(),
components: Some(vec![
TypeApplication {
name: "buf".to_string(),
type_id: 2,
..Default::default()
},
TypeApplication {
name: "len".to_string(),
type_id: 4,
..Default::default()
},
]),
..Default::default()
},
TypeDeclaration {
type_id: 2,
type_field: "struct std::bytes::RawBytes".to_string(),
components: Some(vec![
TypeApplication {
name: "ptr".to_string(),
type_id: 3,
..Default::default()
},
TypeApplication {
name: "cap".to_string(),
type_id: 4,
..Default::default()
},
]),
..Default::default()
},
TypeDeclaration {
type_id: 3,
type_field: "raw untyped ptr".to_string(),
..Default::default()
},
TypeDeclaration {
type_id: 4,
type_field: "u64".to_string(),
..Default::default()
},
],
)
}

#[test]
fn test_resolve_static_str() -> Result<()> {
test_resolve_primitive_type("str[3]", ":: fuels :: types :: SizedAsciiString < 3usize >")
}

Expand Down
1 change: 1 addition & 0 deletions packages/fuels-code-gen/src/program_bindings/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub(crate) fn sdk_provided_custom_types_lookup() -> HashMap<TypePath, TypePath>
("std::identity::Identity", "::fuels::types::Identity"),
("std::option::Option", "::core::option::Option"),
("std::result::Result", "::core::result::Result"),
("std::string::String", "::std::string::String"),
("std::u128::U128", "u128"),
("std::u256::U256", "::fuels::types::U256"),
("std::vec::Vec", "::std::vec::Vec"),
Expand Down
8 changes: 8 additions & 0 deletions packages/fuels-core/src/codec/abi_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ impl ABIDecoder {
ParamType::Tuple(types) => Self::decode_tuple(types, bytes),
ParamType::Vector(param_type) => Self::decode_vector(param_type, bytes),
ParamType::Bytes => Self::decode_bytes(bytes),
ParamType::StdString => Self::decode_std_string(bytes),
}
}

Expand All @@ -92,6 +93,13 @@ impl ABIDecoder {
})
}

fn decode_std_string(bytes: &[u8]) -> Result<DecodeResult> {
Ok(DecodeResult {
token: Token::StdString(str::from_utf8(bytes)?.to_string()),
bytes_read: bytes.len(),
})
}

fn decode_vector(param_type: &ParamType, bytes: &[u8]) -> Result<DecodeResult> {
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)?;
Expand Down
25 changes: 25 additions & 0 deletions packages/fuels-core/src/codec/abi_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ impl ABIEncoder {
Token::Unit => vec![Self::encode_unit()],
Token::RawSlice(data) => Self::encode_raw_slice(data)?,
Token::Bytes(data) => Self::encode_bytes(data.to_vec())?,
// `String` in Sway has the same memory layout as the bytes type
Token::StdString(string) => Self::encode_bytes(string.clone().into_bytes())?,
};

Ok(encoded_token)
Expand Down Expand Up @@ -1171,4 +1173,27 @@ mod tests {

Ok(())
}

#[test]
fn encoding_std_string() -> Result<()> {
// arrange
let string = String::from("This ");
let token = Token::StdString(string);
let offset = 40;

// act
let encoded_std_string = ABIEncoder::encode(&[token])?.resolve(offset);

// assert
let ptr = [0, 0, 0, 0, 0, 0, 0, 64];
let cap = [0, 0, 0, 0, 0, 0, 0, 8];
segfault-magnet marked this conversation as resolved.
Show resolved Hide resolved
let len = [0, 0, 0, 0, 0, 0, 0, 5];
let data = [0x54, 0x68, 0x69, 0x73, 0x20, 0, 0, 0];

let expected_encoded_std_string = [ptr, cap, len, data].concat();

assert_eq!(expected_encoded_std_string, encoded_std_string);

Ok(())
}
}
10 changes: 10 additions & 0 deletions packages/fuels-core/src/codec/function_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ fn resolve_arg(arg: &ParamType) -> String {
}
ParamType::RawSlice => "rawslice".to_string(),
ParamType::Bytes => "s(s(rawptr,u64),u64)".to_string(),
ParamType::StdString => "s(s(s(rawptr,u64),u64))".to_string(),
}
}

Expand Down Expand Up @@ -141,6 +142,15 @@ mod tests {
}
}

#[test]
fn handles_std_strings() {
let inputs = [ParamType::StdString];

let signature = resolve_fn_signature("some_fn", &inputs);

assert_eq!(signature, "some_fn(s(s(s(rawptr,u64),u64)))");
}

#[test]
fn handles_arrays() {
let inputs = [ParamType::Array(Box::new(ParamType::U8), 1)];
Expand Down
6 changes: 6 additions & 0 deletions packages/fuels-core/src/traits/parameterize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ impl Parameterize for Bytes {
}
}

impl Parameterize for String {
fn param_type() -> ParamType {
ParamType::StdString
}
}

impl Parameterize for Address {
fn param_type() -> ParamType {
ParamType::Struct {
Expand Down
30 changes: 30 additions & 0 deletions packages/fuels-core/src/traits/tokenizable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,25 @@ impl Tokenizable for Bytes {
}
}

impl Tokenizable for String {
fn from_token(token: Token) -> Result<Self>
where
Self: Sized,
{
match token {
Token::StdString(string) => Ok(string),
_ => Err(error!(
InvalidData,
"String::from_token expected a token of the variant Token::String, got: {token}"
)),
}
}

fn into_token(self) -> Token {
Token::StdString(self)
}
}

// Here we implement `Tokenizable` for a given tuple of a given length.
// This is done this way because we can't use `impl<T> Tokenizable for (T,)`.
// So we implement `Tokenizable` for each tuple length, covering
Expand Down Expand Up @@ -590,4 +609,15 @@ mod tests {

Ok(())
}

#[test]
fn test_into_token_std_string() -> Result<()> {
let expected = String::from("hello");
let token = Token::StdString(expected.clone());
let detokenized = String::from_token(token.into_token())?;

assert_eq!(detokenized, expected);

Ok(())
}
}
1 change: 1 addition & 0 deletions packages/fuels-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub enum Token {
Tuple(Vec<Token>),
RawSlice(Vec<u64>),
Bytes(Vec<u8>),
StdString(String),
}

impl fmt::Display for Token {
Expand Down
43 changes: 39 additions & 4 deletions packages/fuels-core/src/types/param_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub enum ParamType {
Tuple(Vec<ParamType>),
RawSlice,
Bytes,
StdString,
}

pub enum ReturnLocation {
Expand Down Expand Up @@ -83,13 +84,17 @@ impl ParamType {
match &self {
ParamType::Vector(param_type) => param_type.uses_heap_types(),
ParamType::Bytes => false,
// Here, we return false because even though the `StdString` type has an underlying
// `Bytes` type nested, it is an exception that will be generalized as part of
// https://github.com/FuelLabs/fuels-rs/discussions/944
ParamType::StdString => false,
_ => self.uses_heap_types(),
}
}

fn uses_heap_types(&self) -> bool {
match &self {
ParamType::Vector(..) | ParamType::Bytes => true,
ParamType::Vector(..) | ParamType::Bytes | ParamType::StdString => true,
ParamType::Array(param_type, ..) => param_type.uses_heap_types(),
ParamType::Tuple(param_types, ..) => Self::any_nested_heap_types(param_types),
ParamType::Enum {
Expand All @@ -112,7 +117,10 @@ impl ParamType {
}

pub fn is_vm_heap_type(&self) -> bool {
matches!(self, ParamType::Vector(..) | ParamType::Bytes)
matches!(
self,
ParamType::Vector(..) | ParamType::Bytes | ParamType::StdString
)
}

/// Compute the inner memory size of a containing heap type (`Bytes` or `Vec`s).
Expand All @@ -122,7 +130,7 @@ impl ParamType {
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::<u8>()),
ParamType::Bytes | ParamType::StdString => Some(std::mem::size_of::<u8>()),
_ => None,
}
}
Expand All @@ -146,7 +154,7 @@ impl ParamType {
| ParamType::U64
| ParamType::Bool => 1,
ParamType::U128 | ParamType::RawSlice | ParamType::StringSlice => 2,
ParamType::Vector(_) | ParamType::Bytes => 3,
ParamType::Vector(_) | ParamType::Bytes | ParamType::StdString => 3,
ParamType::U256 | ParamType::B256 => 4,
ParamType::Array(param, count) => param.compute_encoding_width() * count,
ParamType::String(len) => count_words(*len),
Expand Down Expand Up @@ -328,6 +336,7 @@ impl TryFrom<&Type> for ParamType {
try_tuple,
try_vector,
try_bytes,
try_std_string,
try_raw_slice,
try_enum,
try_u128,
Expand Down Expand Up @@ -405,6 +414,12 @@ fn try_bytes(the_type: &Type) -> Result<Option<ParamType>> {
.then_some(ParamType::Bytes))
}

fn try_std_string(the_type: &Type) -> Result<Option<ParamType>> {
Ok(["struct std::string::String", "struct String"]
.contains(&the_type.type_field.as_str())
.then_some(ParamType::StdString))
}

fn try_raw_slice(the_type: &Type) -> Result<Option<ParamType>> {
Ok((the_type.type_field == "raw untyped slice").then_some(ParamType::RawSlice))
}
Expand Down Expand Up @@ -1295,6 +1310,7 @@ mod tests {
assert!(!ParamType::String(10).contains_nested_heap_types());
assert!(!ParamType::RawSlice.contains_nested_heap_types());
assert!(!ParamType::Bytes.contains_nested_heap_types());
assert!(!ParamType::StdString.contains_nested_heap_types());
Ok(())
}

Expand Down Expand Up @@ -1452,6 +1468,25 @@ mod tests {
assert_eq!(param_type, ParamType::RawSlice);
}

#[test]
fn try_std_string_correctly_resolves_param_type() {
let the_type = given_type_with_path("std::string::String");

let param_type = try_std_string(&the_type).unwrap().unwrap();

assert_eq!(param_type, ParamType::StdString);
}

#[test]
fn try_std_string_is_type_path_backward_compatible() {
segfault-magnet marked this conversation as resolved.
Show resolved Hide resolved
// TODO: To be removed once https://github.com/FuelLabs/fuels-rs/issues/881 is unblocked.
let the_type = given_type_with_path("String");

let param_type = try_std_string(&the_type).unwrap().unwrap();

assert_eq!(param_type, ParamType::StdString);
}

fn given_type_with_path(path: &str) -> Type {
Type {
type_field: format!("struct {path}"),
Expand Down
Loading
Loading