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: support Bytes type return from contract #868

Merged
merged 27 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8a36a36
refactor: remove the `Byte` type from the SDK
iqdecay Mar 13, 2023
7617c41
chore: re-add tests removed by mistake
iqdecay Mar 14, 2023
6b3e239
chore: fix typo
iqdecay Mar 14, 2023
fec595d
feat: support `RawUntypedPtr` and `Bytes` type in abigen
iqdecay Mar 1, 2023
901520e
feat: implement `Bytes` type decoding
iqdecay Mar 2, 2023
d97b4d6
test: add test for `Bytes` output
iqdecay Mar 2, 2023
0227cf9
style: appease clippy and all formatting gods
iqdecay Mar 2, 2023
2a3cd66
test: add test project to Forc.toml
iqdecay Mar 2, 2023
09a2472
test: implement tests for nested heap types
iqdecay Mar 6, 2023
2e92985
refactor: make `calculate_num_of_elements` a `ParamType` method
iqdecay Mar 6, 2023
1dd48d0
refactor: remove support for `RawUntypedPtr`
iqdecay Mar 10, 2023
5bdf5a8
refactor: implement code review suggestions
iqdecay Mar 10, 2023
becdd7f
refactor: change the memory size computation
iqdecay Mar 13, 2023
73d2abf
chore: rebase off the `Byte` removal PR
iqdecay Mar 13, 2023
f2c4708
chore: remove leftover `Byte` type
iqdecay Mar 14, 2023
4904e69
Merge branch 'master' into iqdecay/feat-bytes-return
iqdecay Mar 14, 2023
311828d
Update packages/fuels/tests/types/bytes/Forc.toml
iqdecay Mar 14, 2023
81e53fe
chore: re-add ignored test
iqdecay Mar 14, 2023
3e440dd
refactor: apply suggestions from code review
iqdecay Mar 15, 2023
409769c
test: use functional helper
iqdecay Mar 15, 2023
ae342a4
refactor: improve `calculate_num_of_elements` method
iqdecay Mar 15, 2023
9b48a77
refactor: merge match cases
iqdecay Mar 15, 2023
5432cce
Merge branch 'master' into iqdecay/feat-bytes-return
iqdecay Mar 15, 2023
08d92c9
chore: add ignored type
iqdecay Mar 15, 2023
4cea05a
Merge branch 'master' into iqdecay/feat-bytes-return
iqdecay Mar 17, 2023
de54d90
docs: change docs about vectors
iqdecay Mar 17, 2023
ce962a8
Merge branch 'master' into iqdecay/feat-bytes-return
iqdecay Mar 20, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn is_type_sdk_provided(name: &str) -> bool {
}

fn is_type_unused(name: &str) -> bool {
["raw untyped ptr", "RawVec"].contains(&name)
["raw untyped ptr", "RawVec", "RawBytes"].contains(&name)
}

// Doing string -> TokenStream -> string isn't pretty but gives us the opportunity to
Expand Down
18 changes: 18 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 @@ -83,6 +83,7 @@ pub(crate) fn resolve_type(
to_sized_ascii_string,
to_tuple,
to_raw_slice,
to_bytes,
to_custom_type,
]
.into_iter()
Expand All @@ -98,6 +99,23 @@ pub(crate) fn resolve_type(
.ok_or_else(|| error!("Could not resolve {type_field} to any known type"))
}

fn to_bytes(
type_field: &str,
_: impl Fn() -> Vec<ResolvedType>,
_: impl Fn() -> Vec<ResolvedType>,
_: bool,
) -> Option<ResolvedType> {
if type_field == "struct Bytes" {
let type_name = quote! {::fuels::types::Bytes};
Some(ResolvedType {
type_name,
generic_params: vec![],
})
} else {
None
}
}

