Skip to content

Commit

Permalink
feat!: add support for string slices (#1044)
Browse files Browse the repository at this point in the history
Add support for `str` in prevision for
FuelLabs/sway#4778


### Checklist
- [x] I have linked to any relevant issues.
- [x] I have updated the documentation.
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added necessary labels.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

Co-authored-by: hal3e <git@hal3e.io>
  • Loading branch information
IGI-111 and hal3e committed Jul 21, 2023
1 parent 66e150e commit e5e3186
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 66 deletions.
15 changes: 15 additions & 0 deletions packages/fuels-code-gen/src/program_bindings/resolved_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl TypeResolver {
Self::to_generic,
Self::to_array,
Self::to_sized_ascii_string,
Self::to_ascii_string,
Self::to_tuple,
Self::to_bytes,
Self::to_raw_slice,
Expand Down Expand Up @@ -165,6 +166,20 @@ impl TypeResolver {
}))
}

fn to_ascii_string(
&self,
type_application: &FullTypeApplication,
) -> Result<Option<ResolvedType>> {
if type_application.type_decl.type_field == "str" {
Ok(Some(ResolvedType {
type_name: quote! { ::fuels::types::AsciiString },
generic_params: vec![],
}))
} else {
Ok(None)
}
}

fn to_tuple(&self, type_application: &FullTypeApplication) -> Result<Option<ResolvedType>> {
let type_decl = &type_application.type_decl;
if !has_tuple_format(&type_decl.type_field) {
Expand Down
79 changes: 56 additions & 23 deletions packages/fuels-core/src/codec/abi_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ impl ABIDecoder {
ParamType::Bool => Self::decode_bool(bytes),
ParamType::B256 => Self::decode_b256(bytes),
ParamType::RawSlice => Self::decode_raw_slice(bytes),
ParamType::String(length) => Self::decode_string(bytes, *length),
ParamType::StringSlice => Self::decode_string_slice(bytes),
ParamType::String(len) => Self::decode_string_array(bytes, *len),
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),
Expand Down Expand Up @@ -163,17 +164,24 @@ impl ABIDecoder {
})
}

