Skip to content

Commit

Permalink
feat: return Enums containing heap types at depth 1 (#1106)
Browse files Browse the repository at this point in the history
This PR enables returning heap types surrounded in an `Enum` type. It
notably enables returning a `Result<Vec>` from a contract call.

Closes #940.

Main disavantages from this approach: 
- We are injecting more custom bytecode
- Enum can contain only one variant that uses a heap type inside. It
could be extended to having an arbitrary number of them, but they would
all have to have the same encoding width. This is a personal design
choice. 
Co-authored-by: segfault-magnet <[email protected]>
  • Loading branch information
iqdecay authored Oct 6, 2023
1 parent c64de6b commit f51e76f
Show file tree
Hide file tree
Showing 11 changed files with 656 additions and 186 deletions.
67 changes: 67 additions & 0 deletions packages/fuels-core/src/codec/abi_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,73 @@ mod tests {
Ok(())
}

#[test]
fn decoding_enum_with_more_than_one_heap_type_variant_fails() -> Result<()> {
let mut param_types = vec![
ParamType::U64,
ParamType::Bool,
ParamType::Vector(Box::from(ParamType::U64)),
];
// empty data
let data = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
let variants = EnumVariants::new(param_types.clone())?;
let enum_param_type = ParamType::Enum {
variants,
generics: vec![],
};
// it works if there is only one heap type
let _ = ABIDecoder::default().decode(&enum_param_type, &data)?;

param_types.append(&mut vec![ParamType::Bytes]);
let variants = EnumVariants::new(param_types)?;
let enum_param_type = ParamType::Enum {
variants,
generics: vec![],
};
// fails if there is more than one variant using heap type in the enum
let error = ABIDecoder::default()
.decode(&enum_param_type, &data)
.expect_err("Should fail");
let expected_error =
"Invalid type: Enums currently support only one heap-type variant. Found: 2"
.to_string();
assert_eq!(error.to_string(), expected_error);

Ok(())
}

#[test]
fn enums_w_too_deeply_nested_heap_types_not_allowed() {
let param_types = vec![
ParamType::U8,
ParamType::Struct {
fields: vec![ParamType::RawSlice],
generics: vec![],
},
];
let variants = EnumVariants::new(param_types).unwrap();
let enum_param_type = ParamType::Enum {
variants,
generics: vec![],
};

let err = ABIDecoder::default()
.decode(&enum_param_type, &[])
.expect_err("should have failed");

let Error::InvalidType(msg) = err else {
panic!("Unexpected err: {err}");
};

assert_eq!(
msg,
"Enums currently support only one level deep heap types."
);
}

#[test]
fn max_depth_surpassed() {
const MAX_DEPTH: usize = 2;
Expand Down
23 changes: 8 additions & 15 deletions packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl BoundedDecoder {
}

pub(crate) fn decode(&mut self, param_type: &ParamType, bytes: &[u8]) -> Result<Token> {
Self::is_type_decodable(param_type)?;
param_type.validate_is_decodable()?;
Ok(self.decode_param(param_type, bytes)?.token)
}

Expand All @@ -46,24 +46,13 @@ impl BoundedDecoder {
bytes: &[u8],
) -> Result<Vec<Token>> {
for param_type in param_types {
Self::is_type_decodable(param_type)?;
param_type.validate_is_decodable()?;
}
let (tokens, _) = self.decode_params(param_types, bytes)?;

Ok(tokens)
}

fn is_type_decodable(param_type: &ParamType) -> Result<()> {
if param_type.contains_nested_heap_types() {
Err(error!(
InvalidType,
"Type {param_type:?} contains nested heap types (`Vec` or `Bytes`), this is not supported."
))
} else {
Ok(())
}
}

fn run_w_depth_tracking(
&mut self,
decoder: impl FnOnce(&mut Self) -> Result<Decoded>,
Expand Down Expand Up @@ -312,8 +301,12 @@ impl BoundedDecoder {

let discriminant = peek_u32(bytes)? as u8;
let selected_variant = variants.param_type_of_variant(discriminant)?;

let words_to_skip = enum_width - selected_variant.compute_encoding_width();
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();
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
43 changes: 39 additions & 4 deletions packages/fuels-core/src/types/enum_variants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ pub struct EnumVariants {

impl EnumVariants {
pub fn new(param_types: Vec<ParamType>) -> Result<EnumVariants> {
if !param_types.is_empty() {
Ok(EnumVariants { param_types })
} else {
Err(error!(InvalidData, "Enum variants can not be empty!"))
if param_types.is_empty() {
return Err(error!(InvalidData, "Enum variants can not be empty!"));
}
Ok(EnumVariants { param_types })
}

pub fn param_types(&self) -> &[ParamType] {
Expand All @@ -34,6 +33,13 @@ impl EnumVariants {
})
}

pub fn heap_type_variant(&self) -> Option<(u8, &ParamType)> {
self.param_types()
.iter()
.enumerate()
.find_map(|(d, p)| p.is_extra_receipt_needed(false).then_some((d as u8, p)))
}

pub fn only_units_inside(&self) -> bool {
self.param_types
.iter()
Expand Down Expand Up @@ -64,3 +70,32 @@ impl EnumVariants {
(biggest_variant_width - variant_width) * WORD_SIZE
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_get_heap_type_variant_discriminant() -> Result<()> {
let param_types = vec![
ParamType::U64,
ParamType::Bool,
ParamType::Vector(Box::from(ParamType::U64)),
];
let variants = EnumVariants::new(param_types)?;
assert_eq!(variants.heap_type_variant().unwrap().0, 2);

let param_types = vec![
ParamType::Vector(Box::from(ParamType::U64)),
ParamType::U64,
ParamType::Bool,
];
let variants = EnumVariants::new(param_types)?;
assert_eq!(variants.heap_type_variant().unwrap().0, 0);

let param_types = vec![ParamType::U64, ParamType::Bool];
let variants = EnumVariants::new(param_types)?;
assert!(variants.heap_type_variant().is_none());
Ok(())
}
}
Loading

0 comments on commit f51e76f

Please sign in to comment.