fn to_generic(
type_field: &str,
_: impl Fn() -> Vec<ResolvedType>,
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 get_sdk_provided_types() -> Vec<TypePath> {
"::fuels::types::EvmAddress",
"::fuels::types::B512",
"::fuels::types::RawSlice",
"::fuels::types::Bytes",
"::std::vec::Vec",
"::core::result::Result",
"::core::option::Option",
Expand Down
27 changes: 12 additions & 15 deletions packages/fuels-core/src/abi_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ impl ABIDecoder {
}

fn decode_param(param_type: &ParamType, bytes: &[u8]) -> Result<DecodeResult> {
if param_type.contains_nested_vectors() {
if param_type.contains_nested_heap_types() {
return Err(error!(
InvalidData,
"Type {param_type:?} contains nested vectors, this is not supported."
"Type {param_type:?} contains nested heap types (`Vec` or `Bytes`), this is not supported."
));
}
match param_type {
Expand All @@ -72,11 +72,19 @@ impl ABIDecoder {
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::Bytes => Self::decode_bytes(bytes),
}
}

fn decode_bytes(bytes: &[u8]) -> Result<DecodeResult> {
Ok(DecodeResult {
token: Token::Bytes(bytes.to_vec()),
bytes_read: bytes.len(),
})
}
fn decode_vector(param_type: &ParamType, bytes: &[u8]) -> Result<DecodeResult> {
let num_of_elements = calculate_num_of_elements(param_type, bytes)?;
let num_of_elements =
ParamType::Vector(Box::from(param_type.clone())).calculate_num_of_elements(bytes)?;
iqdecay marked this conversation as resolved.
Show resolved Hide resolved
let (tokens, bytes_read) = Self::decode_multiple(vec![param_type; num_of_elements], bytes)?;

Ok(DecodeResult {
Expand Down Expand Up @@ -131,7 +139,7 @@ impl ABIDecoder {
}

fn decode_raw_slice(bytes: &[u8]) -> Result<DecodeResult> {
let num_of_elements = calculate_num_of_elements(&ParamType::U64, bytes)?;
let num_of_elements = ParamType::RawSlice.calculate_num_of_elements(bytes)?;
let (tokens, bytes_read) =
Self::decode_multiple(&vec![ParamType::U64; num_of_elements], bytes)?;
let elements = tokens
Expand Down Expand Up @@ -324,17 +332,6 @@ fn skip(slice: &[u8], num_bytes: usize) -> Result<&[u8]> {
}
}

fn calculate_num_of_elements(param_type: &ParamType, bytes: &[u8]) -> Result<usize> {
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;
Expand Down
9 changes: 8 additions & 1 deletion packages/fuels-core/src/abi_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,14 @@ impl ABIEncoder {
Token::Tuple(arg_tuple) => Self::encode_tuple(arg_tuple)?,
Token::Unit => vec![Self::encode_unit()],
Token::RawSlice(_) => {
unimplemented!("Encoding a raw_slice is currently not supported by the Fuel spec.")
unimplemented!(
"Encoding a `RawSlice` type is currently not supported by the Fuel spec."
)
iqdecay marked this conversation as resolved.
Show resolved Hide resolved
}
Token::Bytes(_) => {
unimplemented!(
"Encoding a `Bytes` type is currently not supported by the Fuel spec."
)
iqdecay marked this conversation as resolved.
Show resolved Hide resolved
}
};

Expand Down
3 changes: 2 additions & 1 deletion packages/fuels-core/src/function_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ fn resolve_arg(arg: &ParamType) -> String {
let inner = resolve_arg(el_type);
format!("s<{inner}>(s<{inner}>(rawptr,u64),u64)")
}
ParamType::RawSlice => unimplemented!("Raw slices are not supported as arguments"),
ParamType::RawSlice => unimplemented!("`RawSlice` type is not supported as arguments"),
ParamType::Bytes => unimplemented!("`Bytes` type is not supported as arguments"),
}
}

Expand Down
37 changes: 20 additions & 17 deletions packages/fuels-programs/src/call_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,28 @@ pub(crate) async fn build_tx_from_contract_calls(
Ok(tx)
}

/// Compute the length of the calling scripts for the two types of contract calls.
/// Compute the length of the calling scripts for the two types of contract calls: those that return
/// a heap type, and those that don't.
/// 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.
/// output type is a heap type.
fn compute_calls_instructions_len(calls: &[ContractCall]) -> usize {
let n_vectors_calls = calls.iter().filter(|c| c.output_param.is_vector()).count();
let n_heap_data_calls = calls
.iter()
.filter(|c| c.output_param.is_vm_heap_type())
.count();

let calls_instructions_len_no_vectors =
let calls_instructions_len_stack_data =
get_single_call_instructions(&CallOpcodeParamsOffset::default(), &ParamType::U64).len()
* (calls.len() - n_vectors_calls);
let calls_instructions_len_vectors = get_single_call_instructions(
* (calls.len() - n_heap_data_calls);
let calls_instructions_len_heap_data = 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
* n_heap_data_calls;

calls_instructions_len_stack_data + calls_instructions_len_heap_data
}

/// Compute how much of each asset is required based on all `CallParameters` of the `ContractCalls`
Expand Down Expand Up @@ -262,23 +267,21 @@ pub(crate) fn get_single_call_instructions(
]
.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;
if let Some(inner_type_byte_size) = output_param_type.heap_inner_element_size() {
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
// The RETL register contains the length of the `CALL` return (=24 because the Vec/Bytes
// struct takes 3 WORDs). We don't actually need it unless the Vec/Bytes 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.
// We know a Vec/Bytes struct has its third byte contain the length of the underlying
iqdecay marked this conversation as resolved.
Show resolved Hide resolved
// vector, so use a 2 offset to store the 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),
// The in-memory size of the type is (in-memory size of the inner type) * length
op::muli(0x16, 0x16, inner_type_byte_size as u16),
op::retd(0x15, 0x16),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels-programs/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ pub fn get_decoded_output(
None => null_contract_id,
};
let encoded_value = match output_param.get_return_location() {
ReturnLocation::ReturnData if output_param.is_vector() => {
ReturnLocation::ReturnData if output_param.is_vm_heap_type() => {
// 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
Expand Down
1 change: 1 addition & 0 deletions packages/fuels-test-helpers/src/signers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ mod tests {
Ok(())
}

#[ignore]
iqdecay marked this conversation as resolved.
Show resolved Hide resolved
#[tokio::test]
async fn test_wallet_config_multiple_assets(
) -> std::result::Result<(), Box<dyn std::error::Error>> {
Expand Down
6 changes: 5 additions & 1 deletion packages/fuels-types/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ use crate::{
};

mod bits;
mod bytes;
mod native;
mod raw_slice;
mod sized_ascii_string;

pub use crate::core::{bits::*, native::*, raw_slice::RawSlice, sized_ascii_string::*};
pub use crate::core::{
bits::*, bytes::Bytes, native::*, raw_slice::RawSlice, sized_ascii_string::*,
};

pub type ByteArray = [u8; 8];
pub type Selector = ByteArray;
Expand Down Expand Up @@ -85,6 +88,7 @@ pub enum Token {
Enum(Box<EnumSelector>),
Tuple(Vec<Token>),
RawSlice(Vec<u64>),
Bytes(Vec<u8>),
}

impl fmt::Display for Token {
Expand Down
20 changes: 20 additions & 0 deletions packages/fuels-types/src/core/bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct Bytes(pub Vec<u8>);

impl From<Bytes> for Vec<u8> {
fn from(raw_slice: Bytes) -> Vec<u8> {
raw_slice.0
}
}

impl PartialEq<Vec<u8>> for Bytes {
fn eq(&self, other: &Vec<u8>) -> bool {
self.0 == *other
}
}

impl PartialEq<Bytes> for Vec<u8> {
fn eq(&self, other: &Bytes) -> bool {
*self == other.0
}
}
Loading