diff --git a/docs/src/indexing-fuel-types/predicates.md b/docs/src/indexing-fuel-types/predicates.md index 7ba1df53e..d01f7e169 100644 --- a/docs/src/indexing-fuel-types/predicates.md +++ b/docs/src/indexing-fuel-types/predicates.md @@ -40,18 +40,72 @@ pub struct Predicate { In order to get started indexing Predicates, users will need to: 1. Create a new Predicate project using [`forc`] -2. Build this predicate project in order to get the JSON ABI. -3. Add this predicate template hash to your indexer manifest. +2. Build this predicate project using `forc build` in order to get the project JSON ABI and predicate template ID. +3. Add this predicate info to your indexer manifest. 4. Include the appropriate witness data in transactions you wish to flag to your indexer +### Sway Predicate + +```sway,ignore +predicate; + +enum PairAsset { + BTC : (), + ETH : (), +} + +impl Eq for PairAsset { + fn eq(self, other: Self) -> bool { + match (self, other) { + (PairAsset::BTC(_), PairAsset::BTC(_)) => true, + (PairAsset::ETH(_), PairAsset::ETH(_)) => true, + _ => false, + } + } +} + +struct OrderPair { + bid: PairAsset, + ask: PairAsset, +} + +impl Eq for OrderPair { + fn eq(self, other: Self) -> bool { + self.bid == other.bid && self.ask == other.ask + } +} + +configurable { + AMOUNT: u64 = 1u64, + PAIR: OrderPair = OrderPair { bid: PairAsset::BTC, ask: PairAsset::ETH }, +} + +fn main( + amount: u64, + pair: OrderPair, +) -> bool { + amount == AMOUNT && pair == PAIR +} +``` + +### Predicate Indexer + ```rust,ignore extern crate alloc; use fuel_indexer_utils::prelude::*; #[indexer(manifest = "indexer.manifest.yaml")] mod indexer_mod { - fn handle_spent_predicates(predicate: Predicate, configurables: MyPredicateConfigurables) { - unimplemented!() + fn handle_spent_predicates(predicates: Predicates, configurables: MyPredicateInputs) { + let template_id = "0xb16545fd38b82ab5178d79c71ad0ce54712cbdcee22f722b08db278e77d1bcbc"; + if let Some(predicate) = predicates.get(template_id) { + match configurables.pair.bid { + OrderPair::BTC => info!("Someone wants to give BTC"), + OrderPair::ETH => info!("Someone wants to get ETH"), + } + } + + info!("No predicates for this indexer found in the transaction"); } } ``` diff --git a/packages/fuel-indexer-macros/src/helpers.rs b/packages/fuel-indexer-macros/src/helpers.rs index 0f3f03115..4a339ca49 100644 --- a/packages/fuel-indexer-macros/src/helpers.rs +++ b/packages/fuel-indexer-macros/src/helpers.rs @@ -84,6 +84,12 @@ pub fn is_tuple_type(typ: &TypeDeclaration) -> bool { && type_field_chars.next().is_some_and(|c| c != ')') } +/// Whether a `TypeDeclaration` is a copy-able type. +pub fn is_copy_type(typ: &TypeDeclaration) -> bool { + let name = typ.type_field.split_whitespace().next().unwrap(); + name == "struct" || name == "enum" +} + /// Whether a `TypeDeclaration` is a unit type pub fn is_unit_type(typ: &TypeDeclaration) -> bool { let mut type_field_chars = typ.type_field.chars(); @@ -200,6 +206,7 @@ pub fn decode_snippet( if ty_id == Predicates::type_id() { quote! { #ty_id => { + // FIXME: Inject template ID here!!! let obj: Predicate = bincode::deserialize(&data).expect("Bad bincode."); self.#name.add("123".to_string(), obj); } @@ -1257,7 +1264,7 @@ pub fn configurable_fn_type_name(configurable: &Configurable) -> Option } /// Derive a mapping of input type IDs to their corresponding names for a given set of predicates. -pub fn derive_configurables_names_map(manifest: &Manifest) -> HashMap { +pub fn predicate_inputs_names_map(manifest: &Manifest) -> HashMap { let mut output = HashMap::new(); if let Some(predicate) = manifest.predicates() { if predicate.is_empty() { @@ -1283,12 +1290,12 @@ pub fn derive_configurables_names_map(manifest: &Manifest) -> HashMap String { - format!("{}IndexerConfigurables", to_pascal_case(template_name)) +/// Derive the name of the set of inputs for this predicate template. +pub fn predicate_inputs_name(template_name: &str) -> String { + format!("{}Inputs", to_pascal_case(template_name)) } -/// Given a template name, derive the name of the corresponding `fuels-rs` `Configurables` type. +/// Derive the name of the set of configurables for this predicate template. pub fn sdk_configurables_name(template_name: &str) -> String { format!("{}Configurables", to_pascal_case(template_name)) } diff --git a/packages/fuel-indexer-macros/src/indexer.rs b/packages/fuel-indexer-macros/src/indexer.rs index 6840e9b0d..29cdb9810 100644 --- a/packages/fuel-indexer-macros/src/indexer.rs +++ b/packages/fuel-indexer-macros/src/indexer.rs @@ -63,7 +63,7 @@ fn process_fn_items( t.iter() .map(|t| { let ty_id = type_id(FUEL_TYPES_NAMESPACE, &t.name()) as usize; - let name = indexer_configurables_name(&t.name()); + let name = predicate_inputs_name(&t.name()); TypeDeclaration { type_id: ty_id, type_field: name, @@ -853,7 +853,7 @@ pub fn process_indexer_module(attrs: TokenStream, item: TokenStream) -> TokenStr let predicate_impl_tokens = predicate_entity_impl_tokens(); - let indexer_configurables_tokens = indexer_configurables_tokens(&manifest); + let predicate_inputs_tokens = predicate_inputs_tokens(&manifest); let (block, fn_items) = process_fn_items(&manifest, indexer_module); let block = handler_block(block, fn_items); @@ -863,7 +863,7 @@ pub fn process_indexer_module(attrs: TokenStream, item: TokenStream) -> TokenStr #graphql_tokens - #indexer_configurables_tokens + #predicate_inputs_tokens #predicate_impl_tokens diff --git a/packages/fuel-indexer-macros/src/tokens.rs b/packages/fuel-indexer-macros/src/tokens.rs index 9a84dbb2d..096835950 100644 --- a/packages/fuel-indexer-macros/src/tokens.rs +++ b/packages/fuel-indexer-macros/src/tokens.rs @@ -1,6 +1,6 @@ /// Various functions that generate TokenStreams used to augment the indexer ASTs. use crate::helpers::*; -use fuel_abi_types::abi::program::{Configurable, TypeDeclaration}; +use fuel_abi_types::abi::program::TypeDeclaration; use fuel_indexer_lib::manifest::Manifest; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -432,7 +432,7 @@ pub fn configurables_match_tokens(manifest: &Manifest) -> Vec { t.iter() .map(|t| { let template_id = t.id().to_string(); - let name = indexer_configurables_name(&t.name()); + let name = predicate_inputs_name(&t.name()); let ident = format_ident! { "{}", name }; // NOTE: This decoding is used to derive the indexer configurable so that we can further use it @@ -460,12 +460,12 @@ pub fn predicate_verification_tokens(manifest: &Manifest) -> Vec { return output; } - let configurables_namings = derive_configurables_names_map(manifest); + let input_names = predicate_inputs_names_map(manifest); if let Some(templates) = predicates.templates() { for template in templates { let indexer_variant_name = - format_ident! { "{}", indexer_configurables_name(&template.name()) }; + format_ident! { "{}", predicate_inputs_name(&template.name()) }; let sdk_variant_name = format_ident! { "{}", sdk_configurables_name(&template.name()) }; @@ -474,6 +474,12 @@ pub fn predicate_verification_tokens(manifest: &Manifest) -> Vec { let abi = get_json_abi(Some(template.abi())) .expect("Could not derive predicate JSON ABI."); + let abi_types = abi + .types + .iter() + .map(|typ| (typ.type_id, typ.clone())) + .collect::>(); + let main = abi .functions .iter() @@ -485,9 +491,11 @@ pub fn predicate_verification_tokens(manifest: &Manifest) -> Vec { .iter() .map(|i| { let ty_id = i.type_id; - let name = configurables_namings + let name = input_names .get(&ty_id) .expect("Could not find configurable naming."); + let name = format_ident! {"{}", name }; + format_ident! {"{}", name } }) .collect::>(); @@ -495,15 +503,25 @@ pub fn predicate_verification_tokens(manifest: &Manifest) -> Vec { let chained_functions = abi.configurables.iter().flat_map(|configurables| { configurables.iter().map(|c| { + let typ = abi_types.get(&c.application.type_id).expect( + "Predicate configurable type not found in the ABI.", + ); + + let clone = if is_copy_type(typ) { + quote! { .clone() } + } else { + quote! {} + }; + let ty_name = configurable_fn_type_name(c) .expect("Cannot use unit types '()' in configurables."); - let arg = configurables_namings + let arg = input_names .get(&c.application.type_id) .expect("Could not find configurable naming."); let arg = format_ident! {"{}", arg }; let fn_name = format_ident! { "with_{}", ty_name }; quote! { - .#fn_name(#arg) + .#fn_name(#arg #clone) } }) }); @@ -532,17 +550,13 @@ pub fn predicate_verification_tokens(manifest: &Manifest) -> Vec { output } -/// `TokenStream` used to generate a set of indexer-specific configurables (not to be confused -/// with `fuels-rs` configurables). -/// -/// While `fuels-rs` configurables are used in the predicate verification process, `fuel-indexer` -/// configurables are passed to indexer handlers and made available to users. -pub fn indexer_configurables_tokens(manifest: &Manifest) -> TokenStream { +/// `TokenStream` used to generate a set of indexer-specific predicate inputs. +pub fn predicate_inputs_tokens(manifest: &Manifest) -> TokenStream { let predicates = manifest.predicates(); let mut output = quote! { - trait ConfigurableDecoder { - fn decode_type(&self, ty_id: usize, data: Vec); + trait InputDecoder { + fn decode_type(&self, ty_id: usize, data: Vec) -> anyhow::Result<()>; } }; @@ -551,9 +565,9 @@ pub fn indexer_configurables_tokens(manifest: &Manifest) -> TokenStream { return output; } - let configurable_namings = derive_configurables_names_map(manifest); + let configurable_namings = predicate_inputs_names_map(manifest); - let mut configurable_variants = Vec::new(); + let mut input_variants = Vec::new(); let names = p .templates() @@ -573,23 +587,13 @@ pub fn indexer_configurables_tokens(manifest: &Manifest) -> TokenStream { .find(|f| f.name == "main") .expect("Could not find main function in predicate ABI."); - let configurable_types = abi - .configurables - .iter() - .flat_map(|configurables| { - configurables - .iter() - .map(|c| (c.application.type_id, c.clone())) - }) - .collect::>(); - let predicate_types = abi .types .iter() .map(|typ| (typ.type_id, typ.clone())) .collect::>(); - let predicate_input_fields = main + let input_fields = main .inputs .iter() .map(|i| { @@ -608,20 +612,9 @@ pub fn indexer_configurables_tokens(manifest: &Manifest) -> TokenStream { }) .collect::>(); - let config_set_decoders = configurable_types - .keys() - .map(|ty_id| { - let typ = predicate_types.get(ty_id).expect( - "Could not get configurable type reference from ABI types.", - ); - let type_tokens = typ.rust_tokens(); - decode_snippet(&type_tokens, typ) - }) - .collect::>(); - - let name = indexer_configurables_name(&name); + let name = predicate_inputs_name(&name); let ident = format_ident! { "{}", name }; - configurable_variants.push(ident.clone()); + input_variants.push(ident.clone()); output = quote! { #output @@ -629,7 +622,7 @@ pub fn indexer_configurables_tokens(manifest: &Manifest) -> TokenStream { #[derive(Debug, Clone)] pub struct #ident { pub id: String, - #(#predicate_input_fields),* + #(#input_fields),* } impl #ident { @@ -637,24 +630,15 @@ pub fn indexer_configurables_tokens(manifest: &Manifest) -> TokenStream { // TODO: Should document the standardization of predicate witness data pub fn new(data: Vec) -> Self { - // FIXME: This will have to be a part of the codegen - + // We need to: + // 1. chunk each piece (ty_id, len, and data) + // 2. Use SDK to decode data into T + // 3. When finished create Self { } using all T's todo!("Finish!") } } - impl ConfigurableDecoder for #ident { - fn decode_type(&self, ty_id: usize, data: Vec) { - match ty_id { - #(#config_set_decoders)* - _ => { - panic!("Could not find type with ID: {}", ty_id); - } - } - } - } - impl TypeId for #ident { fn type_id() -> usize { type_id(FUEL_TYPES_NAMESPACE, #name) as usize @@ -667,7 +651,7 @@ pub fn indexer_configurables_tokens(manifest: &Manifest) -> TokenStream { #output enum ConfigurablesContainer { - #(#configurable_variants(#configurable_variants)),* + #(#input_variants(#input_variants)),* } }; }; 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 c08b50711..037f7302c 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 @@ -17,5 +17,5 @@ predicates: abi: packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-abi.json id: 0xcfd60aa414972babde16215e0cb5a2739628831405a7ae81a9fc1d2ebce87145 - name: TestPredicate2 - id: 0x1c83e1f094b47f14943066f6b6ca41ce5c3ae4e387c973e924564dac0227a896 + id: 0xb16545fd38b82ab5178d79c71ad0ce54712cbdcee22f722b08db278e77d1bcbc abi: packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json \ No newline at end of file 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 4390c4357..7d993a4cf 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 @@ -585,7 +585,7 @@ mod fuel_indexer_test { fn fuel_indexer_test_predicates( _predicates: Predicates, - _configurables: TestPredicate1IndexerConfigurables, + _inputs: TestPredicate1Inputs, ) { info!("fuel_indexer_test_predicates handling trigger_predicates event"); } diff --git a/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json index 03272ec7b..bf8babc36 100644 --- a/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json +++ b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json @@ -14,15 +14,15 @@ }, { "typeId": 2, - "type": "enum Predicate2SimpleEnum", + "type": "enum PairAsset", "components": [ { - "name": "VariantOne", + "name": "BTC", "type": 0, "typeArguments": null }, { - "name": "VariantTwo", + "name": "ETH", "type": 0, "typeArguments": null } @@ -31,16 +31,16 @@ }, { "typeId": 3, - "type": "struct Predicate2SimpleStruct", + "type": "struct OrderPair", "components": [ { - "name": "field_1", - "type": 5, + "name": "bid", + "type": 2, "typeArguments": null }, { - "name": "field_2", - "type": 4, + "name": "ask", + "type": 2, "typeArguments": null } ], @@ -51,36 +51,20 @@ "type": "u64", "components": null, "typeParameters": null - }, - { - "typeId": 5, - "type": "u8", - "components": null, - "typeParameters": null } ], "functions": [ { "inputs": [ { - "name": "u_8", - "type": 5, - "typeArguments": null - }, - { - "name": "switch", - "type": 1, + "name": "amount", + "type": 4, "typeArguments": null }, { - "name": "some_struct", + "name": "pair", "type": 3, "typeArguments": null - }, - { - "name": "some_enum", - "type": 2, - "typeArguments": null } ], "name": "main", @@ -96,22 +80,22 @@ "messagesTypes": [], "configurables": [ { - "name": "U8", + "name": "AMOUNT", "configurableType": { "name": "", - "type": 5, + "type": 4, "typeArguments": null }, - "offset": 124 + "offset": 284 }, { - "name": "BOOL", + "name": "PAIR", "configurableType": { "name": "", - "type": 1, - "typeArguments": null + "type": 3, + "typeArguments": [] }, - "offset": 132 + "offset": 292 } ] } \ No newline at end of file diff --git a/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-bin-root b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-bin-root index 41ae03b7b..e1bbcf0f5 100644 --- a/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-bin-root +++ b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-bin-root @@ -1 +1 @@ -0x048029a18e3254862e2d4108fc9a9dcdcd2f71e0c6e37dad8f1b513845c8e685 \ No newline at end of file +0xb16545fd38b82ab5178d79c71ad0ce54712cbdcee22f722b08db278e77d1bcbc \ No newline at end of file diff --git a/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2.bin b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2.bin index 588bcfa96..fc0d30a5f 100644 Binary files a/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2.bin and b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2.bin differ diff --git a/packages/fuel-indexer-tests/sway/test-predicate2/src/main.sw b/packages/fuel-indexer-tests/sway/test-predicate2/src/main.sw index 11040f327..066a316b5 100644 --- a/packages/fuel-indexer-tests/sway/test-predicate2/src/main.sw +++ b/packages/fuel-indexer-tests/sway/test-predicate2/src/main.sw @@ -1,31 +1,39 @@ predicate; -#[allow(dead_code)] -enum Predicate2SimpleEnum { - VariantOne : (), - VariantTwo : (), +enum PairAsset { + BTC : (), + ETH : (), } -struct Predicate2SimpleStruct { - field_1: u8, - field_2: u64, +impl Eq for PairAsset { + fn eq(self, other: Self) -> bool { + match (self, other) { + (PairAsset::BTC(_), PairAsset::BTC(_)) => true, + (PairAsset::ETH(_), PairAsset::ETH(_)) => true, + _ => false, + } + } +} + +struct OrderPair { + bid: PairAsset, + ask: PairAsset, +} + +impl Eq for OrderPair { + fn eq(self, other: Self) -> bool { + self.bid == other.bid && self.ask == other.ask + } } configurable { - U8: u8 = 8u8, - BOOL: bool = true, - STRUCT: Predicate2SimpleStruct = Predicate2SimpleStruct { -field_1: 8, -field_2: 16, - }, - ENUM: Predicate2SimpleEnum = Predicate2SimpleEnum::VariantOne, + AMOUNT: u64 = 1u64, + PAIR: OrderPair = OrderPair { bid: PairAsset::BTC, ask: PairAsset::ETH }, } fn main( - u_8: u8, - switch: bool, - some_struct: Predicate2SimpleStruct, - some_enum: Predicate2SimpleEnum, + amount: u64, + pair: OrderPair, ) -> bool { - u_8 == U8 && switch == BOOL + amount == AMOUNT && pair == PAIR }