fn decode_string(bytes: &[u8], length: usize) -> Result<DecodeResult> {
fn decode_string_slice(bytes: &[u8]) -> Result<DecodeResult> {
let decoded = str::from_utf8(bytes)?;

Ok(DecodeResult {
token: Token::StringSlice(StringToken::new(decoded.into(), None)),
bytes_read: decoded.len(),
})
}

fn decode_string_array(bytes: &[u8], length: usize) -> Result<DecodeResult> {
let encoded_len = padded_len_usize(length);
let encoded_str = peek(bytes, encoded_len)?;

let decoded = str::from_utf8(&encoded_str[..length])?;

let result = DecodeResult {
token: Token::String(StringToken::new(decoded.into(), length)),
token: Token::StringArray(StringToken::new(decoded.into(), Some(length))),
bytes_read: encoded_len,
};

Ok(result)
}

Expand Down Expand Up @@ -437,25 +445,46 @@ mod tests {
}

#[test]
fn decode_string() -> Result<()> {
fn decode_string_array() -> Result<()> {
let types = vec![ParamType::String(23), ParamType::String(5)];
let data = [
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c,
0x20, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x00, 0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x0, 0x0, 0x0,
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // This is
0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // a full s
0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x00, // entence
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00, // Hello
];

let decoded = ABIDecoder::decode(&types, &data)?;

let expected = vec![
Token::String(StringToken::new("This is a full sentence".into(), 23)),
Token::String(StringToken::new("Hello".into(), 5)),
Token::StringArray(StringToken::new("This is a full sentence".into(), Some(23))),
Token::StringArray(StringToken::new("Hello".into(), Some(5))),
];

assert_eq!(decoded, expected);
Ok(())
}

#[test]
fn decode_string_slice() -> Result<()> {
let types = vec![ParamType::StringSlice];
let data = [
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // This is
0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // a full s
0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // entence
];

let decoded = ABIDecoder::decode(&types, &data)?;

let expected = vec![Token::StringSlice(StringToken::new(
"This is a full sentence".into(),
None,
))];

assert_eq!(decoded, expected);
Ok(())
}

#[test]
fn decode_array() -> Result<()> {
// Create a parameter type for u8[2].
Expand Down Expand Up @@ -563,7 +592,7 @@ mod tests {
// this padding is due to the biggest variant of MyEnum being 3 WORDs bigger than the chosen variant
let enum_padding_enc = vec![0x0; 3 * WORD_SIZE];
let struct_par2_enc = vec![0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD4, 0x31];
let bytes: Vec<u8> = vec![
let data: Vec<u8> = vec![
enum_discriminant_enc,
enum_padding_enc,
enum_data_enc,
Expand All @@ -573,7 +602,7 @@ mod tests {
.flatten()
.collect();

let decoded = ABIDecoder::decode_single(&struct_type, &bytes)?;
let decoded = ABIDecoder::decode_single(&struct_type, &data)?;

let expected = Token::Struct(vec![
Token::Enum(Box::new((1, Token::U32(12345), inner_enum_types))),
Expand Down Expand Up @@ -641,7 +670,7 @@ mod tests {
// b: u8[2],
// }

// fn: long_function(Foo,u8[2],b256,str[23])
// fn: long_function(Foo,u8[2],b256,str[3],str)

// Parameters
let fields = vec![
Expand All @@ -661,11 +690,12 @@ mod tests {

let u8_arr = ParamType::Array(Box::new(ParamType::U8), 2);
let b256 = ParamType::B256;
let s = ParamType::String(23);
let s = ParamType::String(3);
let ss = ParamType::StringSlice;

let types = [nested_struct, u8_arr, b256, s];
let types = [nested_struct, u8_arr, b256, s, ss];

let data = [
let bytes = [
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, // foo.x == 10u16
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.y.a == true
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, // foo.b.0 == 1u8
Expand All @@ -676,12 +706,13 @@ mod tests {
0x20, 0x70, 0x13, 0xe6, 0x5b, 0x44, 0xe4, 0xcb, // b256
0x4e, 0x2c, 0x22, 0x98, 0xf4, 0xac, 0x45, 0x7b, // b256
0xa8, 0xf8, 0x27, 0x43, 0xf3, 0x1e, 0x93, 0xb, // b256
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // str[23]
0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // str[23]
0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x0, // str[23]
0x66, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, // str[3]
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, // str data
0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, // str data
0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, // str data
];

let decoded = ABIDecoder::decode(&types, &data)?;
let decoded = ABIDecoder::decode(&types, &bytes)?;

// Expected tokens
let foo = Token::Struct(vec![
Expand All @@ -700,9 +731,11 @@ mod tests {
0xf3, 0x1e, 0x93, 0xb,
]);

let s = Token::String(StringToken::new("This is a full sentence".into(), 23));
let ss = Token::StringSlice(StringToken::new("This is a full sentence".into(), None));

let s = Token::StringArray(StringToken::new("foo".into(), Some(3)));

let expected: Vec<Token> = vec![foo, u8_arr, b256, s];
let expected: Vec<Token> = vec![foo, u8_arr, b256, s, ss];

assert_eq!(decoded, expected);
Ok(())
Expand Down
71 changes: 61 additions & 10 deletions packages/fuels-core/src/codec/abi_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ impl ABIEncoder {
Token::B256(arg_bits256) => vec![Self::encode_b256(arg_bits256)],
Token::Array(arg_array) => Self::encode_array(arg_array)?,
Token::Vector(data) => Self::encode_vector(data)?,
Token::String(arg_string) => vec![Self::encode_string(arg_string)?],
Token::StringSlice(arg_string) => Self::encode_string_slice(arg_string)?,
Token::StringArray(arg_string) => vec![Self::encode_string_array(arg_string)?],
Token::Struct(arg_struct) => Self::encode_struct(arg_struct)?,
Token::Enum(arg_enum) => Self::encode_enum(arg_enum)?,
Token::Tuple(arg_tuple) => Self::encode_tuple(arg_tuple)?,
Expand Down Expand Up @@ -70,10 +71,6 @@ impl ABIEncoder {
Self::encode_tokens(arg_array)
}

fn encode_string(arg_string: &StringToken) -> Result<Data> {
Ok(Data::Inline(pad_string(arg_string.get_encodable_str()?)))
}

fn encode_b256(arg_bits256: &[u8; 32]) -> Data {
Data::Inline(arg_bits256.to_vec())
}
Expand Down Expand Up @@ -160,6 +157,18 @@ impl ABIEncoder {
Ok(vec![Data::Dynamic(encoded_data), len])
}

fn encode_string_slice(arg_string: &StringToken) -> Result<Vec<Data>> {
let encoded_data = Data::Inline(arg_string.get_encodable_str()?.as_bytes().to_vec());

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])
}

fn encode_string_array(arg_string: &StringToken) -> Result<Data> {
Ok(Data::Inline(pad_string(arg_string.get_encodable_str()?)))
}

fn encode_bytes(mut data: Vec<u8>) -> Result<Vec<Data>> {
let len = data.len();

Expand Down Expand Up @@ -475,7 +484,7 @@ mod tests {
}

#[test]
fn encode_function_with_string_type() -> Result<()> {
fn encode_function_with_string_array_type() -> Result<()> {
// let json_abi =
// r#"
// [
Expand All @@ -490,9 +499,9 @@ mod tests {

let fn_signature = "takes_string(str[23])";

let args: Vec<Token> = vec![Token::String(StringToken::new(
let args: Vec<Token> = vec![Token::StringArray(StringToken::new(
"This is a full sentence".into(),
23,
Some(23),
))];

let expected_encoded_abi = [
Expand All @@ -513,6 +522,48 @@ mod tests {
Ok(())
}

#[test]
fn encode_function_with_string_slice_type() -> Result<()> {
// let json_abi =
// r#"
// [
// {
// "type":"function",
// "inputs": [{"name":"arg","type":"str"}],
// "name":"takes_string",
// "outputs": []
// }
// ]
// "#;

let fn_signature = "takes_string(str)";

let args: Vec<Token> = vec![Token::StringSlice(StringToken::new(
"This is a full sentence".into(),
None,
))];

let expected_encoded_abi = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // str at data index 16
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, // str of lenght 23
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, //
0x61, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x73, //
0x65, 0x6e, 0x74, 0x65, 0x6e, 0x63, 0x65, //
];

let expected_function_selector = [0, 0, 0, 0, 239, 77, 222, 230];

let encoded_function_selector = first_four_bytes_of_sha256_hash(fn_signature);

let encoded = ABIEncoder::encode(&args)?.resolve(0);

println!("Encoded ABI for ({fn_signature}): {encoded:#0x?}");

assert_eq!(hex::encode(expected_encoded_abi), hex::encode(encoded));
assert_eq!(encoded_function_selector, expected_function_selector);
Ok(())
}

#[test]
fn encode_function_with_struct() -> Result<()> {
// let json_abi =
Expand Down Expand Up @@ -644,7 +695,7 @@ mod tests {
*/
let types = vec![ParamType::Bool, ParamType::String(10)];
let deeper_enum_variants = EnumVariants::new(types)?;
let deeper_enum_token = Token::String(StringToken::new("0123456789".into(), 10));
let deeper_enum_token = Token::StringArray(StringToken::new("0123456789".into(), Some(10)));

let str_enc = vec![
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', 0x0, 0x0, 0x0, 0x0, 0x0,
Expand Down Expand Up @@ -817,7 +868,7 @@ mod tests {

let b256 = Token::B256(hasher.finalize().into());

let s = Token::String(StringToken::new("This is a full sentence".into(), 23));
let s = Token::StringArray(StringToken::new("This is a full sentence".into(), Some(23)));

let args: Vec<Token> = vec![foo, u8_arr, b256, s];

Expand Down
2 changes: 2 additions & 0 deletions packages/fuels-core/src/codec/function_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn resolve_arg(arg: &ParamType) -> String {
ParamType::Bool => "bool".to_owned(),
ParamType::B256 => "b256".to_owned(),
ParamType::Unit => "()".to_owned(),
ParamType::StringSlice => "str".to_owned(),
ParamType::String(len) => {
format!("str[{len}]")
}
Expand Down Expand Up @@ -134,6 +135,7 @@ mod tests {
(ParamType::B256, "b256"),
(ParamType::Unit, "()"),
(ParamType::String(15), "str[15]"),
(ParamType::StringSlice, "str"),
] {
check_selector_for_type(param_type, expected_signature);
}
Expand Down
9 changes: 8 additions & 1 deletion packages/fuels-core/src/traits/parameterize.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use fuel_types::{Address, AssetId, ContractId};

use crate::types::{
enum_variants::EnumVariants, param_types::ParamType, Bits256, Bytes, RawSlice, SizedAsciiString,
enum_variants::EnumVariants, param_types::ParamType, AsciiString, Bits256, Bytes, RawSlice,
SizedAsciiString,
};

/// `abigen` requires `Parameterized` to construct nested types. It is also used by `try_from_bytes`
Expand Down Expand Up @@ -146,6 +147,12 @@ impl<const LEN: usize> Parameterize for SizedAsciiString<LEN> {
}
}

impl Parameterize for AsciiString {
fn param_type() -> ParamType {
ParamType::StringSlice
}
}

// Here we implement `Parameterize` for a given tuple of a given length.
// This is done this way because we can't use `impl<T> Parameterize for (T,)`.
// So we implement `Parameterize` for each tuple length, covering
Expand Down
Loading

0 comments on commit e5e3186

Please sign in to comment.