diff --git a/packages/fuel-indexer-lib/src/constants.rs b/packages/fuel-indexer-lib/src/constants.rs index 6477946bb..c874f9501 100644 --- a/packages/fuel-indexer-lib/src/constants.rs +++ b/packages/fuel-indexer-lib/src/constants.rs @@ -72,7 +72,7 @@ lazy_static! { /// Sway ABI types we don't support and won't in the near future. pub static ref IGNORED_ABI_JSON_TYPES: HashSet<&'static str> = - HashSet::from(["()", "struct Vec"]); + HashSet::from(["()"]); /// Fuel VM receipt-related types. pub static ref FUEL_RECEIPT_TYPES: HashSet<&'static str> = HashSet::from([ @@ -209,14 +209,18 @@ lazy_static! { /// ABI types not allowed in the contract ABI. - pub static ref DISALLOWED_ABI_JSON_TYPES: HashSet<&'static str> = HashSet::from([]); + pub static ref UNSUPPORTED_ABI_JSON_TYPES: HashSet<&'static str> = HashSet::from(["Vec"]); /// Generic Sway ABI types. - pub static ref GENERIC_TYPES: HashSet<&'static str> = HashSet::from([ + pub static ref IGNORED_GENERIC_METADATA: HashSet<&'static str> = HashSet::from([ "generic T", "raw untyped ptr", "struct RawVec", - "struct Vec" + ]); + + pub static ref GENERIC_STRUCTS: HashSet<&'static str> = HashSet::from([ + "Vec", + "Option" ]); /// Set of Rust primitive types. diff --git a/packages/fuel-indexer-macros/src/helpers.rs b/packages/fuel-indexer-macros/src/helpers.rs index 57289a9ab..ad3e33670 100644 --- a/packages/fuel-indexer-macros/src/helpers.rs +++ b/packages/fuel-indexer-macros/src/helpers.rs @@ -1,16 +1,19 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; -use async_graphql_parser::types::{BaseType, FieldDefinition, Type}; +use async_graphql_parser::types::{BaseType, FieldDefinition, Type as AsyncGraphQLType}; use async_graphql_value::Name; -use fuel_abi_types::abi::program::{ProgramABI, TypeDeclaration}; +use fuel_abi_types::abi::program::{ + ABIFunction, LoggedType, ProgramABI, TypeDeclaration, +}; use fuel_indexer_lib::{ constants::*, graphql::{list_field_type_name, types::IdCol, ParsedGraphQLSchema}, }; +use fuel_indexer_types::{type_id, FUEL_TYPES_NAMESPACE}; use fuels_code_gen::utils::Source; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::Ident; +use syn::{GenericArgument, Ident, PathArguments, Type, TypePath}; /// Provides a TokenStream to be used for unwrapping `Option`s for external types. /// @@ -76,7 +79,6 @@ pub fn get_json_abi(abi_path: Option) -> Option { /// Whether a `TypeDeclaration` is tuple type pub fn is_tuple_type(typ: &TypeDeclaration) -> bool { let mut type_field_chars = typ.type_field.chars(); - type_field_chars.next().is_some_and(|c| c == '(') && type_field_chars.next().is_some_and(|c| c != ')') } @@ -84,100 +86,62 @@ pub fn is_tuple_type(typ: &TypeDeclaration) -> bool { /// Whether a `TypeDeclaration` is a unit type pub fn is_unit_type(typ: &TypeDeclaration) -> bool { let mut type_field_chars = typ.type_field.chars(); - type_field_chars.next().is_some_and(|c| c == '(') && type_field_chars.next().is_some_and(|c| c == ')') } -/// Whether this TypeDeclaration should be used in the codegen -pub fn is_ignored_type(typ: &TypeDeclaration) -> bool { - is_tuple_type(typ) -} - /// Whether the TypeDeclaration should be used to build struct fields and decoders pub fn is_non_decodable_type(typ: &TypeDeclaration) -> bool { - is_ignored_type(typ) + is_tuple_type(typ) || is_unit_type(typ) - || GENERIC_TYPES.contains(typ.type_field.as_str()) + || IGNORED_GENERIC_METADATA.contains(typ.type_field.as_str()) } /// Derive Ident for decoded type -pub fn decoded_ident(ty: &str) -> Ident { - format_ident! { "{}_decoded", ty.to_ascii_lowercase() } -} - -/// Return type field name for complex type -fn derive_type_field(ty: &TypeDeclaration) -> String { - ty.type_field - .split(' ') - .last() - .expect("Could not parse TypeDeclaration for Rust name.") - .to_string() -} - -/// Derive Ident for given TypeDeclaration -pub fn rust_type_ident(ty: &TypeDeclaration) -> Ident { - if ty.components.is_some() { - if is_tuple_type(ty) { - proc_macro_error::abort_call_site!( - "Cannot derive rust_type_ident of tuple type." - ); +/// +/// These idents are used as fields for the `Decoder` struct. +fn decoded_ident(typ: &TypeDeclaration) -> Ident { + let name = { + let name = derive_type_name(typ); + if typ.components.is_some() { + name + } else if name.starts_with("Option") { + typ.type_field.replace(['<', '>'], "_") + } else { + typ.type_field.replace(['[', ']'], "_") } + }; - let name = derive_type_field(ty); - decoded_ident(&name) + if is_generic_type(typ) { + let gt = GenericType::from(name.as_str()); + match gt { + GenericType::Vec => { + let name = name.replace(['<', '>'], "_").to_ascii_lowercase(); + format_ident! { "{}decoded", name } + } + GenericType::Option => { + let name = name.replace(['<', '>'], "_").to_ascii_lowercase(); + format_ident! { "{}decoded", name } + } + _ => proc_macro_error::abort_call_site!( + "Could not derive decoded ident for generic type: {:?}.", + name + ), + } } else { - let name = ty.type_field.replace(['[', ']'], "_"); - decoded_ident(&name) + format_ident! { "{}_decoded", name.to_ascii_lowercase() } } } -/// Derive Rust type tokens for a given TypeDeclaration -pub fn rust_type_token(ty: &TypeDeclaration) -> proc_macro2::TokenStream { - if ty.components.is_some() { - let ty_str = ty - .type_field - .split(' ') - .last() - .expect("Could not parse TypeDeclaration for Rust type.") - .to_string(); - - let ident = format_ident! { "{}", ty_str }; - quote! { #ident } - } else { - match ty.type_field.as_str() { - "()" => quote! {}, - "b256" => quote! { B256 }, - "BlockData" => quote! { BlockData }, - "bool" => quote! { bool }, - "Burn" => quote! { Burn }, - "Call" => quote! { Call }, - "generic T" => quote! {}, - "Identity" => quote! { Identity }, - "Log" => quote! { Log }, - "LogData" => quote! { LogData }, - "MessageOut" => quote! { MessageOut }, - "Mint" => quote! { Mint }, - "Panic" => quote! { Panic }, - "raw untyped ptr" => quote! {}, - "Return" => quote! { Return }, - "Revert" => quote! { Revert }, - "ScriptResult" => quote! { ScriptResult }, - "Transfer" => quote! { Transfer }, - "TransferOut" => quote! { TransferOut }, - "u16" => quote! { u16 }, - "u32" => quote! { u32 }, - "u64" => quote! { u64 }, - "u8" => quote! { u8 }, - o if o.starts_with("str[") => quote! { String }, - o => { - proc_macro_error::abort_call_site!( - "Unrecognized primitive type: {:?}.", - o - ) - } - } - } +/// Given a `TypeDeclaration`, return name of the base of its typed path. +/// +/// `Vec` returns `Vec`, `Option` returns `Option`, `u8` returns `u8`, etc. +pub fn derive_type_name(typ: &TypeDeclaration) -> String { + typ.type_field + .split(' ') + .last() + .expect("Type field name expected") + .to_string() } /// Whether or not the given token is a Fuel primitive @@ -185,9 +149,9 @@ pub fn rust_type_token(ty: &TypeDeclaration) -> proc_macro2::TokenStream { /// These differ from `RESERVED_TYPEDEF_NAMES` in that `FUEL_PRIMITIVES` are type names /// that are checked against the contract JSON ABI, while `RESERVED_TYPEDEF_NAMES` are /// checked against the GraphQL schema. -pub fn is_fuel_primitive(ty: &proc_macro2::TokenStream) -> bool { - let ident_str = ty.to_string(); - FUEL_PRIMITIVES.contains(ident_str.as_str()) +pub fn is_fuel_primitive(typ: &TypeDeclaration) -> bool { + let name = derive_type_name(typ); + FUEL_PRIMITIVES.contains(name.as_str()) } /// Whether or not the given token is a Rust primitive @@ -196,61 +160,131 @@ pub fn is_rust_primitive(ty: &proc_macro2::TokenStream) -> bool { RUST_PRIMITIVES.contains(ident_str.as_str()) } -/// Given a type ID, a type token, and a type Ident, return a decoder snippet -/// as a set of tokens +/// Whether or not the given tokens are a generic type +pub fn is_generic_type(typ: &TypeDeclaration) -> bool { + let gt = GenericType::from(typ); + matches!(gt, GenericType::Vec | GenericType::Option) +} + +/// Given a `TokenStream` representing this `TypeDeclaration`'s fully typed path, +/// return the associated `match` arm for decoding this type in the `Decoder`. pub fn decode_snippet( - ty_id: usize, - ty: &proc_macro2::TokenStream, - name: &Ident, + type_tokens: &proc_macro2::TokenStream, + typ: &TypeDeclaration, ) -> proc_macro2::TokenStream { - if is_fuel_primitive(ty) { + let name = typ.decoder_field_ident(); + let ty_id = typ.type_id; + + if is_fuel_primitive(typ) { quote! { #ty_id => { - let obj: #ty = bincode::deserialize(&data).expect("Bad bincode."); + let obj: #type_tokens = bincode::deserialize(&data).expect("Bad bincode."); self.#name.push(obj); } } - } else if is_rust_primitive(ty) { + } else if is_rust_primitive(type_tokens) { quote! { #ty_id => { Logger::warn("Skipping primitive decoder."); } } + } else if is_generic_type(typ) { + let gt = GenericType::from(typ); + match gt { + GenericType::Vec => { + // https://github.com/FuelLabs/fuel-indexer/issues/503 + quote! { + #ty_id => { + Logger::warn("Skipping unsupported vec decoder."); + } + } + } + GenericType::Option => { + let (inner, typ) = inner_typedef(typ); + let inner = format_ident! { "{}", inner }; + quote! { + #ty_id => { + let decoded = ABIDecoder::decode_single(&#typ::<#inner>::param_type(), &data).expect("Failed decoding."); + let obj = #typ::<#inner>::from_token(decoded).expect("Failed detokenizing."); + self.#name.push(obj); + } + } + } + _ => proc_macro_error::abort_call_site!( + "Decoder snippet is unsupported for generic type: {:?}.", + gt + ), + } } else { quote! { #ty_id => { - let decoded = ABIDecoder::decode_single(&#ty::param_type(), &data).expect("Failed decoding."); - let obj = #ty::from_token(decoded).expect("Failed detokenizing."); + let decoded = ABIDecoder::decode_single(&#type_tokens::param_type(), &data).expect("Failed decoding."); + let obj = #type_tokens::from_token(decoded).expect("Failed detokenizing."); self.#name.push(obj); } } } } -/// A wrapper trait for helper functions such as rust_type_ident, -/// rust_type_token, and rust_type_ident +/// A hacky wrapper trait for helper functions. pub trait Codegen { - fn decoded_ident(&self) -> Ident; - fn rust_type_token(&self) -> proc_macro2::TokenStream; - fn rust_type_ident(&self) -> Ident; - fn rust_type_token_string(&self) -> String; + /// Return the derived name for this `TypeDeclaration`. + fn name(&self) -> String; + + /// Return the `TokenStream` for this `TypeDeclaration`. + fn rust_tokens(&self) -> proc_macro2::TokenStream; + + /// Return the `Ident` for this `TypeDeclaration`'s decoder field. + fn decoder_field_ident(&self) -> Ident; } impl Codegen for TypeDeclaration { - fn decoded_ident(&self) -> Ident { - decoded_ident(&self.rust_type_token().to_string()) - } - - fn rust_type_token(&self) -> proc_macro2::TokenStream { - rust_type_token(self) + fn name(&self) -> String { + derive_type_name(self) } - fn rust_type_ident(&self) -> Ident { - rust_type_ident(self) + fn rust_tokens(&self) -> proc_macro2::TokenStream { + if self.components.is_some() { + let name = derive_type_name(self); + let ident = format_ident! { "{}", name }; + quote! { #ident } + } else { + match self.type_field.as_str() { + "()" => quote! {}, + "b256" => quote! { B256 }, + "BlockData" => quote! { BlockData }, + "bool" => quote! { bool }, + "Burn" => quote! { Burn }, + "Call" => quote! { Call }, + "generic T" => quote! {}, + "Identity" => quote! { Identity }, + "Log" => quote! { Log }, + "LogData" => quote! { LogData }, + "MessageOut" => quote! { MessageOut }, + "Mint" => quote! { Mint }, + "Panic" => quote! { Panic }, + "Return" => quote! { Return }, + "Revert" => quote! { Revert }, + "ScriptResult" => quote! { ScriptResult }, + "Transfer" => quote! { Transfer }, + "TransferOut" => quote! { TransferOut }, + "u16" => quote! { u16 }, + "u32" => quote! { u32 }, + "u64" => quote! { u64 }, + "u8" => quote! { u8 }, + o if o.starts_with("str[") => quote! { String }, + o => { + proc_macro_error::abort_call_site!( + "Unrecognized primitive type: {:?}.", + o + ) + } + } + } } - fn rust_type_token_string(&self) -> String { - self.rust_type_token().to_string() + fn decoder_field_ident(&self) -> Ident { + decoded_ident(self) } } @@ -429,15 +463,15 @@ pub fn process_typedef_field( let field_typ_name = &parsed.scalar_type_for(&field_def); if parsed.is_list_field_type(&list_field_type_name(&field_def)) { - field_def.ty.node = Type { - base: BaseType::List(Box::new(Type { + field_def.ty.node = AsyncGraphQLType { + base: BaseType::List(Box::new(AsyncGraphQLType { base: BaseType::Named(Name::new(field_typ_name)), nullable: inner_nullable, })), nullable, }; } else { - field_def.ty.node = Type { + field_def.ty.node = AsyncGraphQLType { base: BaseType::Named(Name::new(field_typ_name)), nullable, }; @@ -711,3 +745,360 @@ pub fn can_derive_id(field_set: &HashSet, field_name: &str) -> bool { field_set.contains(IdCol::to_lowercase_str()) && field_name != IdCol::to_lowercase_str() } + +/// Simply represents a value for a generic type. +#[derive(Debug)] +pub enum GenericType { + /// `Vec` + Vec, + + /// `Option` + Option, + #[allow(unused)] + Other, +} + +impl From<&TypeDeclaration> for GenericType { + fn from(t: &TypeDeclaration) -> Self { + Self::from(derive_type_name(t)) + } +} + +impl From for GenericType { + fn from(s: String) -> Self { + if s.starts_with("Vec") { + GenericType::Vec + } else if s.starts_with("Option") { + GenericType::Option + } else { + GenericType::Other + } + } +} + +impl From for &str { + fn from(t: GenericType) -> Self { + match t { + GenericType::Vec => "Vec", + GenericType::Option => "Option", + GenericType::Other => unimplemented!("Generic type not implemented."), + } + } +} + +impl From<&str> for GenericType { + fn from(s: &str) -> Self { + if s.starts_with("Vec") { + GenericType::Vec + } else if s.starts_with("Option") { + GenericType::Option + } else { + GenericType::Other + } + } +} + +impl From for TokenStream { + fn from(t: GenericType) -> Self { + match t { + GenericType::Vec => quote! { Vec }, + GenericType::Option => quote! { Option }, + GenericType::Other => unimplemented!("Generic type not implemented."), + } + } +} + +/// Same as `derive_generic_inner_typedefs` but specifically for log types. +/// +/// Where as `derive_generic_inner_typedefs` can return multiple inner types, this +/// only returns the single inner type associated with this log specific logged type +pub fn derive_log_generic_inner_typedefs<'a>( + typ: &'a LoggedType, + abi: &ProgramABI, + abi_types: &'a HashMap, +) -> &'a TypeDeclaration { + let result = + abi.logged_types + .iter() + .flatten() + .filter_map(|log| { + if log.log_id == typ.log_id && log.application.type_arguments.is_some() { + let args = log.application.type_arguments.as_ref().unwrap(); + let inner = args.first().expect("No type args found."); + return Some(abi_types.get(&inner.type_id).unwrap_or_else(|| { + panic!("Inner type not in ABI: {:?}", inner) + })); + } + None + }) + .collect::>(); + + result.first().expect("No inner type found.") +} + +/// Derive the inner ident names for collections types. +/// +/// Given a `GenericType`, and ABI JSON metadata, derive the inner `TypeDefinition`s associated with the given +/// generic `TypeDefinition`. +/// +/// So this function will parse all function inputs/outputs and log types, find all generics (e.g., `Vec`, `Option`), +/// and return the inner `TypeDefinition`s associated with those generics (e.g., `T` and `U`) +pub fn derive_generic_inner_typedefs<'a>( + typ: &'a TypeDeclaration, + funcs: &[ABIFunction], + log_types: &[LoggedType], + abi_types: &'a HashMap, +) -> Vec<&'a TypeDeclaration> { + let name = typ.type_field.split(' ').last().unwrap(); + let t = GenericType::from(name); + + // Per Ahmed from fuels-rs: + // + // "So if you wish to see all the various Ts used with SomeStruct (in this case Vec) + // you have no choice but to go through all functions and find inputs/outputs that reference + // SomeStruct and see what the typeArguments are. Those will replace the typeParameters inside + // the type declaration." + match t { + GenericType::Option | GenericType::Vec => { + let mut typs = funcs + .iter() + .flat_map(|func| { + func.inputs + .iter() + .filter_map(|i| { + if i.type_id == typ.type_id && i.type_arguments.is_some() { + let args = i.type_arguments.as_ref().unwrap(); + let inner = args.first().expect("No type args found."); + return Some( + abi_types.get(&inner.type_id).unwrap_or_else(|| { + panic!("Inner type not in ABI: {:?}", inner) + }), + ); + } + None + }) + .collect::>() + }) + .collect::>(); + + let mut output_typs = funcs + .iter() + .flat_map(|func| { + if func.output.type_id == typ.type_id + && func.output.type_arguments.is_some() + { + let args = func.output.type_arguments.as_ref().unwrap(); + let inner = args.first().expect("No type args found."); + return Some(abi_types.get(&inner.type_id).unwrap_or_else( + || panic!("Inner type not in ABI: {:?}", inner), + )); + } + None + }) + .collect::>(); + + // Parse these as well because we will need to add them to our + // mapping of type IDs and `TypeDeclaration`s so we can use them when + // we parse log types. + let mut log_types = log_types + .iter() + .filter_map(|log| { + if log.application.type_id == typ.type_id + && log.application.type_arguments.is_some() + { + let args = log.application.type_arguments.as_ref().unwrap(); + let inner = args.first().expect("No type args found."); + return Some(abi_types.get(&inner.type_id).unwrap_or_else( + || panic!("Inner type not in ABI: {:?}", inner), + )); + } + None + }) + .collect::>(); + + typs.append(&mut output_typs); + typs.append(&mut log_types); + typs + } + _ => proc_macro_error::abort_call_site!( + "Can't derive idents for unsupported generic type: {:?}", + t + ), + } +} + +/// Extract the full type ident from a given path. +pub fn typed_path_name(p: &TypePath) -> String { + let base = p + .path + .segments + .last() + .expect("Could not get last path segment."); + + let base_name = base.ident.to_string(); + + if GENERIC_STRUCTS.contains(base_name.as_str()) { + typed_path_string(p) + } else { + base_name + } +} + +/// Extract the fully typed path for this generic type. +/// +/// When given a generic's `TypedPath` (e.g., `Vec`), we need to extract the struct type (e.g., `Vec`) +/// and also inner type `T`. +/// +/// The following assumes a generic type definition format of: +/// `$struct_name $bracket(open) $inner_t_name $bracket(close)` (e.g., `Vec` or `Option`) +fn typed_path_string(p: &TypePath) -> String { + let mut result = String::new(); + + let base = p + .path + .segments + .last() + .expect("Could not get last path segment."); + + result.push_str(&base.ident.to_string()); + result.push('<'); + + match base.arguments { + PathArguments::AngleBracketed(ref inner) => { + let _ = inner + .args + .iter() + .map(|arg| match arg { + GenericArgument::Type(Type::Path(p)) => { + let segment = p + .path + .segments + .last() + .expect("Could not get last path segment."); + let name = segment.ident.to_string(); + result.push_str(&name); + result.push('>'); + } + _ => panic!("Unsupported generic argument."), + }) + .collect::>(); + } + _ => panic!("Unsupported generic argument."), + } + result +} + +/// Retrieve the inner `T` from the given generic `TypeDefinition`s type field. +/// +/// E.g., extrac `T` from `Vec` +pub fn inner_typedef(typ: &TypeDeclaration) -> (String, proc_macro2::TokenStream) { + let name = derive_type_name(typ); + let gt = GenericType::from(name.clone()); + match gt { + GenericType::Vec => (name[4..name.len() - 1].to_string(), gt.into()), + GenericType::Option => (name[7..name.len() - 1].to_string(), gt.into()), + _ => proc_macro_error::abort_call_site!("Unsupported generic type: {:?}", gt), + } +} + +/// Derive the output type ID for a given ABI function. +/// +/// For non-generic `TypeDeclaration`s, we can just return the `type_id` associated with +/// that function output. But for generic `TypeDeclaration`s, we need to derive the `type_id` +/// using the fully typed path of the generic type (e.g., `Vec`) in order to match +/// the type ID of the `TypeDefinition` we manually inserted into the `#[indexer]` `abi_types_tyid` +/// mapping. +pub fn function_output_type_id( + f: &ABIFunction, + abi_types: &HashMap, +) -> usize { + let outer_typ = abi_types.get(&f.output.type_id).unwrap_or_else(|| { + panic!( + "function_output_type_id: Type with TypeID({}) is missing from the JSON ABI", + f.output.type_id + ) + }); + if is_generic_type(outer_typ) { + let name = derive_type_name(outer_typ); + let gt = GenericType::from(name); + let inner = f + .output + .type_arguments + .as_ref() + .unwrap() + .first() + .expect("Missing inner type."); + + match gt { + GenericType::Option | GenericType::Vec => { + let inner_typ = abi_types.get(&inner.type_id).unwrap_or_else(|| { + panic!("function_output_type_id: Generic inner type with TypeID({}) is missing from the JSON ABI", inner.type_id) + }); + let (typ_name, _) = + typed_path_components(outer_typ, inner_typ, abi_types); + type_id(FUEL_TYPES_NAMESPACE, &typ_name) as usize + } + _ => proc_macro_error::abort_call_site!( + "Unsupported generic type for function outputs: {:?}", + gt + ), + } + } else { + f.output.type_id + } +} + +/// Given two `TypeDeclaration`s for a generic struct (e.g., `Vec`) and an inner type (e.g., `T`), +/// return the associated fully typed path. (e.g., `Vec`) +/// +/// We do this by recursively deriving the inner `T` for the provided inner `T`, so long as +/// the inner `T` is itself, generic. +pub fn typed_path_components( + outer: &TypeDeclaration, + inner: &TypeDeclaration, + abi_types: &HashMap, +) -> (String, TokenStream) { + let outer_name = derive_type_name(outer); + let outer_ident = format_ident! { "{}", outer_name }; + let inner_name = derive_type_name(inner); + let inner_ident = format_ident! { "{}", inner_name }; + let mut tokens = quote! { #inner_ident }; + + let mut curr = inner; + while is_generic_type(curr) { + let gt = GenericType::from(curr); + match gt { + GenericType::Option | GenericType::Vec => { + curr = abi_types.get(&inner.type_id).unwrap_or_else(|| { + panic!("typed_path_components: Generic inner type with TypeID({}) is missing from the JSON ABI", inner.type_id) + }); + let name = derive_type_name(curr); + let ident = format_ident! { "{}", name }; + tokens = quote! { #tokens<#ident> } + } + _ => proc_macro_error::abort_call_site!( + "Unsupported generic type for typed path: {:?}", + gt + ), + } + } + + tokens = quote! { #outer_ident<#tokens> }; + + // Remove white space from path` + let name = tokens.to_string().replace(' ', ""); + + (name, tokens) +} + +/// Determine whether or not the given type name is an unsupported type. +/// +/// Since we allow unsupported types in the ABI JSON, this check is only +/// performed on indexer handler function arg typed paths. +pub fn is_unsupported_type(type_name: &str) -> bool { + let gt = GenericType::from(type_name); + match gt { + GenericType::Vec => UNSUPPORTED_ABI_JSON_TYPES.contains(gt.into()), + _ => UNSUPPORTED_ABI_JSON_TYPES.contains(type_name), + } +} diff --git a/packages/fuel-indexer-macros/src/indexer.rs b/packages/fuel-indexer-macros/src/indexer.rs index 000065fad..175c2eb9c 100644 --- a/packages/fuel-indexer-macros/src/indexer.rs +++ b/packages/fuel-indexer-macros/src/indexer.rs @@ -1,6 +1,9 @@ use crate::{ - helpers::*, native::handler_block_native, parse::IndexerConfig, - schema::process_graphql_schema, wasm::handler_block_wasm, + helpers::*, + native::{handler_block_native, native_main}, + parse::IndexerConfig, + schema::process_graphql_schema, + wasm::handler_block_wasm, }; use fuel_abi_types::abi::program::TypeDeclaration; use fuel_indexer_lib::{ @@ -34,15 +37,17 @@ fn process_fn_items( ) } - let abi = get_json_abi(abi_path); + let abi = get_json_abi(abi_path).unwrap_or_default(); - let mut decoded_abi_types = HashSet::new(); + let mut decoded_type_snippets = HashSet::new(); + let mut decoded_log_match_arms = HashSet::new(); + let mut decoded_type_fields = HashSet::new(); let mut abi_dispatchers = Vec::new(); - let funcs = abi.clone().unwrap_or_default().functions; - let abi_types = abi.clone().unwrap_or_default().types; - let abi_log_types = abi.clone().unwrap_or_default().logged_types; - let abi_msg_types = abi.unwrap_or_default().messages_types; + let funcs = abi.clone().functions; + let abi_types = abi.clone().types; + let abi_log_types = abi.clone().logged_types.unwrap_or_default(); + let abi_msg_types = abi.clone().messages_types.unwrap_or_default(); let fuel_types = FUEL_PRIMITIVES .iter() .map(|x| { @@ -57,40 +62,30 @@ fn process_fn_items( }) .collect::>(); + let _tuple_types_tyid = abi_types + .iter() + .filter_map(|typ| { + if is_tuple_type(typ) { + return Some((typ.type_id, typ.clone())); + } + + None + }) + .collect::>(); + + // Used to do a reverse lookup of typed path names to ABI type IDs. let mut type_ids = RESERVED_TYPEDEF_NAMES .iter() .map(|x| (x.to_string(), type_id(FUEL_TYPES_NAMESPACE, x) as usize)) .collect::>(); - let abi_types_tyid = abi_types + let mut abi_types_tyid = abi_types .iter() - .filter(|typ| { - if is_ignored_type(typ) { - return false; - } - true - }) .map(|typ| (typ.type_id, typ.clone())) .collect::>(); - let log_type_decoders = abi_log_types - .iter() - .flatten() - .map(|typ| { - let ty_id = typ.application.type_id; - let log_id = typ.log_id as usize; - - quote! { - #log_id => { - self.decode_type(#ty_id, data); - } - } - }) - .collect::>(); - let message_types_decoders = abi_msg_types .iter() - .flatten() .map(|typ| { let message_type_id = typ.message_id; let ty_id = typ.application.type_id; @@ -115,30 +110,78 @@ fn process_fn_items( return None; } - let name = typ.rust_type_ident(); - let ty = typ.rust_type_token(); - - if is_fuel_primitive(&ty) { - proc_macro_error::abort_call_site!("'{}' is a reserved Fuel type.", ty) + if is_fuel_primitive(typ) { + proc_macro_error::abort_call_site!( + "'{}' is a reserved Fuel type.", + typ.name() + ) } - type_ids.insert(ty.to_string(), typ.type_id); - decoded_abi_types.insert(typ.type_id); + if is_generic_type(typ) { + let gt = GenericType::from(typ); + match gt { + GenericType::Vec | GenericType::Option => { + let ab_types = abi_types_tyid.clone(); + let inner_typs = derive_generic_inner_typedefs( + typ, + &funcs, + &abi_log_types, + &ab_types, + ); + + return Some( + inner_typs + .iter() + .filter_map(|inner_typ| { + let (typ_name, type_tokens) = typed_path_components( + typ, + inner_typ, + &abi_types_tyid, + ); + let ty_id = + type_id(FUEL_TYPES_NAMESPACE, &typ_name) as usize; + + let typ = TypeDeclaration { + type_id: ty_id, + type_field: typ_name.clone(), + ..typ.clone() + }; + + if decoded_type_snippets.contains(&ty_id) { + return None; + } + + abi_types_tyid.insert(ty_id, typ.clone()); + type_ids.insert(typ_name.clone(), ty_id); + + decoded_type_snippets.insert(ty_id); - Some(decode_snippet(typ.type_id, &ty, &name)) + Some(decode_snippet(&type_tokens, &typ)) + }) + .collect::>(), + ); + } + _ => unimplemented!("Unsupported decoder generic type: {:?}", gt), + } + } else { + let type_tokens = typ.rust_tokens(); + type_ids.insert(type_tokens.to_string(), typ.type_id); + decoded_type_snippets.insert(typ.type_id); + Some(vec![decode_snippet(&type_tokens, typ)]) + } }) + .flatten() .collect::>(); let fuel_type_decoders = fuel_types .values() .map(|typ| { - let name = typ.rust_type_ident(); - let ty = typ.rust_type_token(); + let type_tokens = typ.rust_tokens(); - type_ids.insert(ty.to_string(), typ.type_id); - decoded_abi_types.insert(typ.type_id); + type_ids.insert(type_tokens.to_string(), typ.type_id); + decoded_type_snippets.insert(typ.type_id); - decode_snippet(typ.type_id, &ty, &name) + decode_snippet(&type_tokens, typ) }) .collect::>(); @@ -151,20 +194,62 @@ fn process_fn_items( return None; } - let name = typ.rust_type_ident(); - let ty = typ.rust_type_token(); - - if is_fuel_primitive(&ty) { - proc_macro_error::abort_call_site!("'{}' is a reserved Fuel type.", ty) + if is_fuel_primitive(typ) { + proc_macro_error::abort_call_site!( + "'{}' is a reserved Fuel type.", + typ.name() + ) } - type_ids.insert(ty.to_string(), typ.type_id); - decoded_abi_types.insert(typ.type_id); + if is_generic_type(typ) { + let inner_typs = derive_generic_inner_typedefs( + typ, + &funcs, + &abi_log_types, + &abi_types_tyid, + ); + + return Some( + inner_typs + .iter() + .filter_map(|inner_typ| { + let (typ_name, type_tokens) = + typed_path_components(typ, inner_typ, &abi_types_tyid); + let ty_id = type_id(FUEL_TYPES_NAMESPACE, &typ_name) as usize; + + if decoded_type_fields.contains(&ty_id) { + return None; + } - Some(quote! { - #name: Vec<#ty> - }) + let typ = TypeDeclaration { + type_id: ty_id, + type_field: typ_name.clone(), + ..typ.clone() + }; + + let ident = typ.decoder_field_ident(); + + type_ids.insert(typ_name.clone(), ty_id); + decoded_type_fields.insert(ty_id); + + Some(quote! { + #ident: Vec<#type_tokens> + }) + }) + .collect::>(), + ); + } else { + let ident = typ.decoder_field_ident(); + let type_tokens = typ.rust_tokens(); + type_ids.insert(typ.rust_tokens().to_string(), typ.type_id); + decoded_type_fields.insert(typ.type_id); + + Some(vec![quote! { + #ident: Vec<#type_tokens> + }]) + } }) + .flatten() .collect::>(); let fuel_struct_fields = fuel_types @@ -174,11 +259,15 @@ fn process_fn_items( return None; } - let name = typ.rust_type_ident(); - let ty = typ.rust_type_token(); + let name = typ.decoder_field_ident(); + let ty = typ.rust_tokens(); type_ids.insert(ty.to_string(), typ.type_id); - decoded_abi_types.insert(typ.type_id); + decoded_type_snippets.insert(typ.type_id); + + if decoded_type_fields.contains(&typ.type_id) { + return None; + } Some(quote! { #name: Vec<#ty> @@ -188,6 +277,56 @@ fn process_fn_items( let decoder_struct_fields = [abi_struct_fields, fuel_struct_fields].concat(); + // Since log type decoders use `TypeDeclaration`s that were manually created specifically + // for generics, we parsed log types after other ABI types. + let log_type_decoders = abi_log_types + .iter() + .filter_map(|log| { + let ty_id = log.application.type_id; + let log_id = log.log_id as usize; + let typ = abi_types_tyid.get(&log.application.type_id).unwrap(); + + if is_non_decodable_type(typ) { + return None; + } + + if is_generic_type(typ) { + let gt = GenericType::from(typ); + match gt { + GenericType::Vec | GenericType::Option => { + let inner_typ = + derive_log_generic_inner_typedefs(log, &abi, &abi_types_tyid); + + let (typ_name, _) = + typed_path_components(typ, inner_typ, &abi_types_tyid); + + let ty_id = type_id(FUEL_TYPES_NAMESPACE, &typ_name) as usize; + let _typ = abi_types_tyid.get(&ty_id).expect( + "Could not get generic log type reference from ABI types.", + ); + + decoded_log_match_arms.insert(log_id); + + Some(quote! { + #log_id => { + self.decode_type(#ty_id, data); + } + }) + } + _ => unimplemented!("Unsupported decoder generic type: {:?}", gt), + } + } else { + decoded_log_match_arms.insert(log_id); + + Some(quote! { + #log_id => { + self.decode_type(#ty_id, data); + } + }) + } + }) + .collect::>(); + let abi_selectors = funcs .iter() .map(|function| { @@ -201,7 +340,7 @@ fn process_fn_items( .collect(); let sig = resolve_fn_selector(&function.name, ¶ms[..]); let selector = u64::from_be_bytes(sig); - let ty_id = function.output.type_id; + let ty_id = function_output_type_id(function, &abi_types_tyid); quote! { #selector => #ty_id, @@ -312,28 +451,35 @@ fn process_fn_items( } FnArg::Typed(PatType { ty, .. }) => { if let Type::Path(path) = &**ty { - let typ = path + let path_seg = path .path .segments .last() .expect("Could not get last path segment."); - let typ_name = typ.ident.to_string(); - let dispatcher_name = decoded_ident(&typ_name); + let path_type_name = typed_path_name(path); + + if is_unsupported_type(&path_type_name) { + proc_macro_error::abort_call_site!( + "Type with ident '{:?}' is not currently supported.", + path_seg.ident + ) + } - if !type_ids.contains_key(&typ_name) { + if !type_ids.contains_key(&path_type_name) { proc_macro_error::abort_call_site!( "Type with ident '{:?}' not defined in the ABI.", - typ.ident + path_seg.ident ); }; - if DISALLOWED_ABI_JSON_TYPES.contains(typ_name.as_str()) { - proc_macro_error::abort_call_site!( - "Type with ident '{:?}' is not currently supported.", - typ.ident - ) - } + let ty_id = type_ids.get(&path_type_name).unwrap(); + let typ = match abi_types_tyid.get(ty_id) { + Some(typ) => typ, + None => fuel_types.get(ty_id).unwrap(), + }; + + let dispatcher_name = typ.decoder_field_ident(); input_checks .push(quote! { self.#dispatcher_name.len() > 0 }); @@ -779,6 +925,7 @@ pub fn process_indexer_module(attrs: TokenStream, item: TokenStream) -> TokenStr let (handler_block, fn_items) = process_fn_items(&manifest, abi, indexer_module); let handler_block = handler_block_native(handler_block); + let naitve_main_tokens = native_main(); quote! { @@ -790,53 +937,7 @@ pub fn process_indexer_module(attrs: TokenStream, item: TokenStream) -> TokenStr #fn_items - #[tokio::main] - async fn main() -> anyhow::Result<()> { - - let args = IndexerArgs::parse(); - - let IndexerArgs { manifest, .. } = args.clone(); - - - let config = args - .config - .as_ref() - .map(IndexerConfig::from_file) - .unwrap_or(Ok(IndexerConfig::from(args)))?; - - init_logging(&config).await?; - - info!("Configuration: {:?}", config); - - let (tx, rx) = channel::(SERVICE_REQUEST_CHANNEL_SIZE); - - let pool = IndexerConnectionPool::connect(&config.database.to_string()).await?; - - if config.run_migrations { - let mut c = pool.acquire().await?; - queries::run_migration(&mut c).await?; - } - - let mut service = IndexerService::new(config.clone(), pool.clone(), rx).await?; - - if manifest.is_none() { - panic!("Manifest required to use native execution."); - } - - let p = manifest.unwrap(); - if config.verbose { - info!("Using manifest file located at '{}'", p.display()); - } - let manifest = Manifest::from_file(&p)?; - service.register_native_indexer(manifest, handle_events).await?; - - let service_handle = tokio::spawn(service.run()); - let web_handle = tokio::spawn(WebApi::build_and_run(config.clone(), pool, tx)); - - let _ = tokio::join!(service_handle, web_handle); - - Ok(()) - } + #naitve_main_tokens } } ExecutionSource::Wasm => { diff --git a/packages/fuel-indexer-macros/src/native.rs b/packages/fuel-indexer-macros/src/native.rs index ce90f43c2..544dd066c 100644 --- a/packages/fuel-indexer-macros/src/native.rs +++ b/packages/fuel-indexer-macros/src/native.rs @@ -26,7 +26,7 @@ pub fn handler_block_native( } } -/// Prelude imports for the _indexer_ module. +/// Prelude imports for the `indexer` module. /// /// These imports are placed below the top-level lib imports, so any /// dependencies imported here will only be within the scope of the @@ -47,3 +47,56 @@ fn native_prelude() -> proc_macro2::TokenStream { }; } } + +/// Generate the `main` function for the native execution module. +pub fn native_main() -> proc_macro2::TokenStream { + quote! { + #[tokio::main] + async fn main() -> anyhow::Result<()> { + + let args = IndexerArgs::parse(); + + let IndexerArgs { manifest, .. } = args.clone(); + + + let config = args + .config + .as_ref() + .map(IndexerConfig::from_file) + .unwrap_or(Ok(IndexerConfig::from(args)))?; + + init_logging(&config).await?; + + info!("Configuration: {:?}", config); + + let (tx, rx) = channel::(SERVICE_REQUEST_CHANNEL_SIZE); + + let pool = IndexerConnectionPool::connect(&config.database.to_string()).await?; + + if config.run_migrations { + let mut c = pool.acquire().await?; + queries::run_migration(&mut c).await?; + } + + let mut service = IndexerService::new(config.clone(), pool.clone(), rx).await?; + + if manifest.is_none() { + panic!("Manifest required to use native execution."); + } + + let p = manifest.unwrap(); + if config.verbose { + info!("Using manifest file located at '{}'", p.display()); + } + let manifest = Manifest::from_file(&p)?; + service.register_native_indexer(manifest, handle_events).await?; + + let service_handle = tokio::spawn(service.run()); + let web_handle = tokio::spawn(WebApi::build_and_run(config.clone(), pool, tx)); + + let _ = tokio::join!(service_handle, web_handle); + + Ok(()) + } + } +} diff --git a/packages/fuel-indexer-macros/src/wasm.rs b/packages/fuel-indexer-macros/src/wasm.rs index f96aeb02d..8d3a0ed7b 100644 --- a/packages/fuel-indexer-macros/src/wasm.rs +++ b/packages/fuel-indexer-macros/src/wasm.rs @@ -48,7 +48,7 @@ fn wasm_prelude() -> proc_macro2::TokenStream { use fuel_indexer_utils::plugin::serde::{Deserialize, Serialize}; use fuels::{ core::{codec::ABIDecoder, Configurables, traits::{Parameterize, Tokenizable}}, - types::{StringToken}, + types::{StringToken, param_types::ParamType}, }; } } diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock index d643155c4..804c958f2 100644 --- a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock +++ b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock @@ -1,6 +1,6 @@ [[package]] name = 'core' -source = 'path+from-root-63B5D95B29B128A3' +source = 'path+from-root-EB296BD18C0E4CC4' [[package]] name = 'fuel-indexer-test' @@ -9,5 +9,5 @@ dependencies = ['std'] [[package]] name = 'std' -source = 'git+https://github.com/fuellabs/sway?tag=v0.45.0#92dc9f361a9508a940c0d0708130f26fa044f6b3' +source = 'git+https://github.com/fuellabs/sway?tag=v0.44.1#04a597093e7441898933dd412b8e4dc6ac860cd3' dependencies = ['core'] diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json index 2d5d0fa97..ea9eaa56a 100644 --- a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json +++ b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json @@ -12,12 +12,12 @@ "components": [ { "name": "__tuple_element", - "type": 16, + "type": 17, "typeArguments": null }, { "name": "__tuple_element", - "type": 29, + "type": 30, "typeArguments": null } ], @@ -29,7 +29,7 @@ "components": [ { "name": "__tuple_element", - "type": 31, + "type": 32, "typeArguments": null }, { @@ -46,7 +46,7 @@ "components": [ { "name": "__tuple_element", - "type": 32, + "type": 33, "typeArguments": null }, { @@ -68,17 +68,17 @@ "components": [ { "name": "__tuple_element", - "type": 31, + "type": 32, "typeArguments": null }, { "name": "__tuple_element", - "type": 32, + "type": 33, "typeArguments": null }, { "name": "__tuple_element", - "type": 14, + "type": 15, "typeArguments": null } ], @@ -102,17 +102,17 @@ "components": [ { "name": "Ping", - "type": 23, + "type": 24, "typeArguments": null }, { "name": "Pung", - "type": 25, + "type": 26, "typeArguments": null }, { "name": "Call", - "type": 10, + "type": 11, "typeArguments": null } ], @@ -124,12 +124,12 @@ "components": [ { "name": "Address", - "type": 18, + "type": 19, "typeArguments": null }, { "name": "ContractId", - "type": 20, + "type": 21, "typeArguments": null } ], @@ -149,6 +149,25 @@ }, { "typeId": 10, + "type": "enum Option", + "components": [ + { + "name": "None", + "type": 0, + "typeArguments": null + }, + { + "name": "Some", + "type": 13, + "typeArguments": null + } + ], + "typeParameters": [ + 13 + ] + }, + { + "typeId": 11, "type": "enum SimpleEnum", "components": [ { @@ -170,7 +189,7 @@ "typeParameters": null }, { - "typeId": 11, + "typeId": 12, "type": "enum UserError", "components": [ { @@ -182,43 +201,43 @@ "typeParameters": null }, { - "typeId": 12, + "typeId": 13, "type": "generic T", "components": null, "typeParameters": null }, { - "typeId": 13, + "typeId": 14, "type": "raw untyped ptr", "components": null, "typeParameters": null }, { - "typeId": 14, + "typeId": 15, "type": "str[12]", "components": null, "typeParameters": null }, { - "typeId": 15, + "typeId": 16, "type": "str[32]", "components": null, "typeParameters": null }, { - "typeId": 16, + "typeId": 17, "type": "str[5]", "components": null, "typeParameters": null }, { - "typeId": 17, + "typeId": 18, "type": "str[78]", "components": null, "typeParameters": null }, { - "typeId": 18, + "typeId": 19, "type": "struct Address", "components": [ { @@ -230,7 +249,7 @@ "typeParameters": null }, { - "typeId": 19, + "typeId": 20, "type": "struct ComplexTupleStruct", "components": [ { @@ -242,7 +261,7 @@ "typeParameters": null }, { - "typeId": 20, + "typeId": 21, "type": "struct ContractId", "components": [ { @@ -254,85 +273,85 @@ "typeParameters": null }, { - "typeId": 21, + "typeId": 22, "type": "struct ExampleMessageStruct", "components": [ { "name": "id", - "type": 32, + "type": 33, "typeArguments": null }, { "name": "message", - "type": 15, + "type": 16, "typeArguments": null } ], "typeParameters": null }, { - "typeId": 22, + "typeId": 23, "type": "struct ExplicitQueryStruct", "components": [ { "name": "id", - "type": 32, + "type": 33, "typeArguments": null } ], "typeParameters": null }, { - "typeId": 23, + "typeId": 24, "type": "struct Ping", "components": [ { "name": "id", - "type": 32, + "type": 33, "typeArguments": null }, { "name": "value", - "type": 32, + "type": 33, "typeArguments": null }, { "name": "message", - "type": 15, + "type": 16, "typeArguments": null } ], "typeParameters": null }, { - "typeId": 24, + "typeId": 25, "type": "struct Pong", "components": [ { "name": "id", - "type": 32, + "type": 33, "typeArguments": null }, { "name": "value", - "type": 32, + "type": 33, "typeArguments": null } ], "typeParameters": null }, { - "typeId": 25, + "typeId": 26, "type": "struct Pung", "components": [ { "name": "id", - "type": 32, + "type": 33, "typeArguments": null }, { "name": "value", - "type": 32, + "type": 33, "typeArguments": null }, { @@ -349,38 +368,38 @@ "typeParameters": null }, { - "typeId": 26, + "typeId": 27, "type": "struct RawVec", "components": [ { "name": "ptr", - "type": 13, + "type": 14, "typeArguments": null }, { "name": "cap", - "type": 32, + "type": 33, "typeArguments": null } ], "typeParameters": [ - 12 + 13 ] }, { - "typeId": 27, + "typeId": 28, "type": "struct SimpleQueryStruct", "components": [ { "name": "id", - "type": 32, + "type": 33, "typeArguments": null } ], "typeParameters": null }, { - "typeId": 28, + "typeId": 29, "type": "struct SimpleTupleStruct", "components": [ { @@ -392,56 +411,56 @@ "typeParameters": null }, { - "typeId": 29, + "typeId": 30, "type": "struct TupleStructItem", "components": [ { "name": "id", - "type": 32, + "type": 33, "typeArguments": null } ], "typeParameters": null }, { - "typeId": 30, + "typeId": 31, "type": "struct Vec", "components": [ { "name": "buf", - "type": 26, + "type": 27, "typeArguments": [ { "name": "", - "type": 12, + "type": 13, "typeArguments": null } ] }, { "name": "len", - "type": 32, + "type": 33, "typeArguments": null } ], "typeParameters": [ - 12 + 13 ] }, { - "typeId": 31, + "typeId": 32, "type": "u32", "components": null, "typeParameters": null }, { - "typeId": 32, + "typeId": 33, "type": "u64", "components": null, "typeParameters": null }, { - "typeId": 33, + "typeId": 34, "type": "u8", "components": null, "typeParameters": null @@ -468,7 +487,7 @@ "name": "trigger_callreturn", "output": { "name": "", - "type": 25, + "type": 26, "typeArguments": null }, "attributes": null @@ -478,7 +497,7 @@ "name": "trigger_deeply_nested", "output": { "name": "", - "type": 27, + "type": 28, "typeArguments": null }, "attributes": null @@ -497,7 +516,7 @@ "inputs": [ { "name": "num", - "type": 32, + "type": 33, "typeArguments": null } ], @@ -514,11 +533,27 @@ "name": "trigger_explicit", "output": { "name": "", - "type": 22, + "type": 23, "typeArguments": null }, "attributes": null }, + { + "inputs": [], + "name": "trigger_generics", + "output": { + "name": "", + "type": 10, + "typeArguments": [ + { + "name": "", + "type": 24, + "typeArguments": null + } + ] + }, + "attributes": null + }, { "inputs": [], "name": "trigger_log", @@ -569,7 +604,7 @@ "name": "trigger_multiargs", "output": { "name": "", - "type": 23, + "type": 24, "typeArguments": null }, "attributes": null @@ -579,7 +614,7 @@ "name": "trigger_panic", "output": { "name": "", - "type": 32, + "type": 33, "typeArguments": null }, "attributes": null @@ -589,7 +624,7 @@ "name": "trigger_ping", "output": { "name": "", - "type": 23, + "type": 24, "typeArguments": null }, "attributes": null @@ -599,7 +634,7 @@ "name": "trigger_ping_for_optional", "output": { "name": "", - "type": 23, + "type": 24, "typeArguments": null }, "attributes": null @@ -609,7 +644,7 @@ "name": "trigger_pong", "output": { "name": "", - "type": 24, + "type": 25, "typeArguments": null }, "attributes": null @@ -679,7 +714,7 @@ "name": "trigger_tuple", "output": { "name": "", - "type": 19, + "type": 20, "typeArguments": null }, "attributes": null @@ -688,11 +723,11 @@ "inputs": [ { "name": "v", - "type": 30, + "type": 31, "typeArguments": [ { "name": "", - "type": 33, + "type": 34, "typeArguments": null } ] @@ -738,7 +773,7 @@ "logId": 2, "loggedType": { "name": "", - "type": 11, + "type": 12, "typeArguments": [] } }, @@ -746,31 +781,43 @@ "logId": 3, "loggedType": { "name": "", - "type": 32, - "typeArguments": null + "type": 10, + "typeArguments": [ + { + "name": "", + "type": 24, + "typeArguments": [] + } + ] } }, { "logId": 4, "loggedType": { "name": "", - "type": 25, - "typeArguments": [] + "type": 31, + "typeArguments": [ + { + "name": "", + "type": 24, + "typeArguments": [] + } + ] } }, { "logId": 5, "loggedType": { "name": "", - "type": 25, - "typeArguments": [] + "type": 33, + "typeArguments": null } }, { "logId": 6, "loggedType": { "name": "", - "type": 24, + "type": 26, "typeArguments": [] } }, @@ -778,15 +825,15 @@ "logId": 7, "loggedType": { "name": "", - "type": 32, - "typeArguments": null + "type": 26, + "typeArguments": [] } }, { "logId": 8, "loggedType": { "name": "", - "type": 28, + "type": 25, "typeArguments": [] } }, @@ -794,7 +841,7 @@ "logId": 9, "loggedType": { "name": "", - "type": 17, + "type": 33, "typeArguments": null } }, @@ -802,11 +849,27 @@ "logId": 10, "loggedType": { "name": "", - "type": 30, + "type": 29, + "typeArguments": [] + } + }, + { + "logId": 11, + "loggedType": { + "name": "", + "type": 18, + "typeArguments": null + } + }, + { + "logId": 12, + "loggedType": { + "name": "", + "type": 31, "typeArguments": [ { "name": "", - "type": 24, + "type": 25, "typeArguments": [] } ] @@ -818,7 +881,7 @@ "messageId": 0, "messageType": { "name": "", - "type": 21, + "type": 22, "typeArguments": [] } } diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test.bin b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test.bin index ad7945f0d..a9c898598 100644 Binary files a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test.bin and b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test.bin differ diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/src/main.sw b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/src/main.sw index 11d78de24..ab16f0f1d 100644 --- a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/src/main.sw +++ b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/src/main.sw @@ -21,6 +21,7 @@ pub struct Pong { value: u64, } + pub struct Ping { id: u64, value: u64, @@ -103,6 +104,7 @@ abi FuelIndexer { fn trigger_mint(); #[payable] fn trigger_burn(); + fn trigger_generics() -> Option; } impl FuelIndexer for Contract { @@ -215,7 +217,6 @@ impl FuelIndexer for Contract { log("This does nothing as we don't handle CallData. But should implement this soon."); } - // NOTE: Keeping this to ensure Vec in ABI JSON is ok, even though we don't support it yet fn trigger_vec_pong_logdata() { let mut v: Vec = Vec::new(); v.push(Pong{ id: 5555, value: 5555 }); @@ -273,4 +274,18 @@ impl FuelIndexer for Contract { fn trigger_burn() { burn(BASE_ASSET_ID, 100); } + + fn trigger_generics() -> Option { + let x = Some(Ping{ id: 8888, value: 8888, message: "aaaasdfsdfasdfsdfaasdfsdfasdfsdf" }); + + let mut v: Vec = Vec::new(); + v.push(Ping{ id: 5555, value: 5555, message: "aaaasdfsdfasdfsdfaasdfsdfasdfsdf" }); + v.push(Ping{ id: 6666, value: 6666, message: "aaaasdfsdfasdfsdfaasdfsdfasdfsdf" }); + v.push(Ping{ id: 7777, value: 7777, message: "aaaasdfsdfasdfsdfaasdfsdfasdfsdf" }); + + log(x); + log(v); + + x + } } diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/Forc.lock b/packages/fuel-indexer-tests/contracts/simple-wasm/Forc.lock index 7a577d865..95fa04b8a 100644 --- a/packages/fuel-indexer-tests/contracts/simple-wasm/Forc.lock +++ b/packages/fuel-indexer-tests/contracts/simple-wasm/Forc.lock @@ -5,9 +5,9 @@ dependencies = ['std'] [[package]] name = 'core' -source = 'path+from-root-63B5D95B29B128A3' +source = 'path+from-root-EB296BD18C0E4CC4' [[package]] name = 'std' -source = 'git+https://github.com/fuellabs/sway?tag=v0.45.0#92dc9f361a9508a940c0d0708130f26fa044f6b3' +source = 'git+https://github.com/fuellabs/sway?tag=v0.44.1#04a597093e7441898933dd412b8e4dc6ac860cd3' dependencies = ['core'] diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi-unsupported.json b/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi-unsupported.json deleted file mode 100644 index 9a141b65e..000000000 --- a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi-unsupported.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "types": [ - { - "typeId": 0, - "type": "b256", - "components": null, - "typeParameters": null - }, - { - "typeId": 1, - "type": "struct AnotherEvent", - "components": [ - { - "name": "id", - "type": 3, - "typeArguments": null - }, - { - "name": "account", - "type": 0, - "typeArguments": null - }, - { - "name": "hash", - "type": 0, - "typeArguments": null - } - ], - "typeParameters": null - }, - { - "typeId": 2, - "type": "struct SomeEvent", - "components": [ - { - "name": "id", - "type": 3, - "typeArguments": null - }, - { - "name": "account", - "type": 0, - "typeArguments": null - } - ], - "typeParameters": null - }, - { - "typeId": 3, - "type": "u64", - "components": null, - "typeParameters": null - }, - { - "typeId": 4, - "type": "struct RawVec", - "components": [ - { - "name": "ptr", - "type": 13, - "typeArguments": null - }, - { - "name": "cap", - "type": 32, - "typeArguments": null - } - ], - "typeParameters": [ - 2 - ] - } - ], - "functions": [ - { - "inputs": [ - { - "name": "num", - "type": 3, - "typeArguments": null - } - ], - "name": "gimme_someevent", - "output": { - "name": "", - "type": 2, - "typeArguments": null - } - }, - { - "inputs": [ - { - "name": "num", - "type": 3, - "typeArguments": null - } - ], - "name": "gimme_anotherevent", - "output": { - "name": "", - "type": 1, - "typeArguments": null - } - } - ], - "loggedTypes": [] - } \ No newline at end of file diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json b/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json index d973a61f7..450fac9f1 100644 --- a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json +++ b/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json @@ -8,11 +8,23 @@ }, { "typeId": 1, + "type": "generic T", + "components": null, + "typeParameters": null + }, + { + "typeId": 2, + "type": "raw untyped ptr", + "components": null, + "typeParameters": null + }, + { + "typeId": 3, "type": "struct AnotherEvent", "components": [ { "name": "id", - "type": 3, + "type": 7, "typeArguments": null }, { @@ -29,12 +41,31 @@ "typeParameters": null }, { - "typeId": 2, + "typeId": 4, + "type": "struct RawVec", + "components": [ + { + "name": "ptr", + "type": 2, + "typeArguments": null + }, + { + "name": "cap", + "type": 7, + "typeArguments": null + } + ], + "typeParameters": [ + 1 + ] + }, + { + "typeId": 5, "type": "struct SomeEvent", "components": [ { "name": "id", - "type": 3, + "type": 7, "typeArguments": null }, { @@ -46,43 +77,88 @@ "typeParameters": null }, { - "typeId": 3, + "typeId": 6, + "type": "struct Vec", + "components": [ + { + "name": "buf", + "type": 4, + "typeArguments": [ + { + "name": "", + "type": 1, + "typeArguments": null + } + ] + }, + { + "name": "len", + "type": 7, + "typeArguments": null + } + ], + "typeParameters": [ + 1 + ] + }, + { + "typeId": 7, "type": "u64", "components": null, "typeParameters": null } ], "functions": [ + { + "inputs": [], + "name": "gimme_an_unsupported_type", + "output": { + "name": "", + "type": 6, + "typeArguments": [ + { + "name": "", + "type": 5, + "typeArguments": null + } + ] + }, + "attributes": null + }, { "inputs": [ { "name": "num", - "type": 3, + "type": 7, "typeArguments": null } ], - "name": "gimme_someevent", + "name": "gimme_anotherevent", "output": { "name": "", - "type": 2, + "type": 3, "typeArguments": null - } + }, + "attributes": null }, { "inputs": [ { "name": "num", - "type": 3, + "type": 7, "typeArguments": null } ], - "name": "gimme_anotherevent", + "name": "gimme_someevent", "output": { "name": "", - "type": 1, + "type": 5, "typeArguments": null - } + }, + "attributes": null } ], - "loggedTypes": [] + "loggedTypes": [], + "messagesTypes": [], + "configurables": [] } \ No newline at end of file diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts.bin b/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts.bin index aea8f6390..f40363bd3 100644 Binary files a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts.bin and b/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts.bin differ diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/src/main.sw b/packages/fuel-indexer-tests/contracts/simple-wasm/src/main.sw index fb388a5bc..c93b6cdb9 100644 --- a/packages/fuel-indexer-tests/contracts/simple-wasm/src/main.sw +++ b/packages/fuel-indexer-tests/contracts/simple-wasm/src/main.sw @@ -5,6 +5,7 @@ use std::{address::Address, hash::sha256}; abi Simple { fn gimme_someevent(num: u64) -> SomeEvent; fn gimme_anotherevent(num: u64) -> AnotherEvent; + fn gimme_an_unsupported_type() -> Vec; } fn make_someevent(num: u64) -> SomeEvent { @@ -42,4 +43,12 @@ impl Simple for Contract { hash: sha256(num >> 2), } } + + fn gimme_an_unsupported_type() -> Vec { + let mut v: Vec = Vec::new(); + v.push(make_someevent(1)); + v.push(make_someevent(2)); + v.push(make_someevent(3)); + v + } } diff --git a/packages/fuel-indexer-tests/indexers/fuel-indexer-test/fuel_indexer_test.yaml b/packages/fuel-indexer-tests/indexers/fuel-indexer-test/fuel_indexer_test.yaml index b9d6dce0f..03ef1f48d 100644 --- a/packages/fuel-indexer-tests/indexers/fuel-indexer-test/fuel_indexer_test.yaml +++ b/packages/fuel-indexer-tests/indexers/fuel-indexer-test/fuel_indexer_test.yaml @@ -4,7 +4,7 @@ graphql_schema: packages/fuel-indexer-tests/indexers/fuel-indexer-test/schema/fu abi: packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json start_block: ~ end_block: ~ -contract_id: fuel12zfku575m48qgctuca7lf5rxwnrv93nn3yfxxtdte9hqqj7v45vsuujmpt +contract_id: fuel1h97g4w7wrwv78ad5xquxs3ecl30xstuqgzsw4uvpakg7yyh8d86spsken2 identifier: index1 module: wasm: target/wasm32-unknown-unknown/release/fuel_indexer_test.wasm diff --git a/packages/fuel-indexer-tests/indexers/fuel-indexer-test/src/lib.rs b/packages/fuel-indexer-tests/indexers/fuel-indexer-test/src/lib.rs index e51be29a2..a86761c60 100644 --- a/packages/fuel-indexer-tests/indexers/fuel-indexer-test/src/lib.rs +++ b/packages/fuel-indexer-tests/indexers/fuel-indexer-test/src/lib.rs @@ -548,4 +548,27 @@ mod fuel_indexer_test { e.save(); } + + fn fuel_indexer_test_trigger_generics(ping: Option) { + info!("fuel_indexer_test_trigger_generics handling trigger_generics event."); + + assert!(ping.is_some()); + + let ping = ping.unwrap(); + assert_eq!(ping.id, 8888); + assert_eq!(ping.value, 8888); + assert_eq!( + ping.message, + SizedAsciiString::<32>::new("aaaasdfsdfasdfsdfaasdfsdfasdfsdf".to_string()) + .unwrap() + ); + + let ping = PingEntity { + id: uid(ping.id.to_le_bytes()), + value: ping.value, + message: ping.message.to_string(), + }; + + ping.save(); + } } diff --git a/packages/fuel-indexer-tests/src/fixtures.rs b/packages/fuel-indexer-tests/src/fixtures.rs index 806933537..0d03aa2d2 100644 --- a/packages/fuel-indexer-tests/src/fixtures.rs +++ b/packages/fuel-indexer-tests/src/fixtures.rs @@ -809,6 +809,21 @@ pub mod test_web { HttpResponse::Ok() } + async fn fuel_indexer_test_trigger_generics( + state: web::Data>, + ) -> impl Responder { + let _ = state + .contract + .methods() + .trigger_generics() + .tx_params(tx_params()) + .call() + .await + .unwrap(); + + HttpResponse::Ok() + } + pub struct AppState { pub contract: FuelIndexerTest, } @@ -874,6 +889,10 @@ pub mod test_web { .route("/enum", web::post().to(fuel_indexer_test_trigger_enum)) .route("/mint", web::post().to(fuel_indexer_test_trigger_mint)) .route("/burn", web::post().to(fuel_indexer_test_trigger_burn)) + .route( + "/generics", + web::post().to(fuel_indexer_test_trigger_generics), + ) } pub async fn server() -> Result<(), Box> { diff --git a/packages/fuel-indexer-tests/tests/indexing.rs b/packages/fuel-indexer-tests/tests/indexing.rs index d82363528..d4daf3fae 100644 --- a/packages/fuel-indexer-tests/tests/indexing.rs +++ b/packages/fuel-indexer-tests/tests/indexing.rs @@ -10,7 +10,7 @@ use std::{collections::HashSet, str::FromStr}; const REVERT_VM_CODE: u64 = 0x0004; const EXPECTED_CONTRACT_ID: &str = - "50936e53d4dd4e04617cc77df4d06674c6c2c6738912632dabc96e004bccad19"; + "b97c8abbce1b99e3f5b43038684738fc5e682f8040a0eaf181ed91e212e769f5"; const TRANSFER_BASE_ASSET_ID: &str = "0000000000000000000000000000000000000000000000000000000000000000"; @@ -637,3 +637,27 @@ async fn test_start_block() { assert_eq!(start, 3); } + +#[actix_web::test] +async fn test_generics() { + let IndexingTestComponents { ref db, .. } = + setup_indexing_test_components(None).await; + + mock_request("/generics").await; + + let mut conn = db.pool.acquire().await.unwrap(); + let expected_id = "4405f11f2e332ea850a884ce208d97d0cd68dc5bc0fd124a1a7b7f99962ff99b"; + let row = sqlx::query(&format!( + "SELECT * FROM fuel_indexer_test_index1.pingentity where id = '{expected_id}'" + )) + .fetch_one(&mut conn) + .await + .unwrap(); + + assert_eq!(row.get::<&str, usize>(0), expected_id); + assert_eq!(row.get::(1).to_u64().unwrap(), 8888); + assert_eq!( + row.get::<&str, usize>(2), + "aaaasdfsdfasdfsdfaasdfsdfasdfsdf" + ); +} diff --git a/packages/fuel-indexer-tests/tests/trybuild.rs b/packages/fuel-indexer-tests/tests/trybuild.rs index 56512a687..486f7175d 100644 --- a/packages/fuel-indexer-tests/tests/trybuild.rs +++ b/packages/fuel-indexer-tests/tests/trybuild.rs @@ -99,16 +99,7 @@ module: "simple_wasm.yaml", TestKind::Pass, // Using a custom manifest here - format!( - r#" - namespace: test_namespace - identifier: simple_wasm_executor - abi: {tests_root_str}/contracts/simple-wasm/out/debug/contracts-abi-unsupported.json - graphql_schema: {tests_root_str}/indexers/simple-wasm/schema/simple_wasm.graphql - contract_id: ~ - module: - wasm: {project_root_str}/target/wasm32-unknown-unknown/release/simple_wasm.wasm"# - ), + manifest_content.clone(), ), ( "fail_if_abi_contains_reserved_fuel_type.rs", @@ -138,6 +129,12 @@ module: TestKind::Fail, manifest_content.clone(), ), + ( + "fail_if_unsupported_type_used_in_handler_args.rs", + "simple_wasm.yaml", + TestKind::Fail, + manifest_content.clone(), + ), ]; for (name, manifest_name, kind, manifest_content) in tests { diff --git a/packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.rs b/packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.rs new file mode 100644 index 000000000..d97caf827 --- /dev/null +++ b/packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.rs @@ -0,0 +1,12 @@ +extern crate alloc; +use fuel_indexer_utils::prelude::*; + +#[indexer(manifest = "packages/fuel-indexer-tests/trybuild/simple_wasm.yaml")] +mod indexer { + fn function_one(event: Vec) { + let BlockHeight { id, account } = event; + + let t1 = Thing1 { id, account }; + t1.save(); + } +} diff --git a/packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.stderr b/packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.stderr new file mode 100644 index 000000000..96515ff14 --- /dev/null +++ b/packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.stderr @@ -0,0 +1,13 @@ +error: Type with ident 'Ident { ident: "Vec", span: #0 bytes(177..180) }' is not currently supported. + --> trybuild/fail_if_unsupported_type_used_in_handler_args.rs + | + | #[indexer(manifest = "packages/fuel-indexer-tests/trybuild/simple_wasm.yaml")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `indexer` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0601]: `main` function not found in crate `$CRATE` + --> trybuild/fail_if_unsupported_type_used_in_handler_args.rs + | + | } + | ^ consider adding a `main` function to `$DIR/trybuild/fail_if_unsupported_type_used_in_handler_args.rs`