From c08f41a75f55034543b9ea07a442dae34113e67c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 8 Sep 2023 17:14:40 +0200 Subject: [PATCH 1/4] enhancement: rebuild when manifest changed (#1338) rebuild when manifest changed --- .../forc-index/src/ops/forc_index_build.rs | 3 ++- plugins/forc-index/src/utils.rs | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/plugins/forc-index/src/ops/forc_index_build.rs b/plugins/forc-index/src/ops/forc_index_build.rs index b2d0526b3..90e78c4cc 100644 --- a/plugins/forc-index/src/ops/forc_index_build.rs +++ b/plugins/forc-index/src/ops/forc_index_build.rs @@ -85,9 +85,10 @@ pub fn init(command: BuildCommand) -> anyhow::Result<()> { }; // Rebuild the WASM module even if only the schema has changed. - crate::utils::ensure_rebuild_if_schema_changed( + crate::utils::ensure_rebuild_if_schema_or_manifest_changed( root_dir.as_path(), Path::new(manifest_schema_file.as_path()), + indexer_manifest_path.as_path(), manifest.execution_source(), )?; diff --git a/plugins/forc-index/src/utils.rs b/plugins/forc-index/src/utils.rs index 6fe64c104..3d0422050 100644 --- a/plugins/forc-index/src/utils.rs +++ b/plugins/forc-index/src/utils.rs @@ -150,9 +150,10 @@ pub fn touch_file(path: &Path) -> std::io::Result<()> { /// Set src/lib.rs' atime and mtime to now and thus ensure the WASM module is /// rebuilt if schema file has changed. -pub fn ensure_rebuild_if_schema_changed( +pub fn ensure_rebuild_if_schema_or_manifest_changed( project_dir: &Path, schema: &Path, + manifest: &Path, exec_source: ExecutionSource, ) -> std::io::Result<()> { let schema_mtime = { @@ -160,25 +161,29 @@ pub fn ensure_rebuild_if_schema_changed( filetime::FileTime::from_last_modification_time(&metadata) }; - let sourcefile = match exec_source { - ExecutionSource::Native => "main.rs", - ExecutionSource::Wasm => "lib.rs", + let manifest_mtime = { + let metadata = std::fs::metadata(manifest).unwrap(); + filetime::FileTime::from_last_modification_time(&metadata) }; - let lib_rs = { + let entrypoint_rs = { + let sourcefile = match exec_source { + ExecutionSource::Native => "main.rs", + ExecutionSource::Wasm => "lib.rs", + }; let mut path = project_dir.to_owned(); path.push("src"); path.push(sourcefile); path }; - let lib_rs_mtime = { - let metadata = std::fs::metadata(lib_rs.as_path()).unwrap(); + let entrypoint_rs_mtime = { + let metadata = std::fs::metadata(entrypoint_rs.as_path()).unwrap(); filetime::FileTime::from_last_modification_time(&metadata) }; - if schema_mtime > lib_rs_mtime { - touch_file(lib_rs.as_path())?; + if schema_mtime > entrypoint_rs_mtime || manifest_mtime > entrypoint_rs_mtime { + touch_file(entrypoint_rs.as_path())?; } Ok(()) From 8a2636e77066e9d7bccd8e3d9d005d2574d39c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Mon, 11 Sep 2023 16:19:06 +0200 Subject: [PATCH 2/4] chore: remove indexer asset versioning logic (#1340) remove indexer asset version --- .../database-types/src/lib.rs | 3 - .../20230911123600_remove_versions.down.sql | 4 ++ .../20230911123600_remove_versions.up.sql | 3 + .../fuel-indexer-database/postgres/src/lib.rs | 62 +++++-------------- packages/fuel-indexer-database/src/queries.rs | 24 ++----- packages/fuel-indexer-schema/src/db/tables.rs | 9 +-- packages/fuel-indexer/src/service.rs | 6 +- 7 files changed, 32 insertions(+), 79 deletions(-) create mode 100644 packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.down.sql create mode 100644 packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.up.sql diff --git a/packages/fuel-indexer-database/database-types/src/lib.rs b/packages/fuel-indexer-database/database-types/src/lib.rs index 405f9e57c..512d18639 100644 --- a/packages/fuel-indexer-database/database-types/src/lib.rs +++ b/packages/fuel-indexer-database/database-types/src/lib.rs @@ -566,9 +566,6 @@ pub struct IndexerAsset { /// Database ID of the indexer. pub index_id: i64, - /// Version associated with this indexer asset. - pub version: i32, - /// Digest of the asset's bytes. pub digest: String, diff --git a/packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.down.sql b/packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.down.sql new file mode 100644 index 000000000..63d324d38 --- /dev/null +++ b/packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.down.sql @@ -0,0 +1,4 @@ +-- Active: 1693382282533@@127.0.0.1@5432@postgres +ALTER TABLE index_asset_registry_manifest ADD COLUMN version integer not null; +ALTER TABLE index_asset_registry_schema ADD COLUMN version integer not null; +ALTER TABLE index_asset_registry_wasm ADD COLUMN version integer not null; \ No newline at end of file diff --git a/packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.up.sql b/packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.up.sql new file mode 100644 index 000000000..598b5ac72 --- /dev/null +++ b/packages/fuel-indexer-database/postgres/migrations/20230911123600_remove_versions.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE index_asset_registry_manifest DROP COLUMN version; +ALTER TABLE index_asset_registry_schema DROP COLUMN version; +ALTER TABLE index_asset_registry_wasm DROP COLUMN version; \ No newline at end of file diff --git a/packages/fuel-indexer-database/postgres/src/lib.rs b/packages/fuel-indexer-database/postgres/src/lib.rs index 99fde57d3..e02701c68 100644 --- a/packages/fuel-indexer-database/postgres/src/lib.rs +++ b/packages/fuel-indexer-database/postgres/src/lib.rs @@ -526,27 +526,6 @@ pub async fn all_registered_indexers( .collect::>()) } -#[cfg_attr(feature = "metrics", metrics)] -pub async fn indexer_asset_version( - conn: &mut PoolConnection, - index_id: &i64, - asset_type: &IndexerAssetType, -) -> sqlx::Result { - match sqlx::query(&format!( - "SELECT COUNT(*) - FROM index_asset_registry_{} - WHERE index_id = {}", - asset_type.as_ref(), - index_id, - )) - .fetch_one(conn) - .await - { - Ok(row) => Ok(row.try_get::(0).unwrap_or(0)), - Err(_e) => Ok(0), - } -} - /// Register a single indexer asset. #[cfg_attr(feature = "metrics", metrics)] pub async fn register_indexer_asset( @@ -577,15 +556,10 @@ pub async fn register_indexer_asset( return Ok(asset); } - let current_version = indexer_asset_version(conn, &index.id, &asset_type) - .await - .expect("Failed to get asset version."); - let query = format!( - "INSERT INTO index_asset_registry_{} (index_id, bytes, version, digest) VALUES ({}, $1, {}, '{digest}') RETURNING *", + "INSERT INTO index_asset_registry_{} (index_id, bytes, digest) VALUES ({}, $1, '{digest}') RETURNING *", asset_type.as_ref(), index.id, - current_version + 1, ); let row = sqlx::QueryBuilder::new(query) @@ -603,22 +577,20 @@ pub async fn register_indexer_asset( let id = row.get(0); let index_id = row.get(1); - let version = row.get(2); - let digest = row.get(3); - let bytes = row.get(4); + let digest = row.get(2); + let bytes = row.get(3); Ok(IndexerAsset { id, index_id, - version, digest, bytes, }) } -/// Return the latest version for a given indexer asset type. +/// Returns the requested asset for an indexer with the given id. #[cfg_attr(feature = "metrics", metrics)] -pub async fn latest_asset_for_indexer( +pub async fn indexer_asset( conn: &mut PoolConnection, index_id: &i64, asset_type: IndexerAssetType, @@ -633,30 +605,26 @@ pub async fn latest_asset_for_indexer( let id = row.get(0); let index_id = row.get(1); - let version = row.get(2); - let digest = row.get(3); - let bytes = row.get(4); + let digest = row.get(2); + let bytes = row.get(3); Ok(IndexerAsset { id, index_id, - version, digest, bytes, }) } -/// Return the latest version for every indexer asset type. +/// Return every indexer asset type for an indexer with the give id. #[cfg_attr(feature = "metrics", metrics)] -pub async fn latest_assets_for_indexer( +pub async fn indexer_assets( conn: &mut PoolConnection, indexer_id: &i64, ) -> sqlx::Result { - let wasm = latest_asset_for_indexer(conn, indexer_id, IndexerAssetType::Wasm).await?; - let schema = - latest_asset_for_indexer(conn, indexer_id, IndexerAssetType::Schema).await?; - let manifest = - latest_asset_for_indexer(conn, indexer_id, IndexerAssetType::Manifest).await?; + let wasm = indexer_asset(conn, indexer_id, IndexerAssetType::Wasm).await?; + let schema = indexer_asset(conn, indexer_id, IndexerAssetType::Schema).await?; + let manifest = indexer_asset(conn, indexer_id, IndexerAssetType::Manifest).await?; Ok(IndexerAssetBundle { wasm, @@ -705,14 +673,12 @@ pub async fn asset_already_exists( Ok(row) => { let id = row.get(0); let index_id = row.get(1); - let version = row.get(2); - let digest = row.get(3); - let bytes = row.get(4); + let digest = row.get(2); + let bytes = row.get(3); Ok(Some(IndexerAsset { id, index_id, - version, digest, bytes, })) diff --git a/packages/fuel-indexer-database/src/queries.rs b/packages/fuel-indexer-database/src/queries.rs index 1fe6d8f58..39fae7644 100644 --- a/packages/fuel-indexer-database/src/queries.rs +++ b/packages/fuel-indexer-database/src/queries.rs @@ -230,18 +230,6 @@ pub async fn all_registered_indexers( } } -pub async fn indexer_asset_version( - conn: &mut IndexerConnection, - index_id: &i64, - asset_type: &IndexerAssetType, -) -> sqlx::Result { - match conn { - IndexerConnection::Postgres(ref mut c) => { - postgres::indexer_asset_version(c, index_id, asset_type).await - } - } -} - /// Register a single indexer asset. pub async fn register_indexer_asset( conn: &mut IndexerConnection, @@ -261,27 +249,27 @@ pub async fn register_indexer_asset( } } -/// Return the latest version for a given indexer asset type. -pub async fn latest_asset_for_indexer( +/// Returns the requested asset for an indexer with the given id. +pub async fn indexer_asset( conn: &mut IndexerConnection, index_id: &i64, asset_type: IndexerAssetType, ) -> sqlx::Result { match conn { IndexerConnection::Postgres(ref mut c) => { - postgres::latest_asset_for_indexer(c, index_id, asset_type).await + postgres::indexer_asset(c, index_id, asset_type).await } } } -/// Return the latest version for every indexer asset type. -pub async fn latest_assets_for_indexer( +/// Return every indexer asset type for an indexer with the give id. +pub async fn indexer_assets( conn: &mut IndexerConnection, index_id: &i64, ) -> sqlx::Result { match conn { IndexerConnection::Postgres(ref mut c) => { - postgres::latest_assets_for_indexer(c, index_id).await + postgres::indexer_assets(c, index_id).await } } } diff --git a/packages/fuel-indexer-schema/src/db/tables.rs b/packages/fuel-indexer-schema/src/db/tables.rs index b9cfd3e80..b43cbe186 100644 --- a/packages/fuel-indexer-schema/src/db/tables.rs +++ b/packages/fuel-indexer-schema/src/db/tables.rs @@ -203,12 +203,9 @@ impl IndexerSchema { let indexer_id = queries::get_indexer_id(&mut conn, namespace, identifier).await?; - let IndexerAsset { bytes, .. } = queries::latest_asset_for_indexer( - &mut conn, - &indexer_id, - IndexerAssetType::Manifest, - ) - .await?; + let IndexerAsset { bytes, .. } = + queries::indexer_asset(&mut conn, &indexer_id, IndexerAssetType::Manifest) + .await?; let manifest = Manifest::try_from(&bytes)?; let schema = GraphQLSchema::new(root.schema.clone()); diff --git a/packages/fuel-indexer/src/service.rs b/packages/fuel-indexer/src/service.rs index 85cc5f29d..f92f0148a 100644 --- a/packages/fuel-indexer/src/service.rs +++ b/packages/fuel-indexer/src/service.rs @@ -177,7 +177,7 @@ impl IndexerService { let mut conn = self.pool.acquire().await?; let indices = queries::all_registered_indexers(&mut conn).await?; for index in indices { - let assets = queries::latest_assets_for_indexer(&mut conn, &index.id).await?; + let assets = queries::indexer_assets(&mut conn, &index.id).await?; let mut manifest = Manifest::try_from(&assets.manifest.bytes)?; let start_block = get_start_block(&mut conn, &manifest).await.unwrap_or(1); @@ -306,9 +306,7 @@ async fn create_service_task( .await { Ok(id) => { - let assets = - queries::latest_assets_for_indexer(&mut conn, &id) - .await?; + let assets = queries::indexer_assets(&mut conn, &id).await?; let mut manifest = Manifest::try_from(&assets.manifest.bytes)?; From a4e5159ba689c96b63267eb1910c3f9be6a3b62f Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 11 Sep 2023 11:56:36 -0400 Subject: [PATCH 3/4] Add Mint and Burn receipts to fuel-explorer (#1342) --- .../schema/fuel_explorer.schema.graphql | 19 ++++++++++ .../fuel-explorer/fuel-explorer/src/lib.rs | 35 ++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql b/examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql index 1650b44bf..91ab44bf4 100644 --- a/examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql +++ b/examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql @@ -358,6 +358,23 @@ type MessageOutReceipt @entity(virtual: true) { is_message_out: Boolean! } +type MintReceipt @entity(virtual: true) { + sub_id: Bytes32! + contract_id: ContractId! + val: UInt8! + pc: UInt8! + isr: UInt8! +} + + +type BurnReceipt @entity(virtual: true) { + sub_id: Bytes32! + contract_id: ContractId! + val: UInt8! + pc: UInt8! + isr: UInt8! +} + union Receipt = CallReceipt | ReturnReceipt @@ -370,6 +387,8 @@ union Receipt = | TransferOutReceipt | ScriptResultReceipt | MessageOutReceipt + | MintReceipt + | BurnReceipt type VariableOutput @entity { id: ID! diff --git a/examples/fuel-explorer/fuel-explorer/src/lib.rs b/examples/fuel-explorer/fuel-explorer/src/lib.rs index 7c9b574ca..fff8a515a 100644 --- a/examples/fuel-explorer/fuel-explorer/src/lib.rs +++ b/examples/fuel-explorer/fuel-explorer/src/lib.rs @@ -714,7 +714,40 @@ impl From for Receipt { Self::from(receipt) } - fuel::Receipt::Mint { .. } | fuel::Receipt::Burn { .. } => todo!(), + fuel::Receipt::Mint { + sub_id, + contract_id, + val, + pc, + is, + } => { + let receipt = MintReceipt { + sub_id, + contract_id, + val, + pc, + isr: is, + }; + + Self::from(receipt) + } + fuel::Receipt::Burn { + sub_id, + contract_id, + val, + pc, + is, + } => { + let receipt = BurnReceipt { + sub_id, + contract_id, + val, + pc, + isr: is, + }; + + Self::from(receipt) + } } } } From c7d9f974d7483737c64da645ecc120b6a6e3d29d Mon Sep 17 00:00:00 2001 From: rashad Date: Mon, 11 Sep 2023 14:16:16 -0400 Subject: [PATCH 4/4] enhancement: add support for sway generics (#1332) * enhancement: add support for sway generics updates * lostman feedback --------- Co-authored-by: Rashad Alston --- packages/fuel-indexer-lib/src/constants.rs | 12 +- packages/fuel-indexer-macros/src/helpers.rs | 621 ++++++++++++++---- packages/fuel-indexer-macros/src/indexer.rs | 333 ++++++---- packages/fuel-indexer-macros/src/native.rs | 55 +- packages/fuel-indexer-macros/src/wasm.rs | 2 +- .../contracts/fuel-indexer-test/Forc.lock | 4 +- .../out/debug/fuel-indexer-test-abi.json | 225 ++++--- .../out/debug/fuel-indexer-test.bin | Bin 7600 -> 8688 bytes .../contracts/fuel-indexer-test/src/main.sw | 17 +- .../contracts/simple-wasm/Forc.lock | 4 +- .../out/debug/contracts-abi-unsupported.json | 107 --- .../simple-wasm/out/debug/contracts-abi.json | 102 ++- .../simple-wasm/out/debug/contracts.bin | Bin 680 -> 1532 bytes .../contracts/simple-wasm/src/main.sw | 9 + .../fuel-indexer-test/fuel_indexer_test.yaml | 2 +- .../indexers/fuel-indexer-test/src/lib.rs | 23 + packages/fuel-indexer-tests/src/fixtures.rs | 19 + packages/fuel-indexer-tests/tests/indexing.rs | 26 +- packages/fuel-indexer-tests/tests/trybuild.rs | 17 +- ...f_unsupported_type_used_in_handler_args.rs | 12 + ...supported_type_used_in_handler_args.stderr | 13 + 21 files changed, 1148 insertions(+), 455 deletions(-) delete mode 100644 packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi-unsupported.json create mode 100644 packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.rs create mode 100644 packages/fuel-indexer-tests/trybuild/fail_if_unsupported_type_used_in_handler_args.stderr 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 ad7945f0d0465057b027520cc93a98293c1104e3..a9c8985982c019b926c7897b92e542480658ac43 100644 GIT binary patch delta 3280 zcmcguOKcm*8J?9$d-brrGD(ppRcj?u@)|B+Dxw|wkz^Orf?83E#*$poRoSIeIaca4 zWd+wT-~!?yXn6?Q6w`xl)gc86^iWci5FP@AxfzHH_)ru&^q@n5s)bz;XbuKk)cs~> zDAA}3oYrgEh@v3{43&J1aNr|;+g)ljq-|0H>{;N&yS*?{9S?@f^m z@Y@o<4Lm3D3NV%UHt;oxe*-)(@iy>+#Ge8$3aoqvynbDFK1b(U68{N!N#ef(=Oq3I z@C|`Gz5)Dgi3fp~B|Z!M9f?!Gd5JFrf42?00?&6`!9+n$ya9Yu;LdZv?+EPYg1#s5 zJHRUvudX%8gNscjkeV{MHR-I(SBz}RW{a4q8YP6LbFy<^9D0pWei3PJ4L9wRi;ao zJt#HgUgsOB3WbMU3rbm3!y&q1B}^Q?K`o-?Ev&&IOb_kyHtq;eX5n{~xN>M9G+&ovHsPe;GUczDJ zwJBI&-!0{BDCHD2E{#iwIjNFn2TL?tEqb1D78`uruoDexV;$COwr27@u7kavu~iM) z#og=Z+h<{I79Cqh2i8u(TH9BG5-m$}@^Cg74FA6jzW+50-uzkyFCn^a)r7%eH_r_o zMl7=Ecm@~S45lrErX8YIvRYti(A4>xhYLdtN=PMDhIGij#!7%C^d z7-1X?g$sp5%_1gut;b5RmS(mH)~30e5#uh7Q;KgMj4op?jXsF!%_TTaDVM!TLH8Rz z6UDwZ++E?eoGd+!Xym?GodhKvjVGRAWVajKY*5TMV+C28JRN&(u>s#Xq`V#A&bh;=@67eFwuFAI zmC)`CWZGK9lbP9W;G8MQ97m!<&(tFNW%N_tURzTSuo%C<9{JuHxd_LT1W$WWdXmt< zD=V^`@Gy_Dq(2@K#U|4nY1t`f2HDU2nli>V{PDimFCI$b9!;}H{+Lo`#m;{AZ~xi8 zTf!gJ0`yjC7Wtn(692DJgS~&`j*?@$M_lC+OCSAph&jb(tV+a18Tru^>7WZ0b1jElyf#wuyw9;?0}~ZqG7Kj~=)!grQj^9t z_V=zK=b>vNirH=2ItgXQkJCOD8XS((iQ2xm#;a`BM2DTkMWUR)t18Jo^n! z$0%afZ(U%&=#I`WY)7D+;VGBh$?<9waCCl4Di=XOC=sEKuF`9u&4CeZii1X;;&2C) z{M}7!j6Ls8C~2nmL_4Y#cC}|%sW7)^VJJzWf6<8kY@Fnt?{(1Ud%nA*^QY0d$K=O^ z%!!T3zVgc^SL-a;8;7`b@A*r+()}sMK5a z!?~?p58HmiipPc~D5nUtX!DO3WRnLuf%~t;B=1wRG#mDD1x~{oy6=)#G;O@=lsHyJE^?ws|}}Ru{h8(Odq-sdWzv>eSmb;MA$J z{p06B{K$z1N`w9CL_AcG-d{bGP|?|=6HK|!3R+w-*oWGe>#gMCWP|TlxR;)J%yIlB zuIC%}q$pm0dlex%f`Smr7w~WnacOv@MGcO=dc7*w|3Q?FQSuOk*hCF|U)HW4@Mj6C zPQrbfhAK{E+Pq{CxWKeKCG|Q?|RZeJ0w$oNVi|-4m@&Wm}MK@jr!&hQR;; delta 2368 zcmZuyT}WI<6rM4g2g!H>=%gG}Xjx@0ys#Hc+gj z5=bDn^g-G_*q#?33XwjPLZRGsl~`KJKJ-CITZF#kEekCrg#;G*BBWr?+?mz2?FF6h zoH=vOnRC85TkdntKe$84o&>3UVV3>7NY$;sNY==uvWarqm+~01Nz#vaP~tS=A&Dmt zzbf%0;*7*!AZ8Nh5x*w!3gSx=KSKOqSPH+OFe33&#Fr)h1M%wu(*WX8i9?9TB;t7d!Gi7q`Y?+Yob4ETT zM9cP-uLGmU=nc3}@6}GK<{pCfHEa50KNuueNXO7$PJ=E!TKOrooDl>yb*V8M-Y`PqSE+s^H0)DurgfP72 zBdEUx_mv|Jx@~n4Hl!-q;c_Q9O0@In3?oJl?HNIT^Vv-Km}_C7MFk7}aoAKY96q6% z9uhaUv+lxdrg%2?bCD%R(&1WdEZ}PKo~@PqkF~KAr_b(q z9*2Vs#orcA_+jJ2;2W;8{lLsCK zrfiJF(s^g5pC~%g8S_|x=K4o;2{!5d~IByZxDv{r``&II8lGLzFz87ny=|hJ_ z%{fk;K}C2SWtck{Yn~Q)t&Sldbk2wH7$HIPCVY1=d>~U;z!Y_!B9smWPZbte zN>g~hcQUWa78&Dai#5QUzMe2AV9VPDqh4>#vIbM$Lo^Q`dxvQbo_d3I;~V5XLNdZC z4ryn2`|TlHQ~XQeTBruB4XhzH!UQ1 zya+$3=jj?KjnM~d%*3lFR_7|@XML-4D37sp;h9T%eir4dr4pZ|8ngX`p2bJS5`|}i z7OP9!F%nL)%Fhx@FR$(FEkALwaO6-sw~j4fG&O&?&%Kk>)#4LJVCfy&$B zEW@Vs%we%M!D$Gz{Fx)+-&LY*c9XG}r5g6Kat|OC9>N9Chm$ zatR;)TXHnLt)^lG$4#640*6xuhr)3AC}_c0<2g=SIh+i%=!B7Aj2?x%!A$LKIUxIC zBRJY%RSgsidP2A808D!#`2YKT5%@LK+%gcc;s%Z{gC+8On5ueWWe$hZ04Ocdj&&S4 z3HvfK$6|_$EyQNd?I%k(T`(9P-}YH-iO_i{g+sf%HoIP}5#d#hv_MD82UTafcUkNI zvW_-B!?;QNiW`>?#v~Y#+uP^)J${QcU#Y%FukbPHM$}FJ95b1lv1$?nZ=%}#7NhxT zHtuYq_f(bll#M??6??p44p?O8h8XSC8d5~n%@Ko>ihJX)j909xlKe%fx+O9If9o-N zv0$`rQF;c9_FkHR_uJcHp}ig+wtuVQ!zAQG-YNIx6u0O{_<}a=#8rS`V~#d~`9)gerVj Zu8v$c#2PW=IxW|tSX;$HT2(1){{U7XmW2QS 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 aea8f6390e8cf23f4bcfb057c1d83cb299130dcc..f40363bd398b418e02d5feaaa66af5ec3b27574a 100644 GIT binary patch literal 1532 zcmc(f!D|yi6vn?KTe}*;fhJH##04AKW1&=f@GxVtWY;uwLR)H)VT1ILgNG7A5xp#U z=|xaa9z1yTxB>rw^&%cUdhDg3pqL;+rGmcM-PnqX-dtGbG4tM=@BQ|@9RQ^!CePFp z{WU4DZ=aw=H_*+IJrh`B$A?Y!$=u#2`&4EZPWMN+dwB%#_X-bDc&t{2JuZMt^m4Hc z)r{vu4^WcXA?>MF-0cf6H>l8_!_vL_FzPoj>&iKyZVV$;k4OA11K$Ung9Tha+$*ZX zJ!^o%Ngloiop+T4(~|O0e&sP+dkiy@vkT5a@5)L<@o6ngLMVz!d2JXD!&#%1zxy2v zyyUCILaGS|MtPa*twq1*p^kX?6CT-K%7ea#-c%-^tu;`UwIpX*)^*Y^t!VO1tKK$t z8aa=qscj%k&74?->%^3DgwjF!oj)j73&Y#zJK*(w-O!(pmfi zi+gEbI)8XZb9;?Am6^?W&oV9`*M$UpDg!JEYjF-Xb26O@96{KVPqmaG`A_YBk>NuTgxbCTM z?&tXfqi-%r^|dIU$({MRKkoC>`y#r-P45_`u}k5Y`f)#gJINZ2( WaK^0n-;8gX_0HKBSIi1bJ^v2hgL+K> literal 680 zcmZ`%u}T9$5S@fmPa6-CLerQcfh$vqg~cxB$gOZN2MSksWG|Ivh!#PAz($Bx`HYm7 z`UffRGfr$Q1bu7vh#~P{c#pk#Gy7(CLge(Q{HmOOy;i*M4@!krX|Hf=$`Lmq?St=H zE=L939~VSvqR$%_OixrvIOYeAc+QGv$lddT8jS=seN`uxamR#~&1T7w<1|8}CAY6M z3E2|Q;G-@ykt0}gVM5CeG~f|JamWGtEIl&kf9iXv>)HAq@~%0cW&ePwA86deb4?C( zz8}{qJu9}_F??LXM`G?$?QpE;9Aycs>|otT_QEoIS@c=(RUg9*KC&@1{l 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`