From 201a555d1333bd46413dbb3268829a61aac462cb Mon Sep 17 00:00:00 2001 From: Rashad Alston Date: Tue, 26 Sep 2023 16:47:31 -0400 Subject: [PATCH] enhancement: add predicate support --- .markdownlint.yaml | 1 + Cargo.lock | 280 ++++-- Cargo.toml | 1 + ci/Dockerfile.fuel-node | 4 +- clippy.toml | 1 + docs/src/SUMMARY.md | 2 + docs/src/indexing-custom-types/index.md | 29 +- docs/src/indexing-fuel-types/index.md | 1 + .../predicate-witness-data.md | 1 + docs/src/indexing-fuel-types/predicates.md | 113 +++ docs/src/indexing-fuel-types/receipts.md | 3 + docs/src/project-components/manifest.md | 62 +- .../fuel-explorer/fuel_explorer.manifest.yaml | 22 +- .../greetings_indexer.manifest.yaml | 25 +- .../hello-world/hello_world.manifest.yaml | 24 +- packages/fuel-indexer-api-server/src/uses.rs | 2 +- .../benches/wasm/examples/fuel_explorer.rs | 9 +- packages/fuel-indexer-benchmarks/src/lib.rs | 6 +- packages/fuel-indexer-lib/Cargo.toml | 2 +- packages/fuel-indexer-lib/src/constants.rs | 26 +- packages/fuel-indexer-lib/src/graphql/mod.rs | 22 +- packages/fuel-indexer-lib/src/manifest.rs | 201 ++-- packages/fuel-indexer-macros/Cargo.toml | 3 +- packages/fuel-indexer-macros/src/decoder.rs | 40 +- packages/fuel-indexer-macros/src/helpers.rs | 135 ++- packages/fuel-indexer-macros/src/indexer.rs | 745 ++++++-------- packages/fuel-indexer-macros/src/lib.rs | 1 + packages/fuel-indexer-macros/src/tokens.rs | 856 ++++++++++++++++ packages/fuel-indexer-macros/src/wasm.rs | 35 +- packages/fuel-indexer-plugin/Cargo.toml | 2 + packages/fuel-indexer-schema/src/lib.rs | 4 +- .../components/fuel-node/src/main.rs | 10 +- .../contracts/fuel-indexer-test/Forc.lock | 13 - .../fuel-indexer-test/fuel_indexer_test.yaml | 18 +- .../indexers/fuel-indexer-test/src/lib.rs | 18 +- .../indexers/simple-wasm/simple_wasm.yaml | 10 +- packages/fuel-indexer-tests/src/fixtures.rs | 38 +- packages/fuel-indexer-tests/src/lib.rs | 112 +-- .../{contracts => sway}/README.md | 0 .../simple-wasm/.gitignore | 0 .../{contracts => sway}/simple-wasm/Forc.lock | 0 .../{contracts => sway}/simple-wasm/Forc.toml | 0 .../simple-wasm/out/debug/contracts-abi.json | 0 .../out/debug/contracts-flat-abi.json | 87 ++ .../out/debug/contracts-storage_slots.json | 0 .../simple-wasm/out/debug/contracts.bin | Bin .../simple-wasm/src/main.sw | 0 .../sway/test-contract1/.gitignore | 2 + .../sway/test-contract1/Forc.lock | 13 + .../test-contract1}/Forc.toml | 2 +- .../out/debug/test-contract1-abi.json | 923 ++++++++++++++++++ .../debug/test-contract1-storage_slots.json} | 0 .../out/debug/test-contract1.bin | Bin 0 -> 5812 bytes .../test-contract1}/src/main.sw | 0 .../sway/test-predicate1/.gitignore | 2 + .../sway/test-predicate1/Forc.lock | 13 + .../sway/test-predicate1/Forc.toml | 7 + .../out/debug/test-predicate1-abi.json | 117 +++ .../out/debug/test-predicate1-bin-root | 1 + .../out/debug/test-predicate1.bin | Bin 0 -> 140 bytes .../sway/test-predicate1/src/main.sw | 31 + .../sway/test-predicate2/.gitignore | 2 + .../sway/test-predicate2/Forc.lock | 13 + .../sway/test-predicate2/Forc.toml | 7 + .../out/debug/test-predicate2-abi.json | 101 ++ .../out/debug/test-predicate2-bin-root | 1 + .../out/debug/test-predicate2.bin | Bin 0 -> 332 bytes .../sway/test-predicate2/src/main.sw | 39 + packages/fuel-indexer-tests/tests/indexing.rs | 2 +- packages/fuel-indexer-tests/tests/service.rs | 12 +- packages/fuel-indexer-tests/tests/trybuild.rs | 39 +- ...bute_manifest_schema_arg_is_invalid.stderr | 2 +- .../invalid_abi_type_simple_wasm.yaml | 8 - .../trybuild/invalid_schema_simple_wasm.yaml | 9 - packages/fuel-indexer-types/Cargo.toml | 2 + packages/fuel-indexer-types/src/fuel.rs | 40 +- packages/fuel-indexer-types/src/graphql.rs | 35 - packages/fuel-indexer-types/src/indexer.rs | 430 ++++++++ packages/fuel-indexer-types/src/lib.rs | 15 +- packages/fuel-indexer-types/src/scalar.rs | 18 +- packages/fuel-indexer/src/README.md | 22 - packages/fuel-indexer/src/executor.rs | 2 +- packages/fuel-indexer/src/service.rs | 2 +- plugins/forc-index/src/defaults.rs | 27 +- .../forc-index/src/ops/forc_index_build.rs | 2 +- .../forc-index/src/ops/forc_index_deploy.rs | 2 +- plugins/forc-postgres/src/pg.rs | 2 +- scripts/utils/README.md | 23 - 88 files changed, 3864 insertions(+), 1068 deletions(-) create mode 100644 clippy.toml create mode 100644 docs/src/indexing-fuel-types/predicate-witness-data.md create mode 100644 docs/src/indexing-fuel-types/predicates.md create mode 100644 packages/fuel-indexer-macros/src/tokens.rs delete mode 100644 packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock rename packages/fuel-indexer-tests/{contracts => sway}/README.md (100%) rename packages/fuel-indexer-tests/{contracts => sway}/simple-wasm/.gitignore (100%) rename packages/fuel-indexer-tests/{contracts => sway}/simple-wasm/Forc.lock (100%) rename packages/fuel-indexer-tests/{contracts => sway}/simple-wasm/Forc.toml (100%) rename packages/fuel-indexer-tests/{contracts => sway}/simple-wasm/out/debug/contracts-abi.json (100%) create mode 100644 packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-flat-abi.json rename packages/fuel-indexer-tests/{contracts => sway}/simple-wasm/out/debug/contracts-storage_slots.json (100%) rename packages/fuel-indexer-tests/{contracts => sway}/simple-wasm/out/debug/contracts.bin (100%) rename packages/fuel-indexer-tests/{contracts => sway}/simple-wasm/src/main.sw (100%) create mode 100644 packages/fuel-indexer-tests/sway/test-contract1/.gitignore create mode 100644 packages/fuel-indexer-tests/sway/test-contract1/Forc.lock rename packages/fuel-indexer-tests/{contracts/fuel-indexer-test => sway/test-contract1}/Forc.toml (77%) create mode 100644 packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json rename packages/fuel-indexer-tests/{contracts/fuel-indexer-test/out/debug/fuel-indexer-test-storage_slots.json => sway/test-contract1/out/debug/test-contract1-storage_slots.json} (100%) create mode 100644 packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1.bin rename packages/fuel-indexer-tests/{contracts/fuel-indexer-test => sway/test-contract1}/src/main.sw (100%) create mode 100644 packages/fuel-indexer-tests/sway/test-predicate1/.gitignore create mode 100644 packages/fuel-indexer-tests/sway/test-predicate1/Forc.lock create mode 100644 packages/fuel-indexer-tests/sway/test-predicate1/Forc.toml create mode 100644 packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-abi.json create mode 100644 packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-bin-root create mode 100644 packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1.bin create mode 100644 packages/fuel-indexer-tests/sway/test-predicate1/src/main.sw create mode 100644 packages/fuel-indexer-tests/sway/test-predicate2/.gitignore create mode 100644 packages/fuel-indexer-tests/sway/test-predicate2/Forc.lock create mode 100644 packages/fuel-indexer-tests/sway/test-predicate2/Forc.toml create mode 100644 packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json create mode 100644 packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-bin-root create mode 100644 packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2.bin create mode 100644 packages/fuel-indexer-tests/sway/test-predicate2/src/main.sw delete mode 100644 packages/fuel-indexer-tests/trybuild/invalid_abi_type_simple_wasm.yaml delete mode 100644 packages/fuel-indexer-tests/trybuild/invalid_schema_simple_wasm.yaml delete mode 100644 packages/fuel-indexer-types/src/graphql.rs create mode 100644 packages/fuel-indexer-types/src/indexer.rs delete mode 100644 scripts/utils/README.md diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 21909e1ab..7ba6fcce2 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -1,3 +1,4 @@ "default": true # Default state for all rules "MD013": false # Disable rule for line length "MD033": false # Disable rule banning inline HTML +"MD024": false # Disable duplicate headers diff --git a/Cargo.lock b/Cargo.lock index 02e1d808c..07afdac4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,21 +414,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] +[[package]] +name = "async-channel" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37875bd9915b7d67c2f117ea2c30a0989874d0b2cb694fe25403c85763c0c9e" +dependencies = [ + "concurrent-queue", + "event-listener 3.1.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-executor" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0" +checksum = "fc5ea910c42e5ab19012bab31f53cb4d63d54c3a27730f9a833a88efcf4bb52d" dependencies = [ - "async-lock", + "async-lock 3.1.1", "async-task", "concurrent-queue", "fastrand 2.0.1", - "futures-lite", + "futures-lite 2.0.1", "slab", ] @@ -438,12 +451,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", "async-io", - "async-lock", + "async-lock 2.8.0", "blocking", - "futures-lite", + "futures-lite 1.13.0", "once_cell", ] @@ -616,11 +629,11 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", - "futures-lite", + "futures-lite 1.13.0", "log", "parking", "polling", @@ -636,7 +649,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105" +dependencies = [ + "event-listener 3.1.0", + "event-listener-strategy", + "pin-project-lite", ] [[package]] @@ -645,15 +669,15 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-global-executor", "async-io", - "async-lock", + "async-lock 2.8.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite", + "futures-lite 1.13.0", "gloo-timers", "kv-log-macro", "log", @@ -1094,16 +1118,16 @@ dependencies = [ [[package]] name = "blocking" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ - "async-channel", - "async-lock", + "async-channel 2.1.0", + "async-lock 3.1.1", "async-task", "fastrand 2.0.1", "futures-io", - "futures-lite", + "futures-lite 2.0.1", "piper", "tracing", ] @@ -1349,9 +1373,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive 4.4.7", @@ -1359,9 +1383,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", @@ -1444,7 +1468,7 @@ dependencies = [ "coins-core 0.8.7", "digest 0.10.7", "hmac", - "k256 0.13.1", + "k256 0.13.2", "serde", "sha2 0.10.8", "thiserror", @@ -1789,7 +1813,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.7", + "clap 4.4.8", "criterion-plot", "futures", "is-terminal", @@ -1887,9 +1911,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core", @@ -2283,15 +2307,15 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der 0.7.8", "digest 0.10.7", - "elliptic-curve 0.13.6", + "elliptic-curve 0.13.8", "rfc6979 0.4.0", - "signature 2.1.0", + "signature 2.2.0", "spki 0.7.2", ] @@ -2301,18 +2325,19 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "signature 2.1.0", + "signature 2.2.0", ] [[package]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek", "ed25519", "sha2 0.10.8", + "subtle", ] [[package]] @@ -2358,12 +2383,12 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.6" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct 0.2.0", - "crypto-bigint 0.5.3", + "crypto-bigint 0.5.5", "digest 0.10.7", "ff 0.13.0", "generic-array 0.14.7", @@ -2478,9 +2503,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys 0.48.0", @@ -2547,6 +2572,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160" +dependencies = [ + "event-listener 3.1.0", + "pin-project-lite", +] + [[package]] name = "eventsource-client" version = "0.10.2" @@ -2631,9 +2677,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69037fe1b785e84986b4f2cbcf647381876a00671d25ceef715d7812dd7e1dd" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" [[package]] name = "filecheck" @@ -2756,7 +2802,7 @@ checksum = "7e128b6278387c3b95103b292cfc311e213a21b3178ee3a63ee505876e02d0a1" dependencies = [ "ansi_term 0.12.1", "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", ] [[package]] @@ -2767,7 +2813,7 @@ checksum = "15a5505631d4b9deff14b19823372388c861edbc6dccebabe899bbf5b08523b1" dependencies = [ "ansi_term 0.12.1", "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", ] [[package]] @@ -2788,7 +2834,7 @@ dependencies = [ "sway-types", "sway-utils", "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", "unicode-xid", ] @@ -2875,7 +2921,7 @@ dependencies = [ "async-graphql 4.0.16", "async-trait", "axum 0.5.17", - "clap 4.4.7", + "clap 4.4.8", "derive_more", "enum-iterator 1.4.1", "fuel-core-chain-config", @@ -2909,7 +2955,7 @@ dependencies = [ "tokio-stream", "tower-http", "tracing", - "uuid 1.5.0", + "uuid 1.6.1", ] [[package]] @@ -3147,7 +3193,7 @@ dependencies = [ "borrown", "coins-bip32 0.8.7", "coins-bip39 0.8.7", - "ecdsa 0.16.8", + "ecdsa 0.16.9", "ed25519-dalek", "fuel-types 0.35.4", "lazy_static", @@ -3376,16 +3422,16 @@ dependencies = [ "clap 3.2.25", "fuel-indexer-types", "http", + "inflections", "lazy_static", "serde", - "serde_json", "serde_yaml", "sha2 0.9.9", "strum", "thiserror", "tokio", "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", "url", ] @@ -3412,7 +3458,8 @@ dependencies = [ "fuel-indexer-schema", "fuel-indexer-types", "fuels", - "fuels-code-gen 0.46.0", + "fuels-code-gen 0.50.1", + "inflections", "lazy_static", "proc-macro-error", "proc-macro2", @@ -3443,6 +3490,8 @@ dependencies = [ "fuel-indexer-types", "getrandom", "hex", + "proc-macro2", + "quote", "serde", "serde_json", "sha2 0.10.8", @@ -3461,7 +3510,7 @@ dependencies = [ "fuel-indexer-metrics", "sqlx", "tracing", - "uuid 1.5.0", + "uuid 1.6.1", ] [[package]] @@ -3546,6 +3595,8 @@ dependencies = [ name = "fuel-indexer-types" version = "0.23.0" dependencies = [ + "async-trait", + "bincode", "bytes", "fuel-tx 0.35.4", "fuel-types 0.35.4", @@ -3754,7 +3805,7 @@ checksum = "905e1b22d5c7b6ab01f05285ea61cb7e15cdcce762263db2019c192213b03c53" dependencies = [ "async-trait", "chrono", - "elliptic-curve 0.13.6", + "elliptic-curve 0.13.8", "eth-keystore", "fuel-core-client", "fuel-crypto 0.35.4", @@ -3990,6 +4041,20 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-lite" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" +dependencies = [ + "fastrand 2.0.1", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.29" @@ -4184,9 +4249,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -4194,7 +4259,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", @@ -4385,9 +4450,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -4495,7 +4560,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.8", + "rustls 0.21.9", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -4673,6 +4738,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + [[package]] name = "inout" version = "0.1.3" @@ -4729,7 +4800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.3", - "rustix 0.38.21", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -4803,16 +4874,16 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" dependencies = [ "cfg-if", - "ecdsa 0.16.8", - "elliptic-curve 0.13.6", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "once_cell", "sha2 0.10.8", - "signature 2.1.0", + "signature 2.2.0", ] [[package]] @@ -5365,8 +5436,8 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa 0.16.8", - "elliptic-curve 0.13.6", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "primeorder", "sha2 0.10.8", ] @@ -5837,11 +5908,11 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.13.3" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7dbe9ed3b56368bd99483eb32fe9c17fdd3730aebadc906918ce78d54c7eeb4" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "elliptic-curve 0.13.6", + "elliptic-curve 0.13.8", ] [[package]] @@ -6232,7 +6303,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.8", + "rustls 0.21.9", "rustls-pemfile", "serde", "serde_json", @@ -6326,7 +6397,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.5.0", + "uuid 1.6.1", ] [[package]] @@ -6403,9 +6474,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", @@ -6441,9 +6512,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", "ring 0.17.5", @@ -6677,9 +6748,9 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6" +checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" [[package]] name = "semver" @@ -6916,9 +6987,9 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core", @@ -6992,9 +7063,9 @@ checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "snafu" @@ -7132,7 +7203,7 @@ dependencies = [ "dirs 4.0.0", "dotenvy", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -7494,15 +7565,15 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.21", + "rustix 0.38.25", "windows-sys 0.48.0", ] [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -7705,7 +7776,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.8", + "rustls 0.21.9", "tokio", ] @@ -7913,6 +7984,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -7941,15 +8023,15 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.4", "tracing-serde", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers 0.1.0", "nu-ansi-term", @@ -7962,7 +8044,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-serde", ] @@ -8140,9 +8222,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom", ] @@ -8567,7 +8649,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.21", + "rustix 0.38.25", ] [[package]] @@ -8888,18 +8970,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.25" +version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.25" +version = "0.7.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" dependencies = [ "proc-macro2", "quote", @@ -8908,9 +8990,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 043583342..869a12273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ fuel-tx = { version = "=0.35.4", default-features = false } fuel-types = { version = "=0.35.4", default-features = false, features = ["serde"] } fuel-vm = { version = "=0.35.4", default-features = false } fuels = { version = "0.50", default-features = false } +fuels-code-gen = { version = "0.50", default-features = false } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false } thiserror = "1.0" diff --git a/ci/Dockerfile.fuel-node b/ci/Dockerfile.fuel-node index c45ec95ad..97c51a982 100644 --- a/ci/Dockerfile.fuel-node +++ b/ci/Dockerfile.fuel-node @@ -51,5 +51,5 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update -y \ COPY --from=builder /build/target/release/fuel-node . COPY --from=builder /build/target/release/fuel-node.d . COPY --from=builder /build/packages/fuel-indexer-tests/test-chain-config.json . -COPY --from=builder /build/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test.bin . -COPY --from=builder /build/examples/greetings/contracts/greeting/out/debug/greeting.bin . +COPY --from=builder /build/packages/fuel-indexer-tests/sway/fuel-indexer-test/out/debug/fuel-indexer-test.bin . +COPY --from=builder /build/examples/hello-world/contracts/greeting/out/debug/greeting.bin . diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..f09ac0fb5 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.73.0" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 970c27264..dd6fe0e43 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -25,6 +25,8 @@ - [Blocks](./indexing-fuel-types/blocks.md) - [Transactions](./indexing-fuel-types/transactions.md) - [Receipts](./indexing-fuel-types/receipts.md) + - [Predicates](./indexing-fuel-types/predicates.md) + - [Predicate Witness Data](./indexing-fuel-types/predicate-witness-data.md) - [Indexing Custom Types](./indexing-custom-types/index.md) - [Storing Records](./storing-records/index.md) - [Querying](./querying/index.md) diff --git a/docs/src/indexing-custom-types/index.md b/docs/src/indexing-custom-types/index.md index c80c72981..10e12f041 100644 --- a/docs/src/indexing-custom-types/index.md +++ b/docs/src/indexing-custom-types/index.md @@ -171,19 +171,25 @@ Next, we'll cover how to write the manifest file for your indexer. Before writing any of the handler code for your indexer, we need to make sure that our indexer manifest contains the necessary information to allow for the compiler to parse our contract types. -Specifically, we should ensure that the `contract_abi` and `graphql_schema` fields point to the correct locations, respectively. +Specifically, we should ensure that the `contract_abi` and `schema` fields point to the correct locations, respectively. ```yaml # A namespace is a logical grouping of declared names. Think of the namespace # as an organization identifier namespace: fuellabs -# The identifier field is used to identify the given index. +# Unique identifier for this indexer. identifier: custom_types_example -# The abi option is used to provide a link to the Sway JSON ABI that is generated when you -# build your project. -abi: path/to/custom/type/example/contract-abi.json +# Indexer contract configuration. +contract: ~ + + # File paths to the contract JSON ABIs that are generated when you build your Sway contracts. + abis: ~ + + # Specifies which particular contracts you would like your indexer to subscribe to. + subscriptions: ~ + # The particular start block after which you'd like your indexer to start indexing events. start_block: ~ @@ -197,12 +203,11 @@ end_block: ~ # with the `--indexer_net_config` option. fuel_client: ~ -# The contract_id specifies which particular contract you would like your index to subscribe to. +# Specifies which particular contract(s) you would like your indexer to subscribe to. contract_id: ~ -# The graphql_schema field contains the file path that points to the GraphQL schema for the -# given index. -graphql_schema: path/to/custom/type/example/indexer.schema.graphql +# A file path that points to the GraphQL schema for the given indexer. +schema: path/to/custom/type/example/indexer.schema.graphql # The module field contains a file path that points to code that will be run as an executor inside # of the indexer. @@ -213,6 +218,12 @@ module: # The resumable field contains a boolean that specifies whether or not the indexer should, synchronise # with the latest block if it has fallen out of sync. resumable: true + +# Indexer predicate configuration. +predicates: + + # Template commitments (hashes) of the bytecode of predicates used by this indexer. + templates: ~ ``` ## 4. Handler Logic diff --git a/docs/src/indexing-fuel-types/index.md b/docs/src/indexing-fuel-types/index.md index c3646b0a2..b05eefd20 100644 --- a/docs/src/indexing-fuel-types/index.md +++ b/docs/src/indexing-fuel-types/index.md @@ -5,3 +5,4 @@ This document provides information about Fuel-specific types and provides exampl - [Blocks](./blocks.md) - [Transactions](./transactions.md) - [Receipts](./receipts.md) +- [Predicates](./predicates.md) diff --git a/docs/src/indexing-fuel-types/predicate-witness-data.md b/docs/src/indexing-fuel-types/predicate-witness-data.md new file mode 100644 index 000000000..0c36d6038 --- /dev/null +++ b/docs/src/indexing-fuel-types/predicate-witness-data.md @@ -0,0 +1 @@ +# Predicate Witness Data diff --git a/docs/src/indexing-fuel-types/predicates.md b/docs/src/indexing-fuel-types/predicates.md new file mode 100644 index 000000000..d4cb2c4d2 --- /dev/null +++ b/docs/src/indexing-fuel-types/predicates.md @@ -0,0 +1,113 @@ +# Predicates + +## Definition + +- Immutable and ephemeral. +- Predicates are one of the most important abstractions that Fuel makes availabe. A predicate effectively has two states: a _created_ state, and a _spent_ state. A predicate has a `created` state when the predicate is...created. + + - A predicate becomes "created" when a given UTXO inclues certain parameters in its [witness data](./transactions.md). + - A predicate becomes "spent" when the aforementioned UTXO is used as an input into a subsequent [Transaction](./transactions.md), where this subsequent Transaction also includes certain parameters in its witness data. + +> TLDR: A predicate is a UTXO that includes extra information sent in the Transaction. + +```rust,ignore +/// The indexer's public representation of a predicate. +/// +/// This is a manual copy of `fuels::accounts::predicate::Predicate` due to the +/// fact that `fuels::accounts::predicate::Predicate` is not serializable. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Predicate { + /// Address of the predicate. + /// + /// Using `Address` because `Bech32Address` is not serializable. + address: Address, + + /// Bytecode of the predicate. + code: Vec, + + /// Inputs injected into the predicate. + /// + /// Using `Vec because `UnresolvedBytes` is not serializable. + data: Vec, + + /// Chain ID of the predicate. + chain_id: u64, +} +``` + +## Usage + +In order to get started indexing Predicates, users will need to: + +1. Create a new Predicate project using [`forc`] +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(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/docs/src/indexing-fuel-types/receipts.md b/docs/src/indexing-fuel-types/receipts.md index a000df9dd..083cff326 100644 --- a/docs/src/indexing-fuel-types/receipts.md +++ b/docs/src/indexing-fuel-types/receipts.md @@ -34,6 +34,9 @@ pub struct Burn { ``` ```rust, ignore +extern crate alloc; +use fuel_indexer_utils::prelude::*; + mod indexer_mod { fn handle_burn_receipt(block_data: BlockData) { let height = block_data.header.height; diff --git a/docs/src/project-components/manifest.md b/docs/src/project-components/manifest.md index 96ed9c883..69271526b 100644 --- a/docs/src/project-components/manifest.md +++ b/docs/src/project-components/manifest.md @@ -8,13 +8,18 @@ Below is a sample indexer manifest file namespace: fuellabs identifier: order_book_v1 fuel_client: beta-4.fuel.network:80 -abi: path/to/my/contract-abi.json -contract_id: "fuels0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051" -graphql_schema: path/to/my/schema.graphql +contract: + abi: path/to/my/contract-abi.json + subscriptions: + - fuels0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051 +schema: path/to/my/schema.graphql start_block: 1564 end_block: 310000 module: wasm: path/to/my/wasm_module.wasm +predicates: + abis: ~ + templates: ~ ``` ## `namespace` @@ -35,19 +40,26 @@ _Optional._ The `fuel_client` denotes the address (host, port combination) of the running Fuel client that you would like your indexer to index events from. In order to use this per-indexer `fuel_client` option, the indexer service at which your indexer is deployed will have to run with the `--indexer_net_config` option. -## `abi` +## `contract` _Optional._ -The `abi` option is used to provide a link to the Sway JSON application binary interface (ABI) that is generated when you build your Sway project. This generated ABI contains all types, type IDs, logged types, and message types used in your Sway contract. +Indexer contract settings. -## `contract_id` +### `abi` _Optional._ -The `contract_id` specifies the particular contract to which you would like an indexer to subscribe. Setting this field to an empty string will index events from any contract that is currently executing on the network. This field accepts either a single string, or a list of strings. The indexer will index events from all IDs if a list is passed. +The `abi` is used to provide a link to the contract JSON application binary interface (ABI) that is generated when you build your contract project. -> Important: Contract IDs are unique to the content of a contract. If you are subscribing to a certain contract and then the contract itself is changed or updated, you will need to change the `contract_id` field of the manifest to the new ID. +### `subscriptions` + +_Optional._ + +The `contract_subscriptions` specifies the particular contract to which you would like an indexer to subscribe. Setting this field to an empty string will index events from any contract that is currently executing on the network. This field accepts either a single string, or a list of strings. The indexer will index events from all IDs if a list is passed. + +> Important: Contract IDs are unique to the content of a contract. If you are subscribing to a certain contract and then the contract itself is changed or updated, you will need to change the `subscriptions` field of the manifest with the new contract ID. +> > Note: This parameter supports both Bech32 contract IDs and non-Bech32 contract IDs ## `graphql_schema` @@ -83,3 +95,37 @@ The `module` field contains a file path that points to code that will be run as _Optional._ The `resumable` field contains a boolean value and specifies whether the indexer should synchronise with the latest block if it has fallen out of sync. + +## `predicates` + +_Optional._ + +Indexer predicate settings. + +### `templates` + +_Optional._ + +A list of predicate template configurations. + +#### `name` + +_Required._ + +The name of the predicate template. + +> This name follow the name of the predicate listed in the predicate's `Forc.toml` + +#### `id` + +_Required._ + +The SHA-256 hash of the predicate bytecode. + +> This can be found in your predicate's `[predicate-name]-bin-root` file after building your predicate with `forc build` + +#### `abi` + +_Required._ + +The `abi` is used to provide a link to the predicate JSON application binary interface (ABI) that is generated when you build your predicate project. diff --git a/examples/fuel-explorer/fuel-explorer/fuel_explorer.manifest.yaml b/examples/fuel-explorer/fuel-explorer/fuel_explorer.manifest.yaml index 21d61ce07..77925181f 100644 --- a/examples/fuel-explorer/fuel-explorer/fuel_explorer.manifest.yaml +++ b/examples/fuel-explorer/fuel-explorer/fuel_explorer.manifest.yaml @@ -5,9 +5,14 @@ namespace: fuellabs # The identifier field is used to identify the given index. identifier: explorer -# The abi option is used to provide a link to the Sway JSON ABI that is generated when you -# build your project. -abi: ~ +# Indexer contract configuration. +contract: + + # File paths to the contract JSON ABIs that are generated when you build your Sway contracts. + abi: ~ + + # Specifies which particular contracts you would like your indexer to subscribe to. + subscriptions: ~ # The particular start block after which you'd like your indexer to start indexing events. start_block: ~ @@ -21,12 +26,9 @@ end_block: ~ # with the `--indexer_net_config` option. fuel_client: ~ -# The contract_id specifies which particular contract you would like your index to subscribe to. -contract_id: ~ - # The graphql_schema field contains the file path that points to the GraphQL schema for the # given index. -graphql_schema: examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql +schema: examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql # The module field contains a file path that points to code that will be run as an executor inside # of the indexer. @@ -37,3 +39,9 @@ module: # The resumable field contains a boolean that specifies whether or not the indexer should, synchronise # with the latest block if it has fallen out of sync. resumable: true + +# Indexer predicate configuration. +predicates: + + # Template commitments (hashes) of the bytecode of predicates used by this indexer. + templates: ~ diff --git a/examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml b/examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml index 2c49dbbc7..789babe8c 100644 --- a/examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml +++ b/examples/greetings/greetings-indexer/greetings_indexer.manifest.yaml @@ -5,9 +5,15 @@ namespace: fuellabs # The identifier field is used to identify the given index. identifier: greetings -# The abi option is used to provide a link to the Sway JSON ABI that is generated when you -# build your project. -abi: examples/greetings/contracts/greeting/out/debug/greeting-abi.json +# Indexer contract configuration. +contract: + + # File paths to the contract JSON ABIs that are generated when you build your Sway contracts. + abi: examples/greetings/contracts/greeting/out/debug/greeting-abi.json + + # Specifies which particular contracts you would like your indexer to subscribe to. + subscriptions: + - fuel1q6sj2srt0u40jdqg2lvvnspyuhse9rs2s2fv9nmv0hqjcrdc7sqsfpwv9x # The particular start block after which you'd like your indexer to start indexing events. start_block: ~ @@ -21,12 +27,9 @@ end_block: ~ # with the `--indexer_net_config` option. fuel_client: ~ -# The contract_id specifies which particular contract you would like your index to subscribe to. -contract_id: fuel1q6sj2srt0u40jdqg2lvvnspyuhse9rs2s2fv9nmv0hqjcrdc7sqsfpwv9x - # The graphql_schema field contains the file path that points to the GraphQL schema for the # given index. -graphql_schema: examples/greetings/greetings-indexer/schema/greetings_indexer.schema.graphql +schema: examples/greetings/greetings-indexer/schema/greetings_indexer.schema.graphql # The module field contains a file path that points to code that will be run as an executor inside # of the indexer. @@ -36,4 +39,10 @@ module: # The resumable field contains a boolean that specifies whether or not the indexer should, synchronise # with the latest block if it has fallen out of sync. -resumable: true \ No newline at end of file +resumable: true + +# Indexer predicate configuration. +predicates: + + # Template commitments (hashes) of the bytecode of predicates used by this indexer. + templates: ~ diff --git a/examples/hello-world/hello-world/hello_world.manifest.yaml b/examples/hello-world/hello-world/hello_world.manifest.yaml index bebda374e..4613e8aa8 100644 --- a/examples/hello-world/hello-world/hello_world.manifest.yaml +++ b/examples/hello-world/hello-world/hello_world.manifest.yaml @@ -5,9 +5,14 @@ namespace: fuellabs # The identifier field is used to identify the given index. identifier: hello_world -# The abi option is used to provide a link to the Sway JSON ABI that is generated when you -# build your project. -abi: ~ +# Indexer contract configuration. +contract: + + # File paths to the contract JSON ABIs that are generated when you build your Sway contracts. + abi: ~ + + # Specifies which particular contracts you would like your indexer to subscribe to. + subscriptions: ~ # The particular start block after which you'd like your indexer to start indexing events. start_block: ~ @@ -21,12 +26,9 @@ end_block: ~ # with the `--indexer_net_config` option. fuel_client: ~ -# The contract_id specifies which particular contract you would like your index to subscribe to. -contract_id: ~ - # The graphql_schema field contains the file path that points to the GraphQL schema for the # given index. -graphql_schema: examples/hello-world/hello-world/schema/hello_world.schema.graphql +schema: examples/hello-world/hello-world/schema/hello_world.schema.graphql # The module field contains a file path that points to code that will be run as an executor inside # of the indexer. @@ -36,4 +38,10 @@ module: # The resumable field contains a boolean that specifies whether or not the indexer should, synchronise # with the latest block if it has fallen out of sync. -resumable: true \ No newline at end of file +resumable: true + +# Indexer predicate configuration. +predicates: + + # Template commitments (hashes) of the bytecode of predicates used by this indexer. + templates: ~ diff --git a/packages/fuel-indexer-api-server/src/uses.rs b/packages/fuel-indexer-api-server/src/uses.rs index 747b36f3b..316d0a09c 100644 --- a/packages/fuel-indexer-api-server/src/uses.rs +++ b/packages/fuel-indexer-api-server/src/uses.rs @@ -492,7 +492,7 @@ pub(crate) async fn verify_signature( } _ => { error!("Unsupported authentication strategy."); - unimplemented!(); + unimplemented!("Unsupported authentication strategy."); } } } diff --git a/packages/fuel-indexer-benchmarks/benches/wasm/examples/fuel_explorer.rs b/packages/fuel-indexer-benchmarks/benches/wasm/examples/fuel_explorer.rs index 48caa2318..ffa3c1996 100644 --- a/packages/fuel-indexer-benchmarks/benches/wasm/examples/fuel_explorer.rs +++ b/packages/fuel-indexer-benchmarks/benches/wasm/examples/fuel_explorer.rs @@ -5,17 +5,20 @@ use fuel_indexer_lib::{config::IndexerConfig, manifest::Manifest}; fn setup_fuel_explorer_manifest() -> Manifest { let manifest_str = r#" namespace: indexer_benchmarks -abi: ~ +contract: + - abi: ~ + - subscriptions: ~ identifier: fuel_explorer fuel_client: ~ -graphql_schema: ../../examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql +schema: ../../examples/fuel-explorer/fuel-explorer/schema/fuel_explorer.schema.graphql module: wasm: ../../target/wasm32-unknown-unknown/release/fuel_explorer.wasm metrics: ~ -contract_id: ~ start_block: ~ end_block: ~ resumable: ~ +predicates: + templates: ~ "#; Manifest::try_from(manifest_str).unwrap() diff --git a/packages/fuel-indexer-benchmarks/src/lib.rs b/packages/fuel-indexer-benchmarks/src/lib.rs index 474b53643..e05a98dc7 100644 --- a/packages/fuel-indexer-benchmarks/src/lib.rs +++ b/packages/fuel-indexer-benchmarks/src/lib.rs @@ -41,11 +41,7 @@ async fn setup_wasm_executor( pool: IndexerConnectionPool, ) -> Result { config.database = DatabaseConfig::from_str(&db_url).unwrap(); - let schema_version = manifest - .graphql_schema_content() - .unwrap() - .version() - .to_string(); + let schema_version = manifest.schema_content().unwrap().version().to_string(); let executor = WasmIndexExecutor::new( &config, &manifest, diff --git a/packages/fuel-indexer-lib/Cargo.toml b/packages/fuel-indexer-lib/Cargo.toml index b58276d79..a9501b1df 100644 --- a/packages/fuel-indexer-lib/Cargo.toml +++ b/packages/fuel-indexer-lib/Cargo.toml @@ -17,9 +17,9 @@ bincode = { workspace = true } clap = { features = ["cargo", "derive", "env"], workspace = true } fuel-indexer-types = { workspace = true } http = { version = "0.2", default-features = false } +inflections = { version = "1", default-features = false } lazy_static = { version = "1.4" } serde = { workspace = true } -serde_json = { workspace = true } serde_yaml = "0.8" sha2 = "0.9" strum = { version = "0.24", default-features = false, features = ["derive"] } diff --git a/packages/fuel-indexer-lib/src/constants.rs b/packages/fuel-indexer-lib/src/constants.rs index 9c296eede..cdae9e7f7 100644 --- a/packages/fuel-indexer-lib/src/constants.rs +++ b/packages/fuel-indexer-lib/src/constants.rs @@ -6,6 +6,8 @@ lazy_static! { /// Set of internal indexer entities. pub static ref INTERNAL_INDEXER_ENTITIES: HashSet<&'static str> = HashSet::from([ "IndexMetadataEntity", + "IndexerPredicateEntity", + "PredicateCoinOutputEntity", ]); /// Set of types that implement `AsRef<[u8]>`. @@ -84,28 +86,30 @@ lazy_static! { "Option", "Option", "Option", - "Option", "Option", + "Option", + "String", "UID", "Vec", - "String", ]); /// Fuel-specific receipt-related type names. pub static ref FUEL_PRIMITIVES: HashSet<&'static str> = HashSet::from([ "BlockData", + "Burn", "Call", "Log", "LogData", "MessageOut", + "Mint", "Panic", + "Predicate", + "Predicates", "Return", "Revert", "ScriptResult", "Transfer", "TransferOut", - "Mint", - "Burn", ]); /// Type names that are not allowed in GraphQL schema. @@ -156,6 +160,8 @@ lazy_static! { "Mint", "Outputs", "Panic", + "Predicate", + "Predicates", "ReceiptsRoot", "Return", "Revert", @@ -175,23 +181,23 @@ lazy_static! { "Witnesses", ]); - /// ABI types not allowed in the contract ABI. pub static ref UNSUPPORTED_ABI_JSON_TYPES: HashSet<&'static str> = HashSet::from(["Vec"]); /// Generic Sway ABI types. pub static ref IGNORED_GENERIC_METADATA: HashSet<&'static str> = HashSet::from([ - "generic T", + "enum Result", + "generic D", "generic E", + "generic T", "raw untyped ptr", - "struct RawVec", - "struct RawBytes", "struct Bytes", - "enum Result" + "struct RawBytes", + "struct RawVec", ]); pub static ref GENERIC_STRUCTS: HashSet<&'static str> = HashSet::from([ + "Option", "Vec", - "Option" ]); } diff --git a/packages/fuel-indexer-lib/src/graphql/mod.rs b/packages/fuel-indexer-lib/src/graphql/mod.rs index 69bed9628..7ff445adc 100644 --- a/packages/fuel-indexer-lib/src/graphql/mod.rs +++ b/packages/fuel-indexer-lib/src/graphql/mod.rs @@ -14,7 +14,9 @@ use async_graphql_parser::{ }, Pos, Positioned, }; -use fuel_indexer_types::graphql::IndexMetadata; +use fuel_indexer_types::indexer::{ + GraphQLEntity, IndexMetadata, IndexerPredicate, PredicateCoinOutput, +}; use sha2::{Digest, Sha256}; use std::collections::{HashMap, HashSet}; use types::IdCol; @@ -32,11 +34,23 @@ pub fn schema_version(schema: &str) -> String { /// Inject native entities into the GraphQL schema. fn inject_native_entities_into_schema(schema: &str) -> String { + // NOTE: This is fine for now as long as the number of native entities is relatively small + // But should we continue to inject entities like this we'll need to make this a bit more + // idiomatic. + let mut schema = schema.to_string(); if !schema.contains("type IndexMetadataEntity") { - format!("{}{}", schema, IndexMetadata::schema_fragment()) - } else { - schema.to_string() + schema = format!("{}{}", schema, IndexMetadata::schema_fragment()) + } + + if !schema.contains("type PredicateCoinOutputEntity") { + schema = format!("{}{}", schema, PredicateCoinOutput::schema_fragment()) } + + if !schema.contains("type IndexerPredicateEntity") { + schema = format!("{}{}", schema, IndexerPredicate::schema_fragment()) + } + + schema } /// Inject internal types into the schema. In order to support popular diff --git a/packages/fuel-indexer-lib/src/manifest.rs b/packages/fuel-indexer-lib/src/manifest.rs index 24d954282..2d3e75877 100644 --- a/packages/fuel-indexer-lib/src/manifest.rs +++ b/packages/fuel-indexer-lib/src/manifest.rs @@ -1,11 +1,11 @@ use crate::graphql::GraphQLSchema; use anyhow::Result; +use inflections::case::to_pascal_case; use serde::{Deserialize, Serialize}; use std::{ fs::File, io::{Read, Write}, path::{Path, PathBuf}, - str::FromStr, }; use thiserror::Error; @@ -55,6 +55,76 @@ impl AsRef for Module { } } +/// Predicates used by this indexer. +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Predicates { + /// Predicate templates + templates: Option>, +} + +impl Predicates { + /// Get the predicate templates. + pub fn templates(&self) -> Option<&[PredicateTemplate]> { + self.templates.as_deref() + } + + /// Check if this predicate set is empty. + pub fn is_empty(&self) -> bool { + self.templates.is_none() + } +} + +/// Represents a predicate template. +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct PredicateTemplate { + /// Name of predicate. + name: String, + + /// Hash of predicate bytecode used to uniquely identify predicate. + id: String, + + /// Filepath to Sway predicate ABI. + abi: String, +} + +impl PredicateTemplate { + /// Get the predicate name. + pub fn name(&self) -> String { + to_pascal_case(&self.name) + } + + /// Get the predicate ID. + pub fn id(&self) -> &str { + &self.id + } + + /// Get the predicate ABI. + pub fn abi(&self) -> &str { + &self.abi + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Contract { + /// Filepath to Sway contract ABI. + abi: Option, + + /// Set of contract IDs this indexer should subscribe to. + subsriptions: Option>, +} + +impl Contract { + /// Get the contract ABI. + pub fn abi(&self) -> Option<&str> { + self.abi.as_deref() + } + + /// Get the contract subscriptions. + pub fn subscriptions(&self) -> Option<&[String]> { + self.subsriptions.as_deref() + } +} + /// Represents the indexer manifest file. /// /// This manifest file is a simple YAML file that is read and passed @@ -69,8 +139,8 @@ pub struct Manifest { /// Identifier of indexer. identifier: String, - /// Filepath to Sway contract ABI. - abi: Option, + /// Contract configuration. + contract: Option, /// URL to Fuel client. /// @@ -78,21 +148,11 @@ pub struct Manifest { fuel_client: Option, /// Filepath to this indexer's GraphQL schema. - graphql_schema: String, + schema: String, /// Executor module. module: Module, - /// Whether or not to record metrics for this indexer. - metrics: Option, - - /// Set of contract IDs this indexer should subscribe to. - #[serde( - serialize_with = "ContractIds::serialize", - deserialize_with = "ContractIds::deserialize" - )] - contract_id: ContractIds, - /// Block at which indexer should start. start_block: Option, @@ -100,8 +160,10 @@ pub struct Manifest { end_block: Option, /// When set to true, the indexer will resume from the block height at which it last stopped. - #[serde(default)] resumable: Option, + + /// Set of predicates used by this indexer. + predicates: Option, } impl Manifest { @@ -118,12 +180,12 @@ impl Manifest { } /// Return the raw GraphQL schema string for an indexer manifest. - pub fn graphql_schema_content(&self) -> ManifestResult { - let mut file = File::open(&self.graphql_schema) - .map_err(|err| ManifestError::FileError(self.graphql_schema.clone(), err))?; + pub fn schema_content(&self) -> ManifestResult { + let mut file = File::open(&self.schema) + .map_err(|err| ManifestError::FileError(self.schema.clone(), err))?; let mut schema = String::new(); file.read_to_string(&mut schema) - .map_err(|err| ManifestError::FileError(self.graphql_schema.clone(), err))?; + .map_err(|err| ManifestError::FileError(self.schema.clone(), err))?; Ok(GraphQLSchema::new(schema)) } @@ -174,16 +236,7 @@ impl Manifest { self.end_block = Some(block); } - /// Set the GraphQL schema for this indexer. - pub fn set_graphql_schema(&mut self, schema: String) { - self.graphql_schema = schema; - } - - /// Set the contract ABI for this indexer. - pub fn set_abi(&mut self, abi: String) { - self.abi = Some(abi); - } - + /// Get the indexer namespace. pub fn namespace(&self) -> &str { &self.namespace } @@ -200,34 +253,47 @@ impl Manifest { &self.identifier } - pub fn graphql_schema(&self) -> &str { - &self.graphql_schema + /// Get the indexer GraphQL schema. + pub fn schema(&self) -> &str { + &self.schema } + /// Get the indexer start block. pub fn start_block(&self) -> Option { self.start_block } - pub fn contract_id(&self) -> &ContractIds { - &self.contract_id + /// Get the indexer contract configuration. + pub fn contract_abi(&self) -> Option<&str> { + self.contract.as_ref().and_then(|c| c.abi()) } - pub fn abi(&self) -> Option<&str> { - self.abi.as_deref() + /// Get the indexer contract subscriptions. + pub fn contract_subscriptions(&self) -> Option<&[String]> { + self.contract.as_ref().and_then(|c| c.subscriptions()) } + /// Get the indexer predicates. + pub fn predicates(&self) -> Option<&Predicates> { + self.predicates.as_ref() + } + + /// Get the indexer Fuel client. pub fn fuel_client(&self) -> Option<&str> { self.fuel_client.as_deref() } + /// Get the indexer module. pub fn module(&self) -> &Module { &self.module } + /// Get the indexer end block. pub fn end_block(&self) -> Option { self.end_block } + /// Get the indexer's resumability. pub fn resumable(&self) -> Option { self.resumable } @@ -256,68 +322,3 @@ impl TryFrom<&Vec> for Manifest { Ok(manifest) } } - -/// Represents contract IDs in a `Manifest` struct. -#[derive(Debug, Deserialize, Serialize, Clone)] -#[serde(untagged)] -pub enum ContractIds { - /// Single represents a single contract ID as an `Option`. - #[serde(alias = "single")] - Single(Option), - - /// Multiple represents a vector of contracts IDs as a Vec. - #[serde(alias = "multiple")] - Multiple(Vec), -} - -impl ContractIds { - fn serialize(ids: &ContractIds, serializer: S) -> Result - where - S: serde::Serializer, - { - let s = match ids { - ContractIds::Single(Some(id)) => id.clone(), - ContractIds::Multiple(ids) => { - serde_json::to_string(ids).map_err(serde::ser::Error::custom)? - } - _ => return serializer.serialize_none(), - }; - serializer.serialize_str(&s) - } - - fn deserialize<'de, D>(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let value = serde_yaml::Value::deserialize(deserializer)?; - match value { - serde_yaml::Value::String(s) => Ok(ContractIds::Single(Some(s))), - serde_yaml::Value::Sequence(seq) => { - let ids = seq - .into_iter() - .filter_map(|val| match val { - serde_yaml::Value::String(s) => Some(s), - _ => None, - }) - .collect::>(); - Ok(ContractIds::Multiple(ids.into_iter().collect())) - } - serde_yaml::Value::Null => Ok(ContractIds::Single(None)), - _ => Err(serde::de::Error::custom("Invalid contract_id value")), - } - } -} - -impl FromStr for ContractIds { - type Err = String; - - fn from_str(s: &str) -> Result { - if s.starts_with('[') { - serde_json::from_str::>(s) - .map(ContractIds::Multiple) - .map_err(|err| err.to_string()) - } else { - Ok(ContractIds::Single(Some(s.to_string()))) - } - } -} diff --git a/packages/fuel-indexer-macros/Cargo.toml b/packages/fuel-indexer-macros/Cargo.toml index 242b9a72b..1d9bf60b6 100644 --- a/packages/fuel-indexer-macros/Cargo.toml +++ b/packages/fuel-indexer-macros/Cargo.toml @@ -22,7 +22,8 @@ fuel-indexer-lib = { workspace = true, default-features = true } fuel-indexer-schema = { workspace = true, default-features = false } fuel-indexer-types = { workspace = true } fuels = { workspace = true } -fuels-code-gen = { version = "0.46", default-features = false } +fuels-code-gen = { workspace = true } +inflections = { version = "1", default-features = false } lazy_static = "1.4" proc-macro-error = "1.0" proc-macro2 = "1.0" diff --git a/packages/fuel-indexer-macros/src/decoder.rs b/packages/fuel-indexer-macros/src/decoder.rs index bf383e980..8f38e1512 100644 --- a/packages/fuel-indexer-macros/src/decoder.rs +++ b/packages/fuel-indexer-macros/src/decoder.rs @@ -49,28 +49,6 @@ pub struct ImplementationDecoder { parsed: ParsedGraphQLSchema, } -impl Default for ImplementationDecoder { - fn default() -> Self { - Self { - parameters: quote! {}, - hasher: quote! {}, - struct_fields: quote! {}, - field_selectors: vec![], - typdef: TypeDefinition { - description: None, - extend: false, - name: Positioned::new(Name::new(String::new()), Pos::default()), - kind: TypeKind::Object(ObjectType { - implements: vec![], - fields: vec![], - }), - directives: vec![], - }, - parsed: ParsedGraphQLSchema::default(), - } - } -} - impl Decoder for ImplementationDecoder { /// Create a decoder from a GraphQL `TypeDefinition`. fn from_typedef(typ: &TypeDefinition, parsed: &ParsedGraphQLSchema) -> Self { @@ -517,20 +495,6 @@ pub struct ObjectDecoder { type_id: i64, } -impl Default for ObjectDecoder { - fn default() -> Self { - Self { - ident: format_ident!("unnamed"), - struct_fields: quote! {}, - field_extractors: quote! {}, - from_row: quote! {}, - to_row: quote! {}, - impl_decoder: ImplementationDecoder::default(), - type_id: std::i64::MAX, - } - } -} - impl Decoder for ObjectDecoder { /// Create a decoder from a GraphQL `TypeDefinition`. fn from_typedef(typ: &TypeDefinition, parsed: &ParsedGraphQLSchema) -> Self { @@ -776,13 +740,13 @@ impl From for TokenStream { impl From<#ident> for Json { fn from(value: #ident) -> Self { let s = serde_json::to_string(&value).expect("Failed to serialize Entity."); - Self(s) + Self::new(s) } } impl From for #ident { fn from(value: Json) -> Self { - let s: #ident = serde_json::from_str(&value.0).expect("Failed to deserialize Entity."); + let s: #ident = serde_json::from_str(&value.into_inner()).expect("Failed to deserialize Entity."); s } } diff --git a/packages/fuel-indexer-macros/src/helpers.rs b/packages/fuel-indexer-macros/src/helpers.rs index 550335c30..5b3ba4c95 100644 --- a/packages/fuel-indexer-macros/src/helpers.rs +++ b/packages/fuel-indexer-macros/src/helpers.rs @@ -1,18 +1,19 @@ -use std::collections::{HashMap, HashSet}; - use async_graphql_parser::types::{BaseType, FieldDefinition, Type as AsyncGraphQLType}; use async_graphql_value::Name; use fuel_abi_types::abi::program::{ - ABIFunction, LoggedType, ProgramABI, TypeDeclaration, + ABIFunction, Configurable, LoggedType, ProgramABI, TypeDeclaration, }; use fuel_indexer_lib::{ constants::*, graphql::{list_field_type_name, types::IdCol, ParsedGraphQLSchema}, + manifest::Manifest, }; use fuel_indexer_types::{type_id, FUEL_TYPES_NAMESPACE}; use fuels_code_gen::utils::Source; +use inflections::case::to_pascal_case; use proc_macro2::TokenStream; use quote::{format_ident, quote}; +use std::collections::{HashMap, HashSet}; use syn::{GenericArgument, Ident, PathArguments, Type, TypePath}; /// Provides a TokenStream to be used for unwrapping `Option`s for external types. @@ -39,14 +40,14 @@ pub fn unwrap_or_default_for_external_type( } /// Extract tokens from JSON ABI file -pub fn get_json_abi(abi_path: Option) -> Option { +pub fn get_json_abi(abi_path: Option<&str>) -> Option { match abi_path { Some(abi) => { let src = match Source::parse(abi) { Ok(src) => src, Err(e) => { proc_macro_error::abort_call_site!( - "`abi` must be a file path to valid json abi: {:?}.", + "`abi` must be a file path to valid json contract_abi:{:?}.", e ) } @@ -83,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(); @@ -183,6 +190,10 @@ pub fn is_generic_type(typ: &TypeDeclaration) -> bool { matches!(gt, GenericType::Vec | GenericType::Option) } +pub fn is_predicate_primitive(typ: &TypeDeclaration) -> bool { + typ.type_field.as_str() == "Predicate" || typ.type_field.as_str() == "Predicates" +} + /// 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( @@ -194,10 +205,18 @@ pub fn decode_snippet( let ty_id = typ.type_id; if is_fuel_primitive(typ) { - quote! { - #ty_id => { - let obj: #type_tokens = bincode::deserialize(&data).expect("Bad bincode."); - self.#name.push(obj); + if is_predicate_primitive(typ) { + quote! { + #ty_id => { + Logger::warn("Skipping predicate decoder."); + } + } + } else { + quote! { + #ty_id => { + let obj: #type_tokens = bincode::deserialize(&data).expect("Bad bincode."); + self.#name.push(obj); + } } } } else if is_rust_primitive(type_tokens) { @@ -301,16 +320,18 @@ impl Codegen for TypeDeclaration { "MessageOut" => quote! { MessageOut }, "Mint" => quote! { Mint }, "Panic" => quote! { Panic }, + "Predicate" => quote! { Predicate }, + "Predicates" => quote! { Predicates }, "Return" => quote! { Return }, "Revert" => quote! { Revert }, "ScriptResult" => quote! { ScriptResult }, + "str" => quote! { String }, "Transfer" => quote! { Transfer }, "TransferOut" => quote! { TransferOut }, "u16" => quote! { u16 }, "u32" => quote! { u32 }, "u64" => quote! { u64 }, "u8" => quote! { u8 }, - "str" => quote! { String }, o if o.starts_with("str[") => quote! { String }, o => { proc_macro_error::abort_call_site!( @@ -878,13 +899,12 @@ impl From for TokenStream { /// 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, + logged_types: &[LoggedType], abi_types: &'a HashMap, ) -> &'a TypeDeclaration { let result = - abi.logged_types + logged_types .iter() - .flatten() .filter_map(|log| { if log.log_id == typ.log_id && log.application.type_arguments.is_some() { let args = log @@ -1174,6 +1194,7 @@ pub fn typed_path_components( (name, tokens) } +/// Whether or not the given `TypeDeclaration` is an array-like type. pub fn is_array_type(typ: &TypeDeclaration) -> bool { typ.type_field.starts_with('[') && typ.type_field.ends_with(']') @@ -1191,3 +1212,91 @@ pub fn is_unsupported_type(type_name: &str) -> bool { _ => UNSUPPORTED_ABI_JSON_TYPES.contains(type_name), } } + +/// Prefix both schema and ABI paths with `std::env::COMPILE_TEST_PREFIX` if it exists. +pub fn prefix_abi_and_schema_paths( + abi_path: Option<&str>, + schema: Option<&str>, +) -> (Option, Option) { + if let Some(abi_path) = abi_path { + match std::env::var("COMPILE_TEST_PREFIX") { + Ok(prefix) => { + let prefixed = std::path::Path::new(&prefix).join(abi_path); + let abi_string = prefixed + .into_os_string() + .to_str() + .expect("Could not parse prefixed ABI path.") + .to_string(); + + if let Some(schema) = schema { + let prefixed = std::path::Path::new(&prefix).join(schema); + let schema = prefixed + .into_os_string() + .to_str() + .expect("Could not parse prefixed GraphQL schema path.") + .to_string(); + + return (Some(abi_string), Some(schema)); + } + + return (Some(abi_string), None); + } + Err(_) => { + return (Some(abi_path.to_string()), schema.map(|s| s.to_string())); + } + }; + } + + (None, schema.map(|s| s.to_string())) +} + +/// Return the name of the configurable type of a given predicate's `TypeDeclaration`. +pub fn configurable_fn_type_name(configurable: &Configurable) -> Option { + let name = configurable + .name + .split_whitespace() + .next() + .unwrap() + .to_uppercase(); + if name.as_str() == "()" { + return None; + } + Some(name) +} + +/// Derive a mapping of input type IDs to their corresponding names for a given set of predicates. +pub fn predicate_inputs_names_map(manifest: &Manifest) -> HashMap { + let mut output = HashMap::new(); + if let Some(predicate) = manifest.predicates() { + if predicate.is_empty() { + return output; + } + + if let Some(templates) = predicate.templates() { + for template in templates { + let abi = get_json_abi(Some(template.abi())) + .expect("Could not derive predicate JSON ABI."); + let main = abi + .functions + .iter() + .find(|f| f.name == "main") + .expect("ABI missing main function."); + for input in main.inputs.iter() { + output.insert(input.type_id, input.name.clone()); + } + } + } + } + + output +} + +/// Derive the name of the set of indexer-specific configurables for this predicate template. +pub fn predicate_inputs_name(template_name: &str) -> String { + format!("{}Inputs", to_pascal_case(template_name)) +} + +/// Derive the name of the set of configurables for this predicate template. +pub fn 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 2600c4989..db6ff774e 100644 --- a/packages/fuel-indexer-macros/src/indexer.rs +++ b/packages/fuel-indexer-macros/src/indexer.rs @@ -1,33 +1,27 @@ use crate::{ - helpers::*, parse::IndexerConfig, schema::process_graphql_schema, - wasm::handler_block_wasm, + helpers::*, + parse::IndexerConfig, + schema::process_graphql_schema, + tokens::*, + wasm::{handler_block, predicate_handler_block}, }; use fuel_abi_types::abi::program::TypeDeclaration; use fuel_indexer_lib::{ - constants::*, manifest::ContractIds, manifest::Manifest, - utils::workspace_manifest_prefix, + constants::*, manifest::Manifest, utils::workspace_manifest_prefix, }; -use fuel_indexer_types::{type_id, FUEL_TYPES_NAMESPACE}; +use fuel_indexer_types::{indexer::Predicates, type_id, TypeId, FUEL_TYPES_NAMESPACE}; use fuels::{core::codec::resolve_fn_selector, types::param_types::ParamType}; use fuels_code_gen::{Abigen, AbigenTarget, ProgramType}; use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use syn::{parse_macro_input, FnArg, Item, ItemMod, PatType, Type}; -fn additional_declarations() -> proc_macro2::TokenStream { - quote! { - // Miscellaneous types that can be included in ABI JSON - type b256 = [u8; 32]; - type Bytes = Vec; - type B512 = [u8; 64]; - } -} - +/// Derive a resultant handler block and a set of handler functions from an indexer manifest and +/// a `TokenStream` of indexer handler functions passed by the user. fn process_fn_items( manifest: &Manifest, - abi_path: Option, indexer_module: ItemMod, ) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { if indexer_module.content.is_none() @@ -43,22 +37,48 @@ fn process_fn_items( ) } - let abi = get_json_abi(abi_path).unwrap_or_default(); + let (contract_abi_path, _schema_path) = + prefix_abi_and_schema_paths(manifest.contract_abi(), Some(manifest.schema())); + + let contract_abi = get_json_abi(contract_abi_path.as_deref()).unwrap_or_default(); 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().functions; - let abi_types: Vec = abi + let funcs = contract_abi.clone().functions; + let contract_abi_types = contract_abi .clone() .types .iter() .map(|t| strip_callpath_from_type_field(t.clone())) - .collect(); - let abi_log_types = abi.clone().logged_types.unwrap_or_default(); - let abi_msg_types = abi.clone().messages_types.unwrap_or_default(); + .collect::>(); + + let predicate_abi_types = manifest + .predicates() + .map(|p| { + p.templates() + .map(|t| { + t.iter() + .map(|t| { + let ty_id = type_id(FUEL_TYPES_NAMESPACE, &t.name()) as usize; + let name = predicate_inputs_name(&t.name()); + TypeDeclaration { + type_id: ty_id, + type_field: name, + components: Some(Vec::default()), + type_parameters: None, + } + }) + .collect::>() + }) + .unwrap_or_default() + }) + .unwrap_or_default(); + + let contract_abi_log_types = contract_abi.clone().logged_types.unwrap_or_default(); + let contract_abi_msg_types = contract_abi.clone().messages_types.unwrap_or_default(); let fuel_types = FUEL_PRIMITIVES .iter() .map(|x| { @@ -73,7 +93,7 @@ fn process_fn_items( }) .collect::>(); - let _tuple_types_tyid = abi_types + let _tuple_types_tyid = contract_abi_types .iter() .filter_map(|typ| { if is_tuple_type(typ) { @@ -85,17 +105,38 @@ fn process_fn_items( .collect::>(); // Used to do a reverse lookup of typed path names to ABI type IDs. - let mut type_ids = RESERVED_TYPEDEF_NAMES + let mut contract_abi_type_ids = RESERVED_TYPEDEF_NAMES .iter() .map(|x| (x.to_string(), type_id(FUEL_TYPES_NAMESPACE, x) as usize)) .collect::>(); - let mut abi_types_tyid = abi_types + let predicate_abi_type_ids = predicate_abi_types + .iter() + .map(|typ| { + let name = typ.type_field.clone(); + (name, typ.type_id) + }) + .collect::>(); + + let mut contract_abi_types_tyid = contract_abi_types .iter() .map(|typ| (typ.type_id, typ.clone())) .collect::>(); - let message_types_decoders = abi_msg_types + let predicate_abi_types_tyid = predicate_abi_type_ids + .iter() + .map(|(name, ty_id)| { + let typ = TypeDeclaration { + type_id: *ty_id, + type_field: name.to_string(), + components: None, + type_parameters: None, + }; + (typ.type_id, typ) + }) + .collect::>(); + + let message_types_decoders = contract_abi_msg_types .iter() .map(|typ| { let message_type_id = typ.message_id; @@ -114,9 +155,9 @@ fn process_fn_items( }]) .collect::>(); - // Take a second pass over `abi_types` in order to update an `TypeDeclarations` that need to + // Take a second pass over `contract_abi_types` in order to update an `TypeDeclarations` that need to // be updated with more information for the codegen process. - let abi_types = abi_types + let contract_abi_types = contract_abi_types .iter() .map(|typ| { // If this is an array type we have to manually update it's type field to include its inner @@ -139,7 +180,7 @@ fn process_fn_items( .trim_end_matches(']') .parse::() .expect("Array type size could not be determined."); - let inner = abi_types_tyid + let inner = contract_abi_types_tyid .get(&inner) .expect("Array type inner not found in ABI types."); let name = format!("[{}; {}]", inner.name(), size); @@ -148,7 +189,7 @@ fn process_fn_items( ..typ.clone() }; - abi_types_tyid.insert(typ.type_id, typ.clone()); + contract_abi_types_tyid.insert(typ.type_id, typ.clone()); typ } else { typ.to_owned() @@ -156,7 +197,7 @@ fn process_fn_items( }) .collect::>(); - let abi_type_decoders = abi_types + let contract_abi_decoders = contract_abi_types .iter() .filter_map(|typ| { if is_non_decodable_type(typ) { @@ -174,11 +215,11 @@ fn process_fn_items( let gt = GenericType::from(typ); match gt { GenericType::Vec | GenericType::Option => { - let ab_types = abi_types_tyid.clone(); + let ab_types = contract_abi_types_tyid.clone(); let inner_typs = derive_generic_inner_typedefs( typ, &funcs, - &abi_log_types, + &contract_abi_log_types, &ab_types, ); @@ -189,7 +230,7 @@ fn process_fn_items( let (typ_name, type_tokens) = typed_path_components( typ, inner_typ, - &abi_types_tyid, + &contract_abi_types_tyid, ); let ty_id = type_id(FUEL_TYPES_NAMESPACE, &typ_name) as usize; @@ -204,8 +245,8 @@ fn process_fn_items( return None; } - abi_types_tyid.insert(ty_id, typ.clone()); - type_ids.insert(typ_name.clone(), ty_id); + contract_abi_types_tyid.insert(ty_id, typ.clone()); + contract_abi_type_ids.insert(typ_name.clone(), ty_id); decoded_type_snippets.insert(ty_id); @@ -217,8 +258,12 @@ fn process_fn_items( _ => unimplemented!("Unsupported decoder generic type: {:?}", gt), } } else { + if decoded_type_fields.contains(&typ.type_id) { + return None; + } + let type_tokens = typ.rust_tokens(); - type_ids.insert(type_tokens.to_string(), typ.type_id); + contract_abi_type_ids.insert(type_tokens.to_string(), typ.type_id); decoded_type_snippets.insert(typ.type_id); Some(vec![decode_snippet(&type_tokens, typ)]) } @@ -228,19 +273,23 @@ fn process_fn_items( let fuel_type_decoders = fuel_types .values() - .map(|typ| { + .filter_map(|typ| { + if decoded_type_fields.contains(&typ.type_id) { + return None; + } + let type_tokens = typ.rust_tokens(); - type_ids.insert(type_tokens.to_string(), typ.type_id); + contract_abi_type_ids.insert(type_tokens.to_string(), typ.type_id); decoded_type_snippets.insert(typ.type_id); - decode_snippet(&type_tokens, typ) + Some(decode_snippet(&type_tokens, typ)) }) .collect::>(); - let decoders = [fuel_type_decoders, abi_type_decoders].concat(); + let decoders = [fuel_type_decoders, contract_abi_decoders].concat(); - let abi_struct_fields = abi_types + let contract_struct_fields = contract_abi_types .iter() .filter_map(|typ| { if is_non_decodable_type(typ) { @@ -258,16 +307,19 @@ fn process_fn_items( let inner_typs = derive_generic_inner_typedefs( typ, &funcs, - &abi_log_types, - &abi_types_tyid, + &contract_abi_log_types, + &contract_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 (typ_name, type_tokens) = typed_path_components( + typ, + inner_typ, + &contract_abi_types_tyid, + ); let ty_id = type_id(FUEL_TYPES_NAMESPACE, &typ_name) as usize; if decoded_type_fields.contains(&ty_id) { @@ -282,7 +334,7 @@ fn process_fn_items( let ident = typ.decoder_field_ident(); - type_ids.insert(typ_name.clone(), ty_id); + contract_abi_type_ids.insert(typ_name.clone(), ty_id); decoded_type_fields.insert(ty_id); Some(quote! { @@ -292,9 +344,13 @@ fn process_fn_items( .collect::>(), ); } else { + if decoded_type_fields.contains(&typ.type_id) { + return None; + } + let ident = typ.decoder_field_ident(); let type_tokens = typ.rust_tokens(); - type_ids.insert(typ.rust_tokens().to_string(), typ.type_id); + contract_abi_type_ids.insert(typ.rust_tokens().to_string(), typ.type_id); decoded_type_fields.insert(typ.type_id); Some(vec![quote! { @@ -312,32 +368,66 @@ fn process_fn_items( return None; } + if decoded_type_fields.contains(&typ.type_id) { + return None; + } + let name = typ.decoder_field_ident(); let ty = typ.rust_tokens(); - type_ids.insert(ty.to_string(), typ.type_id); + contract_abi_type_ids.insert(ty.to_string(), typ.type_id); decoded_type_snippets.insert(typ.type_id); - if decoded_type_fields.contains(&typ.type_id) { - return None; + if typ.type_id == Predicates::type_id() { + Some(quote! { + #name: Predicates + }) + } else { + Some(quote! { + #name: Vec<#ty> + }) } - - Some(quote! { - #name: Vec<#ty> - }) }) .collect::>(); - let decoder_struct_fields = [abi_struct_fields, fuel_struct_fields].concat(); + let predicate_inputs_fields = manifest + .predicates() + .map(|p| { + p.templates() + .map(|t| { + t.iter() + .map(|t| { + let name = predicate_inputs_name(&t.name()); + let ident = + format_ident! { "{}_decoded", name.to_lowercase() }; + let ty = format_ident! { "{}", name }; + let ty = quote! { #ty }; + + quote! { + #ident: Vec<#ty> + } + }) + .collect::>() + }) + .unwrap_or_default() + }) + .unwrap_or_default(); + + let decoder_fields = [ + contract_struct_fields, + fuel_struct_fields, + predicate_inputs_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 + let contract_log_type_decoders = contract_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 + let typ = contract_abi_types_tyid .get(&log.application.type_id) .expect("Could not get log type reference from ABI types."); @@ -349,14 +439,20 @@ fn process_fn_items( 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 inner_typ = derive_log_generic_inner_typedefs( + log, + &contract_abi_log_types, + &contract_abi_types_tyid, + ); - let (typ_name, _) = - typed_path_components(typ, inner_typ, &abi_types_tyid); + let (typ_name, _) = typed_path_components( + typ, + inner_typ, + &contract_abi_types_tyid, + ); let ty_id = type_id(FUEL_TYPES_NAMESPACE, &typ_name) as usize; - let _typ = abi_types_tyid.get(&ty_id).expect( + let _typ = contract_abi_types_tyid.get(&ty_id).expect( "Could not get generic log type reference from ABI types.", ); @@ -382,20 +478,20 @@ fn process_fn_items( }) .collect::>(); - let abi_selectors = funcs + let contract_abi_selectors = funcs .iter() .map(|function| { let params: Vec = function .inputs .iter() .map(|x| { - ParamType::try_from_type_application(x, &abi_types_tyid) + ParamType::try_from_type_application(x, &contract_abi_types_tyid) .expect("Could not derive TypeApplication param types.") }) .collect(); let sig = resolve_fn_selector(&function.name, ¶ms[..]); let selector = u64::from_be_bytes(sig); - let ty_id = function_output_type_id(function, &abi_types_tyid); + let ty_id = function_output_type_id(function, &contract_abi_types_tyid); quote! { #selector => #ty_id, @@ -403,14 +499,14 @@ fn process_fn_items( }) .collect::>(); - let abi_selectors_to_fn_names = funcs + let contract_abi_selectors_to_fn_names = funcs .iter() .map(|function| { let params: Vec = function .inputs .iter() .map(|x| { - ParamType::try_from_type_application(x, &abi_types_tyid) + ParamType::try_from_type_application(x, &contract_abi_types_tyid) .expect("Could not derive TypeApplication param types.") }) .collect(); @@ -442,46 +538,23 @@ fn process_fn_items( None => quote! {}, }; - let subscribed_contract_ids = match &manifest.contract_id() { - ContractIds::Single(_) => quote! {}, - ContractIds::Multiple(contract_ids) => { - let contract_ids = contract_ids - .iter() - .map(|id| { - quote! { - Bech32ContractId::from_str(#id).unwrap_or_else(|_| { - let contract_id = ContractId::from_str(&#id).expect("Failed to parse manifest 'contract_id'"); - Bech32ContractId::from(contract_id) - }) - } - }) - .collect::>(); + let subscribed_contract_ids = match &manifest.contract_subscriptions() { + Some(ids) => { + let contract_ids = ids.iter().map(|id| { + quote! { + Bech32ContractId::from_str(#id).expect("Failed to parse contract ID from manifest.") + } + }).collect::>(); quote! { let contract_ids = HashSet::from([#(#contract_ids),*]); } } + None => quote! {}, }; - let check_if_subscribed_to_contract = match &manifest.contract_id() { - ContractIds::Single(contract_id) => match contract_id { - Some(contract_id) => { - quote! { - let id_bytes = <[u8; 32]>::try_from(id).expect("Could not convert contract ID into bytes"); - let bech32_id = Bech32ContractId::new("fuel", id_bytes); - let manifest_contract_id = Bech32ContractId::from_str(#contract_id).unwrap_or_else(|_| { - let contract_id = ContractId::from_str(&#contract_id).expect("Failed to parse manifest 'contract_id'"); - Bech32ContractId::from(contract_id) - }); - if bech32_id != manifest_contract_id { - debug!("Not subscribed to this contract. Will skip this receipt event. <('-'<)"); - continue; - } - } - } - None => quote! {}, - }, - ContractIds::Multiple(_) => { + let check_if_subscribed_to_contract = match &manifest.contract_subscriptions() { + Some(_) => { quote! { let id_bytes = <[u8; 32]>::try_from(id).expect("Could not convert contract ID into bytes"); let bech32_id = Bech32ContractId::new("fuel", id_bytes); @@ -492,6 +565,7 @@ fn process_fn_items( } } } + None => quote! {}, }; for item in contents { @@ -524,19 +598,37 @@ fn process_fn_items( ) } - if !type_ids.contains_key(&path_type_name) { + if !contract_abi_type_ids.contains_key(&path_type_name) + && !predicate_abi_type_ids + .contains_key(&path_type_name) + { proc_macro_error::abort_call_site!( "Type with ident '{:?}' not defined in the ABI.", path_seg.ident ); }; - let ty_id = type_ids - .get(&path_type_name) - .expect("Path type name not found in type IDs."); - let typ = match abi_types_tyid.get(ty_id) { + let ty_id = + match contract_abi_type_ids.get(&path_type_name) { + Some(ty_id) => ty_id, + None => predicate_abi_type_ids + .get(&path_type_name) + .expect("Type not found in Fuel types."), + }; + + let typ = match contract_abi_types_tyid.get(ty_id) { Some(typ) => typ, - None => fuel_types.get(ty_id).unwrap(), + None => match fuel_types.get(ty_id) { + Some(typ) => typ, + None => { + match predicate_abi_types_tyid.get(ty_id) { + Some(typ) => typ, + None => { + panic!("Type not found in ABI types.") + } + } + } + }, }; let dispatcher_name = typ.decoder_field_ident(); @@ -544,8 +636,7 @@ fn process_fn_items( input_checks .push(quote! { self.#dispatcher_name.len() > 0 }); - arg_list - .push(quote! { self.#dispatcher_name[0].clone() }); + arg_list.push(dispatcher_tokens(&dispatcher_name)); } else { proc_macro_error::abort_call_site!( "Arguments must be types defined in the ABI." @@ -601,308 +692,29 @@ fn process_fn_items( } } - let decoder_struct = quote! { - #[derive(Default)] - struct Decoders { - #(#decoder_struct_fields),* - } - - impl Decoders { - fn selector_to_type_id(&self, sel: u64) -> usize { - match sel { - #(#abi_selectors)* - _ => { - debug!("Unknown selector; check ABI to make sure function outputs match to types"); - usize::MAX - } - } - } - - pub fn selector_to_fn_name(&self, sel: u64) -> String { - match sel { - #(#abi_selectors_to_fn_names)* - _ => { - debug!("Unknown selector; check ABI to make sure function outputs match to types"); - "".to_string() - } - } - } - - fn compute_message_id(&self, sender: &Address, recipient: &Address, nonce: Nonce, amount: Word, data: Option>) -> MessageId { - - let mut raw_message_id = Sha256::new() - .chain_update(sender) - .chain_update(recipient) - .chain_update(nonce) - .chain_update(amount.to_be_bytes()); - - let raw_message_id = if let Some(buffer) = data { - raw_message_id - .chain_update(&buffer[..]) - .finalize() - } else { - raw_message_id.finalize() - }; - - let message_id = <[u8; 32]>::try_from(&raw_message_id[..]).expect("Could not calculate message ID from receipt fields"); - - message_id.into() - } - - fn decode_type(&mut self, ty_id: usize, data: Vec) -> anyhow::Result<()> { - let decoder = ABIDecoder::default(); - match ty_id { - #(#decoders),* - _ => { - debug!("Unknown type ID; check ABI to make sure types are correct."); - }, - } - Ok(()) - } - - pub fn decode_block(&mut self, data: BlockData) { - self.blockdata_decoded.push(data); - } - - pub fn decode_return_type(&mut self, sel: u64, data: Vec) -> anyhow::Result<()> { - let ty_id = self.selector_to_type_id(sel); - self.decode_type(ty_id, data)?; - Ok(()) - } - - pub fn decode_logdata(&mut self, rb: usize, data: Vec) -> anyhow::Result<()> { - match rb { - #(#log_type_decoders),* - _ => debug!("Unknown logged type ID; check ABI to make sure that logged types are correct.") - } - Ok(()) - } - - pub fn decode_messagedata(&mut self, type_id: u64, data: Vec) -> anyhow::Result<()> { - match type_id { - #(#message_types_decoders),* - _ => debug!("Unknown message type ID; check ABI to make sure that message types are correct.") - } - Ok(()) - } + let predicate_tokens = transaction_predicate_tokens(manifest); + let predicate_block = predicate_handler_block(manifest, predicate_tokens); + let predicate_decoder_fns = predicate_decoder_fn_tokens(manifest); + + let decoder_struct = decoder_struct_tokens( + decoder_fields, + contract_abi_selectors, + contract_abi_selectors_to_fn_names, + decoders, + contract_log_type_decoders, + message_types_decoders, + abi_dispatchers, + predicate_decoder_fns, + ); - pub fn dispatch(&self) -> anyhow::Result<()> { - #(#abi_dispatchers)* + let process_transaction = + process_transaction_tokens(check_if_subscribed_to_contract, predicate_block); - unsafe { - if !ERROR_MESSAGE.is_empty() { - anyhow::bail!(ERROR_MESSAGE.clone()); - } else { - Ok(()) - } - } - } - } - }; ( quote! { #subscribed_contract_ids - use anyhow::Context; - use fuel::TransactionData; - - let mut process_transaction = |decoder: &mut Decoders, tx: TransactionData| -> anyhow::Result<()> { - let mut return_types = Vec::new(); - let mut callees = HashSet::new(); - - for receipt in tx.receipts { - match receipt { - fuel::Receipt::Call { id: contract_id, amount, asset_id, gas, param1, to: id, .. } => { - #check_if_subscribed_to_contract - - let fn_name = decoder.selector_to_fn_name(param1); - return_types.push(param1); - callees.insert(id); - - let data = serialize( - &Call { - contract_id: ContractId::from(<[u8; 32]>::from(contract_id)), - to: ContractId::from(<[u8; 32]>::from(id)), - amount, - asset_id: AssetId::from(<[u8; 32]>::from(asset_id)), - gas, - fn_name - } - ); - let ty_id = Call::type_id(); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::Log { id, ra, rb, .. } => { - #check_if_subscribed_to_contract - let ty_id = Log::type_id(); - let data = serialize( - &Log { - contract_id: ContractId::from(<[u8; 32]>::from(id)), - ra, - rb - } - ); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::LogData { rb, data, ptr, len, id, .. } => { - #check_if_subscribed_to_contract - decoder.decode_logdata(rb as usize, data.unwrap_or(Vec::::new()))?; - } - fuel::Receipt::Return { id, val, pc, is } => { - #check_if_subscribed_to_contract - if callees.contains(&id) { - let ty_id = Return::type_id(); - let data = serialize( - &Return { - contract_id: ContractId::from(<[u8; 32]>::from(id)), - val, - pc, - is - } - ); - decoder.decode_type(ty_id, data)?; - } - } - fuel::Receipt::ReturnData { data, id, .. } => { - #check_if_subscribed_to_contract - if callees.contains(&id) { - let selector = return_types.pop().expect("No return type available. <('-'<)"); - decoder.decode_return_type(selector, data.unwrap_or(Vec::::new()))?; - } - } - fuel::Receipt::MessageOut { sender, recipient, amount, nonce, len, digest, data, .. } => { - let sender = Address::from(<[u8; 32]>::from(sender)); - let recipient = Address::from(<[u8; 32]>::from(recipient)); - let message_id = decoder.compute_message_id(&sender, &recipient, nonce, amount, data.clone()); - - // It's possible that the data field was generated from an empty Sway `Bytes` array - // in the send_message() instruction in which case the data field in the receipt will - // have no type information or data to decode. Thus, we check for a None value or - // an empty byte vector; if either condition is present, then we decode to a unit struct instead. - let (type_id, data) = data - .map_or((u64::MAX, Vec::::new()), |buffer| { - if buffer.is_empty() { - (u64::MAX, Vec::::new()) - } else { - let (type_id_bytes, data_bytes) = buffer.split_at(8); - let type_id = u64::from_be_bytes( - <[u8; 8]>::try_from(type_id_bytes) - .expect("Could not get type ID for data in MessageOut receipt") - ); - let data = data_bytes.to_vec(); - (type_id, data) - } - }); - - - decoder.decode_messagedata(type_id, data.clone())?; - - let ty_id = MessageOut::type_id(); - let data = serialize( - &MessageOut { - message_id, - sender, - recipient, - amount, - nonce, - len, - digest, - data - } - ); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::ScriptResult { result, gas_used } => { - let ty_id = ScriptResult::type_id(); - let data = serialize(&ScriptResult{ result: u64::from(result), gas_used }); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::Transfer { id, to, asset_id, amount, pc, is, .. } => { - #check_if_subscribed_to_contract - let ty_id = Transfer::type_id(); - let data = serialize( - &Transfer { - contract_id: ContractId::from(<[u8; 32]>::from(id)), - to: ContractId::from(<[u8; 32]>::from(to)), - asset_id: AssetId::from(<[u8; 32]>::from(asset_id)), - amount, - pc, - is - } - ); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::TransferOut { id, to, asset_id, amount, pc, is, .. } => { - #check_if_subscribed_to_contract - let ty_id = TransferOut::type_id(); - let data = serialize( - &TransferOut { - contract_id: ContractId::from(<[u8; 32]>::from(id)), - to: Address::from(<[u8; 32]>::from(to)), - asset_id: AssetId::from(<[u8; 32]>::from(asset_id)), - amount, - pc, - is - } - ); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::Panic { id, reason, .. } => { - #check_if_subscribed_to_contract - let ty_id = Panic::type_id(); - let data = serialize( - &Panic { - contract_id: ContractId::from(<[u8; 32]>::from(id)), - reason: *reason.reason() as u32 - } - ); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::Revert { id, ra, .. } => { - #check_if_subscribed_to_contract - let ty_id = Revert::type_id(); - let data = serialize( - &Revert { - contract_id: ContractId::from(<[u8; 32]>::from(id)), - error_val: u64::from(ra & 0xF) - } - ); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::Mint { sub_id, contract_id, val, pc, is } => { - let ty_id = Mint::type_id(); - let data = serialize( - &Mint { - sub_id: AssetId::from(<[u8; 32]>::from(sub_id)), - contract_id: ContractId::from(<[u8; 32]>::from(contract_id)), - val, - pc, - is - } - ); - decoder.decode_type(ty_id, data)?; - } - fuel::Receipt::Burn { sub_id, contract_id, val, pc, is } => { - let ty_id = Burn::type_id(); - let data = serialize( - &Burn { - sub_id: AssetId::from(<[u8; 32]>::from(sub_id)), - contract_id: ContractId::from(<[u8; 32]>::from(contract_id)), - val, - pc, - is - } - ); - decoder.decode_type(ty_id, data)?; - } - _ => { - info!("This type is not handled yet. (>'.')>"); - } - } - } - - Ok(()) - }; + #process_transaction let mut process_block = |block: BlockData| -> anyhow::Result<()> { #start_block @@ -931,6 +743,7 @@ fn process_fn_items( } }, quote! { + #decoder_struct #(#handler_fns)* @@ -938,56 +751,6 @@ fn process_fn_items( ) } -pub fn prefix_abi_and_schema_paths( - abi: Option<&str>, - schema: &str, -) -> (Option, String) { - if let Some(abi) = abi { - match std::env::var("COMPILE_TEST_PREFIX") { - Ok(prefix) => { - let prefixed = std::path::Path::new(&prefix).join(abi); - let abi_string = prefixed - .into_os_string() - .to_str() - .expect("Could not parse prefixed ABI path.") - .to_string(); - let prefixed = std::path::Path::new(&prefix).join(schema); - let schema = prefixed - .into_os_string() - .to_str() - .expect("Could not parse prefixed GraphQL schema path.") - .to_string(); - - return (Some(abi_string), schema); - } - Err(_) => { - return (Some(abi.into()), schema.to_string()); - } - }; - } - - (None, schema.to_string()) -} - -pub fn get_abi_tokens(namespace: &str, abi: &str) -> proc_macro2::TokenStream { - match Abigen::generate( - vec![AbigenTarget { - name: namespace.to_string(), - abi: abi.to_owned(), - program_type: ProgramType::Contract, - }], - true, - ) { - Ok(tokens) => tokens, - Err(e) => { - proc_macro_error::abort_call_site!( - "Could not generate tokens for abi: {:?}.", - e - ) - } - } -} - pub fn process_indexer_module(attrs: TokenStream, item: TokenStream) -> TokenStream { let config = parse_macro_input!(attrs as IndexerConfig); @@ -1001,36 +764,84 @@ pub fn process_indexer_module(attrs: TokenStream, item: TokenStream) -> TokenStr let indexer_module = parse_macro_input!(item as ItemMod); - let (abi, schema_string) = - prefix_abi_and_schema_paths(manifest.abi(), manifest.graphql_schema()); + let predicate_abi_info = manifest + .predicates() + .map(|p| { + p.templates() + .map(|t| { + t.iter() + .filter_map(|t| { + let (predicate_abi, _) = prefix_abi_and_schema_paths( + Some(t.abi()), + Some(manifest.schema()), + ); + + if let Some(predicate_abi) = predicate_abi { + return Some((predicate_abi, t.name())); + } + None + }) + .collect::>() + }) + .unwrap_or_default() + }) + .unwrap_or_default(); + + let mut targets = Vec::new(); - let abi_tokens = match abi { - Some(ref abi_path) => get_abi_tokens(manifest.namespace(), abi_path), - None => proc_macro2::TokenStream::new(), + if let Some(ref abi) = manifest.contract_abi() { + targets.push(AbigenTarget { + // FIXME: We should be using the name of the contract here + name: manifest.namespace().to_string(), + abi: abi.to_string(), + program_type: ProgramType::Contract, + }); + } + + predicate_abi_info + .iter() + .for_each(|(abi_path, template_name)| { + targets.push(AbigenTarget { + name: template_name.to_string(), + abi: abi_path.to_string(), + program_type: ProgramType::Predicate, + }); + }); + + let abi_tokens = match Abigen::generate(targets, true) { + Ok(tokens) => tokens, + Err(e) => { + proc_macro_error::abort_call_site!( + "Could not generate tokens for ABI: {:?}.", + e + ) + } }; // NOTE: https://nickb.dev/blog/cargo-workspace-and-the-feature-unification-pitfall/ let graphql_tokens = process_graphql_schema( manifest.namespace(), manifest.identifier(), - &schema_string, + manifest.schema(), ); - let decl_tokens = additional_declarations(); + let predicate_impl_tokens = predicate_entity_impl_tokens(); + + let (block, fn_items) = process_fn_items(&manifest, indexer_module); + let block = handler_block(block, fn_items); + let predicate_inputs = predicate_inputs_tokens(&manifest); - let (handler_block, fn_items) = process_fn_items(&manifest, abi, indexer_module); - let handler_block = handler_block_wasm(handler_block); let output = quote! { - #decl_tokens + #predicate_inputs #abi_tokens #graphql_tokens - #handler_block + #predicate_impl_tokens - #fn_items + #block }; proc_macro::TokenStream::from(output) diff --git a/packages/fuel-indexer-macros/src/lib.rs b/packages/fuel-indexer-macros/src/lib.rs index d7f941b3e..b6e9147a9 100644 --- a/packages/fuel-indexer-macros/src/lib.rs +++ b/packages/fuel-indexer-macros/src/lib.rs @@ -6,6 +6,7 @@ pub(crate) mod helpers; pub(crate) mod indexer; pub(crate) mod parse; pub(crate) mod schema; +pub(crate) mod tokens; pub(crate) mod wasm; use indexer::process_indexer_module; diff --git a/packages/fuel-indexer-macros/src/tokens.rs b/packages/fuel-indexer-macros/src/tokens.rs new file mode 100644 index 000000000..e2364772e --- /dev/null +++ b/packages/fuel-indexer-macros/src/tokens.rs @@ -0,0 +1,856 @@ +/// Various functions that generate TokenStreams used to augment the indexer ASTs. +use crate::helpers::*; +use fuel_abi_types::abi::program::TypeDeclaration; +use fuel_indexer_lib::manifest::Manifest; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use std::collections::HashMap; +use syn::Ident; + +/// `TokenStream` that represents the `Decoder` from which all incoming data to the transaction handler +/// is decoded, and all indexer handlers are dispatched. +#[allow(clippy::too_many_arguments)] +pub fn decoder_struct_tokens( + decoder_fields: Vec, + abi_selectors: Vec, + abi_selectors_to_fn_names: Vec, + decoders: Vec, + contract_log_decoders: Vec, + message_decoders: Vec, + abi_dispatchers: Vec, + predicate_decoder_fns: TokenStream, +) -> TokenStream { + quote! { + #[derive(Default)] + struct Decoders { + #(#decoder_fields),* + } + + impl Decoders { + fn selector_to_type_id(&self, sel: u64) -> usize { + match sel { + #(#abi_selectors)* + _ => { + debug!("Unknown selector; check ABI to make sure function outputs match to types"); + usize::MAX + } + } + } + + pub fn selector_to_fn_name(&self, sel: u64) -> String { + match sel { + #(#abi_selectors_to_fn_names)* + _ => { + debug!("Unknown selector; check ABI to make sure function outputs match to types"); + String::new() + } + } + } + + fn compute_message_id(&self, sender: &Address, recipient: &Address, nonce: Nonce, amount: Word, data: Option>) -> MessageId { + + let mut raw_message_id = Sha256::new() + .chain_update(sender) + .chain_update(recipient) + .chain_update(nonce) + .chain_update(amount.to_be_bytes()); + + let raw_message_id = if let Some(buffer) = data { + raw_message_id + .chain_update(&buffer[..]) + .finalize() + } else { + raw_message_id.finalize() + }; + + let message_id = <[u8; 32]>::try_from(&raw_message_id[..]).expect("Could not calculate message ID from receipt fields"); + + message_id.into() + } + + fn decode_type(&mut self, ty_id: usize, data: Vec) -> anyhow::Result<()> { + let decoder = ABIDecoder::default(); + match ty_id { + #(#decoders),* + _ => { + debug!("Unknown type ID; check ABI to make sure types are correct."); + }, + } + Ok(()) + } + + pub fn decode_predicate(&mut self, data: Vec, template_id: &str) -> anyhow::Result<()> { + let predicate: IndexerPredicate = bincode::deserialize(&data) + .expect("Could not deserialize predicate from bytes"); + self.predicates_decoded.add(template_id.to_string(), predicate); + Ok(()) + } + + #predicate_decoder_fns + + pub fn decode_block(&mut self, data: BlockData) { + self.blockdata_decoded.push(data); + } + + pub fn decode_return_type(&mut self, sel: u64, data: Vec) -> anyhow::Result<()> { + let ty_id = self.selector_to_type_id(sel); + self.decode_type(ty_id, data)?; + Ok(()) + } + + pub fn decode_logdata(&mut self, rb: usize, data: Vec) -> anyhow::Result<()> { + match rb { + #(#contract_log_decoders),* + _ => debug!("Unknown logged type ID; check ABI to make sure that logged types are correct.") + } + Ok(()) + } + + pub fn decode_messagedata(&mut self, type_id: u64, data: Vec) -> anyhow::Result<()> { + match type_id { + #(#message_decoders),* + _ => debug!("Unknown message type ID; check ABI to make sure that message types are correct.") + } + Ok(()) + } + + pub fn dispatch(&self) -> anyhow::Result<()> { + #(#abi_dispatchers)* + + unsafe { + if !ERROR_MESSAGE.is_empty() { + anyhow::bail!(ERROR_MESSAGE.clone()); + } else { + Ok(()) + } + } + } + } + } +} + +/// `TokenStream` used to build the transaction handler in the `handle_events` entrypoint. +/// +/// This triggers the `Decoder` based on the supplied inputs (i.e., the outputs received from Fuel client). +pub fn process_transaction_tokens( + contract_subscription: TokenStream, + predicate_block: TokenStream, +) -> TokenStream { + quote! { + let mut process_transaction = |decoder: &mut Decoders, tx: fuel::TransactionData| -> anyhow::Result<()> { + let tx_id = tx.id; + + #predicate_block + + let mut return_types = Vec::new(); + let mut callees = HashSet::new(); + + for receipt in tx.receipts { + match receipt { + fuel::Receipt::Call { id: contract_id, amount, asset_id, gas, param1, to: id, .. } => { + #contract_subscription + + let fn_name = decoder.selector_to_fn_name(param1); + return_types.push(param1); + callees.insert(id); + + let data = serialize( + &Call { + contract_id: ContractId::from(<[u8; 32]>::from(contract_id)), + to: ContractId::from(<[u8; 32]>::from(id)), + amount, + asset_id: AssetId::from(<[u8; 32]>::from(asset_id)), + gas, + fn_name + } + ); + let ty_id = Call::type_id(); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::Log { id, ra, rb, .. } => { + #contract_subscription + let ty_id = Log::type_id(); + let data = serialize( + &Log { + contract_id: ContractId::from(<[u8; 32]>::from(id)), + ra, + rb + } + ); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::LogData { rb, data, ptr, len, id, .. } => { + #contract_subscription + decoder.decode_logdata(rb as usize, data.unwrap_or(Vec::::new()))?; + } + fuel::Receipt::Return { id, val, pc, is } => { + #contract_subscription + if callees.contains(&id) { + let ty_id = Return::type_id(); + let data = serialize( + &Return { + contract_id: ContractId::from(<[u8; 32]>::from(id)), + val, + pc, + is + } + ); + decoder.decode_type(ty_id, data)?; + } + } + fuel::Receipt::ReturnData { data, id, .. } => { + #contract_subscription + if callees.contains(&id) { + let selector = return_types.pop().expect("No return type available. <('-'<)"); + decoder.decode_return_type(selector, data.unwrap_or(Vec::::new()))?; + } + } + fuel::Receipt::MessageOut { sender, recipient, amount, nonce, len, digest, data, .. } => { + let sender = Address::from(<[u8; 32]>::from(sender)); + let recipient = Address::from(<[u8; 32]>::from(recipient)); + let message_id = decoder.compute_message_id(&sender, &recipient, nonce, amount, data.clone()); + + // It's possible that the data field was generated from an empty Sway `Bytes` array + // in the send_message() instruction in which case the data field in the receipt will + // have no type information or data to decode. Thus, we check for a None value or + // an empty byte vector; if either condition is present, then we decode to a unit struct instead. + let (type_id, data) = data + .map_or((u64::MAX, Vec::::new()), |buffer| { + if buffer.is_empty() { + (u64::MAX, Vec::::new()) + } else { + let (type_id_bytes, data_bytes) = buffer.split_at(8); + let type_id = u64::from_be_bytes( + <[u8; 8]>::try_from(type_id_bytes) + .expect("Could not get type ID for data in MessageOut receipt") + ); + let data = data_bytes.to_vec(); + (type_id, data) + } + }); + + decoder.decode_messagedata(type_id, data.clone())?; + + let ty_id = MessageOut::type_id(); + let data = serialize( + &MessageOut { + message_id, + sender, + recipient, + amount, + nonce, + len, + digest, + data + } + ); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::ScriptResult { result, gas_used } => { + let ty_id = ScriptResult::type_id(); + let data = serialize(&ScriptResult{ result: u64::from(result), gas_used }); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::Transfer { id, to, asset_id, amount, pc, is, .. } => { + #contract_subscription + let ty_id = Transfer::type_id(); + let data = serialize( + &Transfer { + contract_id: ContractId::from(<[u8; 32]>::from(id)), + to: ContractId::from(<[u8; 32]>::from(to)), + asset_id: AssetId::from(<[u8; 32]>::from(asset_id)), + amount, + pc, + is + } + ); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::TransferOut { id, to, asset_id, amount, pc, is, .. } => { + #contract_subscription + let ty_id = TransferOut::type_id(); + let data = serialize( + &TransferOut { + contract_id: ContractId::from(<[u8; 32]>::from(id)), + to: Address::from(<[u8; 32]>::from(to)), + asset_id: AssetId::from(<[u8; 32]>::from(asset_id)), + amount, + pc, + is + } + ); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::Panic { id, reason, .. } => { + #contract_subscription + let ty_id = Panic::type_id(); + let data = serialize( + &Panic { + contract_id: ContractId::from(<[u8; 32]>::from(id)), + reason: *reason.reason() as u32 + } + ); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::Revert { id, ra, .. } => { + #contract_subscription + let ty_id = Revert::type_id(); + let data = serialize( + &Revert { + contract_id: ContractId::from(<[u8; 32]>::from(id)), + error_val: u64::from(ra & 0xF) + } + ); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::Mint { sub_id, contract_id, val, pc, is } => { + let ty_id = Mint::type_id(); + let data = serialize( + &Mint { + sub_id: AssetId::from(<[u8; 32]>::from(sub_id)), + contract_id: ContractId::from(<[u8; 32]>::from(contract_id)), + val, + pc, + is + } + ); + decoder.decode_type(ty_id, data)?; + } + fuel::Receipt::Burn { sub_id, contract_id, val, pc, is } => { + let ty_id = Burn::type_id(); + let data = serialize( + &Burn { + sub_id: AssetId::from(<[u8; 32]>::from(sub_id)), + contract_id: ContractId::from(<[u8; 32]>::from(contract_id)), + val, + pc, + is + } + ); + decoder.decode_type(ty_id, data)?; + } + _ => { + info!("This type is not handled yet. (>'.')>"); + } + } + } + + Ok(()) + }; + } +} + +/// `TokenStream` used to augment the `process_transaction_tokens` `TokenStream` with logic +/// specific to the handling of predicates in the `handle_events` entrypoint. +/// +/// If predicates are not enabled, the AST will not be augmented with these tokens. +pub fn transaction_predicate_tokens(manifest: &Manifest) -> TokenStream { + let configurable_names = predicate_inputs_names_map(manifest); + let verification_tokens = manifest + .predicates() + .map(|p| { + p.templates() + .map(|t| { + t.iter().map(|t| { + + let inputs_name = + format_ident! { "{}", predicate_inputs_name(&t.name()) }; + let configurables_name = + format_ident! { "{}", configurables_name(&t.name()) }; + + let abi = get_json_abi(Some(t.abi())) + .expect("Could not derive predicate JSON ABI."); + + let abi_types = abi + .types + .iter() + .map(|typ| (typ.type_id, typ.clone())) + .collect::>(); + + let inputs_fields = abi + .configurables + .iter() + .flatten() + .map(|c| { + let ty_id = c.application.type_id; + let name = configurable_names + .get(&ty_id) + .expect("Could not find configurable naming."); + + format_ident! {"{}", name } + }) + .collect::>(); + + let chained_encoder_funcs = abi.configurables + .iter() + .flatten() + .map(|configurable| { + let typ = abi_types.get(&configurable.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(configurable) + .expect("Cannot use unit types '()' in configurables."); + let arg = configurable_names + .get(&configurable.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 #clone) + } + }); + + quote! { + PredicatesInputs::#inputs_name(#inputs_name { #(#inputs_fields),*, .. }) => { + let configurables = #configurables_name::new()#(#chained_encoder_funcs)*; + let mut predicate = SDKPredicate::from_code(indexer_predicate.bytecode().clone(), BETA4_CHAIN_ID) + .with_configurables(configurables); + // Convert the SDK predicate to the indexer-friendly predicate + let predicate = Predicate::from(predicate); + + if predicate.address() == indexer_predicate.coin_output().owner() { + // IndexerPredicateEntity will automatically save the PredicateCoinOutputEntity + let predicate_entity = IndexerPredicateEntity::from(indexer_predicate.clone()); + predicate_entity.save(); + } + } + } + }).collect::>() + }).unwrap_or_default() + }).unwrap_or_default(); + + let decode_inputs_tokens = manifest + .predicates() + .map(|p| { + p.templates() + .map(|t| { + t.iter().map(|t| { + let name = predicate_inputs_name(&t.name()); + let template_id = t.id().to_string(); + let ident = format_ident! { "{}", name }; + let fn_name = format_ident! { "decode_{}", name.to_lowercase() }; + + quote! { + #template_id => { + let obj = #ident::new(predicate_data.to_owned()); + decoder.#fn_name(obj).expect("Could not decode predicate input."); + }, + } + }).collect::>() + }).unwrap_or_default() + }).unwrap_or_default(); + + let inputs_match_tokens = manifest + .predicates() + .map(|p| { + p.templates() + .map(|t| { + t.iter() + .map(|t| { + let template_id = t.id().to_string(); + let name = predicate_inputs_name(&t.name()); + let ident = format_ident! { "{}", name }; + quote! { + #template_id => { + let obj = #ident::new(configurables); + Some(PredicatesInputs::#ident(obj)) + }, + } + }) + .collect::>() + }) + .unwrap_or_default() + }) + .unwrap_or_default(); + + let template_ids = manifest + .predicates() + .map(|p| { + p.templates() + .map(|t| { + t.iter() + .map(|t| { + let template_id = t.id().to_string(); + quote! { #template_id.to_string() } + }) + .collect::>() + }) + .unwrap_or_default() + }) + .unwrap_or_default(); + + quote! { + match &tx.transaction { + // Part 1: Finding and saving any submitted predicates in this transaction + fuel::Transaction::Script(script) => { + let fuel::Script { + inputs, + outputs, + witnesses, + .. + } = script; + + let template_ids = vec![#(#template_ids),*]; + + let submitted_predicates = witnesses + .iter() + .map(|w| PredicateWitnessData::try_from(w.to_owned())) + .filter_map(Result::ok) + .filter_map(|pwd| { + let template_id = pwd.template_id().to_string(); + if template_ids.contains(&template_id) { + Some(pwd) + } else { + None + } + }) + .map(|data| { + IndexerPredicate::from_witness( + data.to_owned(), + tx_id, + outputs[data.output_index() as usize].to_owned(), + ) + }) + .collect::>(); + + submitted_predicates.iter().for_each(|indexer_predicate| { + let template_id = indexer_predicate.template_id().to_string(); + let configurables = indexer_predicate.configurables().to_owned(); + let predicate_inputs = match template_id.as_str() { + #(#inputs_match_tokens)* + _ => { + Logger::error("Unknown predicate template ID; check ABI to make sure that predicate IDs are correct."); + None + } + }; + + + if let Some(predicate_inputs) = predicate_inputs { + match predicate_inputs { + #(#verification_tokens)* + _ => Logger::error("Unrecognized configurable type."), + } + + } + }); + + // Part 2: Finding any spent predicates in this transaction + inputs.iter().for_each(|i| { + match i { + fuel::Input::Coin(coin) => { + let fuel::InputCoin { + utxo_id, + owner, + amount, + asset_id, + predicate: predicate_code, + predicate_data, + .. + } = coin; + + let utxos = PredicateCoinOutputEntity::find_many(PredicateCoinOutputEntity::owner().eq(owner.to_owned())); + utxos.iter().for_each(|utxo| { + if let Some(predicate_entity) = IndexerPredicateEntity::find(IndexerPredicateEntity::coin_output().eq(utxo.id.clone())) { + let indexer_predicate = IndexerPredicate::from(predicate_entity); + let template_id = indexer_predicate.template_id().to_string(); + let data = bincode::serialize(&indexer_predicate).expect("Could not serialize predicate."); + decoder.decode_predicate(data, template_id.as_str()).expect("Could not decode predicate."); + + match template_id.as_str() { + #(#decode_inputs_tokens)* + _ => { + Logger::error("Unknown predicate template ID; check ABI to make sure that predicate IDs are correct."); + } + } + } + }); + } + _ => { + debug!("Unsupported predicate input type."); + } + } + }); + } + _ => { + debug!("Unsupported predicate transaction type."); + } + } + } +} + +/// `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 InputDecoder { + fn decode_type(&self, ty_id: usize, data: Vec) -> anyhow::Result<()>; + } + }; + + if let Some(p) = predicates { + if p.is_empty() { + return output; + } + + let inputs_namings = predicate_inputs_names_map(manifest); + + let mut configurable_variants = Vec::new(); + + let names = p + .templates() + .map(|t| t.iter().map(|t| t.name()).collect::>()) + .unwrap_or_default(); + let paths = p + .templates() + .map(|t| t.iter().map(|t| t.abi()).collect::>()) + .unwrap_or_default(); + + names.into_iter().zip(paths).for_each(|(name, abi_path)| { + let abi = get_json_abi(Some(abi_path)) + .expect("Could not derive predicate JSON ABI."); + + let predicate_types = abi + .types + .iter() + .map(|typ| (typ.type_id, typ.clone())) + .collect::>(); + + let main = abi + .functions + .iter() + .find(|f| f.name == "main") + .expect("ABI missing main function."); + + let inputs_struct_fields = main.inputs.iter().map(|input| { + let ty_id = input.type_id; + let typ = predicate_types.get(&ty_id).expect("Could not find type in ABI."); + let name = inputs_namings + .get(&ty_id) + .expect("Could not find configurable naming."); + let ty = typ.rust_tokens(); + let name = format_ident! { "{}", name }; + + quote! { + #name: #ty + } + }).collect::>(); + + let mut predicate_data_token_decoding = Vec::new(); + let mut inputs_struct_constructor = Vec::new(); + + let inputs_param_types = main.inputs.iter().map(|input| { + let ty_id = input.type_id; + let typ = predicate_types.get(&ty_id).expect("Could not find type in ABI."); + let ty = typ.rust_tokens(); + + quote! { + #ty::param_type() + } + }).collect::>(); + + main.inputs.iter().for_each(|input| { + let ty_id = input.type_id; + let typ = predicate_types + .get(&ty_id) + .expect("Predicate configurable type not found in the ABI."); + let name = inputs_namings + .get(&ty_id) + .expect("Could not find configurable naming."); + let ty = typ.rust_tokens(); + let name = format_ident! { "{}", name }; + + predicate_data_token_decoding.push(quote! { + let #name = { + let token = tokens.next().expect("Could not get token from decoded data"); + let obj: #ty = #ty::from_token(token.to_owned()).expect("Could not convert token to type."); + obj + }; + }); + + inputs_struct_constructor.push(quote!{ + #name + }); + }); + + let name = predicate_inputs_name(&name); + let ident = format_ident! { "{}", name }; + configurable_variants.push(ident.clone()); + + output = quote! { + #output + + #[derive(Debug, Clone)] + pub struct #ident { + #(#inputs_struct_fields),* + } + + impl #ident { + pub fn new(data: Vec) -> Self { + let mut left = 0usize; + let decoder = ABIDecoder::default(); + let tokens = decoder.decode_multiple(&[#(#inputs_param_types),*], &data).expect("Could not decode predicate witness data"); + let mut tokens = tokens.iter(); + + #(#predicate_data_token_decoding)* + Self { + #(#inputs_struct_constructor),* + } + } + } + + impl TypeId for #ident { + fn type_id() -> usize { + type_id(FUEL_TYPES_NAMESPACE, #name) as usize + } + } + } + }); + + output = quote! { + #output + + enum PredicatesInputs { + #(#configurable_variants(#configurable_variants)),* + } + }; + }; + + output +} + +/// Generate a set of tokens for converting predicate types between their ABI-like type +/// (e.g., `IndexerPredicate`) and their Entity-like type (e.g., `IndexerPredicateEntity`). +pub fn predicate_entity_impl_tokens() -> TokenStream { + quote! { + impl From for PredicateCoinOutputEntity { + fn from(p: PredicateCoinOutput) -> Self { + let owner = p.owner().to_owned(); + let amount = p.amount().to_owned(); + let asset_id = p.asset_id().to_owned(); + + Self::new(owner, amount, asset_id) + } + } + + impl From for PredicateCoinOutput { + fn from(entity: PredicateCoinOutputEntity) -> Self { + let PredicateCoinOutputEntity { + owner, + amount, + asset_id, + .. + } = entity; + + Self::new(owner, amount, asset_id) + } + } + + impl From for IndexerPredicateEntity { + fn from(p: IndexerPredicate) -> Self { + let coin_output = p.coin_output(); + let spent_tx_id = p.spent_tx_id(); + let unspent_tx_id = p.unspent_tx_id(); + let configurables = p.configurables(); + let template_id = p.template_id(); + let output_index = p.output_index(); + let template_name = p.template_name(); + let bytecode = p.bytecode(); + + let coin_output = PredicateCoinOutputEntity::from(coin_output.to_owned()).get_or_create(); + + Self::new( + template_name.to_owned(), + configurables.to_owned(), + template_id.to_owned(), + output_index.to_owned(), + coin_output.id.clone(), + unspent_tx_id.to_owned(), + spent_tx_id.to_owned(), + bytecode.to_owned(), + ) + } + } + + impl From for IndexerPredicate { + fn from(entity: IndexerPredicateEntity) -> Self { + let IndexerPredicateEntity { + configurables, + unspent_tx_id, + spent_tx_id, + coin_output, + template_id, + output_index, + template_name, + bytecode, + .. + } = entity; + + let coin_output = PredicateCoinOutputEntity::load(coin_output).expect("Could not load coin output entity."); + let coin_output = PredicateCoinOutput::from(coin_output); + + Self::new( + output_index, + configurables.to_vec(), + template_name, + template_id, + coin_output, + unspent_tx_id, + spent_tx_id, + bytecode, + ) + } + } + } +} + +/// `TokenStream` used to build args passed to indexer handlers. +pub fn dispatcher_tokens(dispatcher_name: &Ident) -> TokenStream { + let name = dispatcher_name.to_string(); + match name.as_str() { + // Since predicates are contained in the container `Predicates`, we pass the entire predicate container + // to the indexer handler (rather than just a single predicate). + "predicates_decoded" => { + quote! { self.#dispatcher_name.clone() } + } + _ => { + quote! { self.#dispatcher_name[0].clone() } + } + } +} + +/// `TokenStream` used to build a set of decoder functions for each predicate template. +/// +/// Since not every indexer will use predicates, we only generate these functions if predicates are enabled. +pub fn predicate_decoder_fn_tokens(manifest: &Manifest) -> TokenStream { + let mut output = quote! {}; + manifest.predicates().map(|p| { + p.templates().map(|t| { + t.iter().for_each(|t| { + let name = predicate_inputs_name(&t.name()); + let ident = format_ident! { "{}", name }; + let name = name.to_lowercase(); + let decoded_ident = format_ident! { "{}_decoded", name }; + let fn_name = format_ident! { "decode_{}", name }; + + output = quote! { + #output + + pub fn #fn_name(&mut self, data: #ident) -> anyhow::Result<()> { + self.#decoded_ident.push(data); + Ok(()) + } + }; + }) + }) + }); + + output +} diff --git a/packages/fuel-indexer-macros/src/wasm.rs b/packages/fuel-indexer-macros/src/wasm.rs index 67a3dff78..e5251c900 100644 --- a/packages/fuel-indexer-macros/src/wasm.rs +++ b/packages/fuel-indexer-macros/src/wasm.rs @@ -1,9 +1,9 @@ +use fuel_indexer_lib::manifest::Manifest; +use proc_macro2::TokenStream; use quote::quote; -/// Generate the handler block for the wasm execution environment. -pub fn handler_block_wasm( - handler_block: proc_macro2::TokenStream, -) -> proc_macro2::TokenStream { +/// Generate the `handle_events` WASM entrypoint handler block. +pub fn handler_block(block: TokenStream, handler_fns: TokenStream) -> TokenStream { let wasm_prelude = wasm_prelude(); let panic_hook = panic_hook(); @@ -40,10 +40,12 @@ pub fn handler_block_wasm( }; core::mem::forget(bytes); - #handler_block + #block Ok(()) } + + #handler_fns } } @@ -52,7 +54,7 @@ pub fn handler_block_wasm( /// When a panic occurs, the message is stored in a `static mut` `String` and a /// `WasmIndexerError::Panic` error code is returned. The message is then /// retrieved by the indexer service and logged. -fn panic_hook() -> proc_macro2::TokenStream { +fn panic_hook() -> TokenStream { quote! { static mut ERROR_MESSAGE: String = String::new(); @@ -89,7 +91,7 @@ fn panic_hook() -> proc_macro2::TokenStream { /// These imports are placed below the top-level lib imports, so any /// dependencies imported here will only be within the scope of the /// indexer module, not within the scope of the entire lib module. -fn wasm_prelude() -> proc_macro2::TokenStream { +fn wasm_prelude() -> TokenStream { quote! { use fuel_indexer_utils::plugin::anyhow::{self, Context}; use alloc::{format, vec, vec::Vec}; @@ -100,8 +102,27 @@ fn wasm_prelude() -> proc_macro2::TokenStream { use fuel_indexer_utils::plugin::{serde_json, serialize, deserialize, bincode}; use fuel_indexer_utils::plugin::serde::{Deserialize, Serialize}; use fuels::{ + accounts::predicate::{Predicate as SDKPredicate}, core::{codec::{ABIDecoder}, Configurables, traits::{Parameterize, Tokenizable}}, types::{param_types::ParamType}, + prelude::abigen, }; } } + +/// Generate the predicate-specific handler block. +pub fn predicate_handler_block(manifest: &Manifest, block: TokenStream) -> TokenStream { + let mut output = quote! {}; + let should_run = manifest + .predicates() + .map(|p| !p.is_empty()) + .unwrap_or(false); + + if should_run { + output = quote! { + #block + } + } + + output +} diff --git a/packages/fuel-indexer-plugin/Cargo.toml b/packages/fuel-indexer-plugin/Cargo.toml index 8c991313e..928340a87 100644 --- a/packages/fuel-indexer-plugin/Cargo.toml +++ b/packages/fuel-indexer-plugin/Cargo.toml @@ -20,6 +20,8 @@ fuel-indexer-schema = { workspace = true, default-features = false } fuel-indexer-types = { workspace = true } getrandom = { version = "0.2", features = ["js"] } hex = "0.4" +proc-macro2 = "1.0" +quote = "1.0" serde = { workspace = true } serde_json = { workspace = true } sha2 = { version = "0.10" } diff --git a/packages/fuel-indexer-schema/src/lib.rs b/packages/fuel-indexer-schema/src/lib.rs index b399618b4..78f1f4f84 100644 --- a/packages/fuel-indexer-schema/src/lib.rs +++ b/packages/fuel-indexer-schema/src/lib.rs @@ -166,7 +166,7 @@ impl FtColumn { }, FtColumn::Json(value) => match value { Some(val) => { - let x = &val.0; + let x: &str = val.as_ref(); format!("'{x}'") } None => String::from(NULL_VALUE), @@ -280,7 +280,7 @@ mod tests { let int4 = FtColumn::I32(Some(i32::from_le_bytes([0x78; 4]))); let int64 = FtColumn::I64(Some(i64::from_le_bytes([0x78; 8]))); let int8 = FtColumn::I64(Some(i64::from_le_bytes([0x78; 8]))); - let json = FtColumn::Json(Some(Json(r#"{"hello":"world"}"#.to_string()))); + let json = FtColumn::Json(Some(Json::new(r#"{"hello":"world"}"#.to_string()))); let r#bool = FtColumn::Boolean(Some(true)); let r#enum = FtColumn::Enum(Some(String::from("hello"))); let uint1 = FtColumn::U8(Some(u8::from_le_bytes([0x78; 1]))); diff --git a/packages/fuel-indexer-tests/components/fuel-node/src/main.rs b/packages/fuel-indexer-tests/components/fuel-node/src/main.rs index 2f83adb36..ce10bac49 100644 --- a/packages/fuel-indexer-tests/components/fuel-node/src/main.rs +++ b/packages/fuel-indexer-tests/components/fuel-node/src/main.rs @@ -4,8 +4,8 @@ use fuels::macros::abigen; use std::path::{Path, PathBuf}; abigen!(Contract( - name = "FuelIndexerTest", - abi = "packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json" + name = "FuelIndexer", + abi = "packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json" )); #[derive(Debug, Parser, Clone)] @@ -38,11 +38,11 @@ async fn main() -> Result<(), Box> { Path::new(&manifest_dir) .join("..") .join("..") - .join("contracts") - .join("fuel-indexer-test") + .join("sway") + .join("test-contract1") .join("out") .join("debug") - .join("fuel-indexer-test.bin") + .join("test-contract1.bin") }); let host = opts diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock b/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock deleted file mode 100644 index 5f646df66..000000000 --- a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.lock +++ /dev/null @@ -1,13 +0,0 @@ -[[package]] -name = "core" -source = "path+from-root-AD80769CAE44474A" - -[[package]] -name = "fuel-indexer-test" -source = "member" -dependencies = ["std"] - -[[package]] -name = "std" -source = "git+https://github.com/fuellabs/sway?tag=v0.46.0#e75f14b03636bc96751a6760304a1a6d3eb5937d" -dependencies = ["core"] 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 e7d34ebf0..9bd8846c2 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 @@ -1,11 +1,21 @@ namespace: fuel_indexer_test fuel_client: ~ -graphql_schema: packages/fuel-indexer-tests/indexers/fuel-indexer-test/schema/fuel_indexer_test.graphql -abi: packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json +schema: packages/fuel-indexer-tests/indexers/fuel-indexer-test/schema/fuel_indexer_test.graphql start_block: ~ end_block: ~ -contract_id: fuel17fpcf8dmhdfhs008llq7cy4p66jzz5452mgmgc8yzwsfw62dy37stzyegh +contract: + abi: packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json + subscriptions: + - fuel1vrwlw55qkc8xgn5sljjlawhlkqjgstczqwcyakx8a80c42tpqc8qv23p4k identifier: index1 module: wasm: target/wasm32-unknown-unknown/release/fuel_indexer_test.wasm -resumable: true \ No newline at end of file +resumable: true +predicates: + templates: + - name: TestPredicate1 + abi: packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-abi.json + id: 0xcfd60aa414972babde16215e0cb5a2739628831405a7ae81a9fc1d2ebce87145 + - name: TestPredicate2 + 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 538dd4a50..b767a0a12 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 @@ -1,5 +1,4 @@ extern crate alloc; - use fuel_indexer_utils::prelude::*; #[indexer( @@ -13,8 +12,12 @@ mod fuel_indexer_test { let input_data = r#"{"foo":"bar"}"#.to_string(); for _tx in block_data.transactions.iter() { - TxEntity::new(block.id.clone(), Json(input_data.clone()), block_data.time) - .get_or_create(); + TxEntity::new( + block.id.clone(), + Json::new(input_data.clone()), + block_data.time, + ) + .get_or_create(); } } @@ -146,7 +149,7 @@ mod fuel_indexer_test { block_data: BlockData, ) { info!( - "fuel_indexer_test_multiargs handling Pung, Pong, Ping, and BlockData events." + "fuel_indexer_test_multiargs handling Pung, Pong, Ping, and BlockData events.." ); BlockEntity::new(block_data.height, block_data.time).get_or_create(); @@ -693,4 +696,11 @@ mod fuel_indexer_test { .unwrap(); } } + + fn fuel_indexer_test_predicates( + _predicates: Predicates, + _inputs: TestPredicate1Inputs, + ) { + info!("fuel_indexer_test_predicates handling trigger_predicates event"); + } } diff --git a/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.yaml b/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.yaml index ab4c5a140..520c674a3 100644 --- a/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.yaml +++ b/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.yaml @@ -1,7 +1,11 @@ namespace: test_namespace identifier: simple_wasm_executor -abi: packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json -graphql_schema: packages/fuel-indexer-tests/indexers/simple-wasm/schema/simple_wasm.graphql -contract_id: ~ +contract: + abi: packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-abi.json + subscriptions: ~ +schema: packages/fuel-indexer-tests/indexers/simple-wasm/schema/simple_wasm.graphql module: wasm: target/wasm32-unknown-unknown/release/simple_wasm.wasm +predicates: + abis: ~ + templates: ~ diff --git a/packages/fuel-indexer-tests/src/fixtures.rs b/packages/fuel-indexer-tests/src/fixtures.rs index c7925edb4..1a365e8f0 100644 --- a/packages/fuel-indexer-tests/src/fixtures.rs +++ b/packages/fuel-indexer-tests/src/fixtures.rs @@ -1,6 +1,4 @@ -use crate::{ - assets, defaults, utils::update_test_manifest_asset_paths, TestError, WORKSPACE_ROOT, -}; +use crate::{assets, defaults, TestError, WORKSPACE_ROOT}; use actix_service::Service; use actix_web::test; use axum::routing::Router; @@ -40,8 +38,8 @@ use tokio::{ use tracing_subscriber::filter::EnvFilter; abigen!(Contract( - name = "FuelIndexerTest", - abi = "packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-abi.json" + name = "FuelIndexer", + abi = "packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json" )); pub struct TestPostgresDb { @@ -93,8 +91,7 @@ pub async fn setup_indexing_test_components( let db = TestPostgresDb::new().await.unwrap(); let mut service = indexer_service_postgres(Some(&db.url), config).await; - let mut manifest = Manifest::try_from(assets::FUEL_INDEXER_TEST_MANIFEST).unwrap(); - update_test_manifest_asset_paths(&mut manifest); + let manifest = assets::test_indexer_updated_manifest(); service .register_indexer_from_manifest( @@ -119,8 +116,7 @@ pub async fn setup_web_test_components( let db = TestPostgresDb::new().await.unwrap(); let mut service = indexer_service_postgres(Some(&db.url), config.clone()).await; - let mut manifest = Manifest::try_from(assets::FUEL_INDEXER_TEST_MANIFEST).unwrap(); - update_test_manifest_asset_paths(&mut manifest); + let manifest = assets::test_indexer_updated_manifest(); service .register_indexer_from_manifest( @@ -346,22 +342,22 @@ pub async fn setup_example_test_fuel_node() -> Result<(), ()> { let wallet_path = Path::new(WORKSPACE_ROOT).join("test-chain-config.json"); let contract_bin_path = Path::new(WORKSPACE_ROOT) - .join("contracts") - .join("fuel-indexer-test") + .join("sway") + .join("test-contract1") .join("out") .join("debug") - .join("fuel-indexer-test.bin"); + .join("test-contract1.bin"); setup_test_fuel_node(wallet_path, Some(contract_bin_path), None).await } pub fn get_test_contract_id() -> Bech32ContractId { let contract_bin_path = Path::new(WORKSPACE_ROOT) - .join("contracts") - .join("fuel-indexer-test") + .join("sway") + .join("test-contract1") .join("out") .join("debug") - .join("fuel-indexer-test.bin"); + .join("test-contract1.bin"); let loaded_contract = Contract::load_from( contract_bin_path.as_os_str().to_str().unwrap(), @@ -413,7 +409,7 @@ pub async fn indexer_service_postgres( } pub async fn connect_to_deployed_contract( -) -> Result, Box> { +) -> Result, Box> { let wallet_path = Path::new(WORKSPACE_ROOT).join("test-chain-config.json"); let wallet_path_str = wallet_path.as_os_str().to_str().unwrap(); let mut wallet = @@ -433,7 +429,7 @@ pub async fn connect_to_deployed_contract( let contract_id: Bech32ContractId = Bech32ContractId::from(fuels::types::ContractId::from(get_test_contract_id())); - let contract = FuelIndexerTest::new(contract_id.clone(), wallet); + let contract = FuelIndexer::new(contract_id.clone(), wallet); println!("Using contract at {contract_id}"); @@ -454,7 +450,7 @@ pub mod test_web { use fuels::types::bech32::Bech32ContractId; use std::path::Path; - use super::{tx_params, FuelIndexerTest}; + use super::{tx_params, FuelIndexer}; async fn fuel_indexer_test_blocks(state: web::Data>) -> impl Responder { let _ = state @@ -840,11 +836,11 @@ pub mod test_web { } pub struct AppState { - pub contract: FuelIndexerTest, + pub contract: FuelIndexer, } pub fn app( - contract: FuelIndexerTest, + contract: FuelIndexer, ) -> App< impl ServiceFactory< ServiceRequest, @@ -944,7 +940,7 @@ pub mod test_web { println!("Starting server at {}", defaults::WEB_API_ADDR); let _ = HttpServer::new(move || { - app(FuelIndexerTest::new(contract_id.clone(), wallet.clone())) + app(FuelIndexer::new(contract_id.clone(), wallet.clone())) }) .bind(defaults::WEB_API_ADDR) .unwrap() diff --git a/packages/fuel-indexer-tests/src/lib.rs b/packages/fuel-indexer-tests/src/lib.rs index 667f90b74..594523706 100644 --- a/packages/fuel-indexer-tests/src/lib.rs +++ b/packages/fuel-indexer-tests/src/lib.rs @@ -1,11 +1,11 @@ #[cfg(not(feature = "trybuild"))] pub mod fixtures; -pub const WORKSPACE_ROOT: &str = env!("CARGO_MANIFEST_DIR"); - use fuel_indexer_lib::config::IndexerConfigError; use thiserror::Error; +pub const WORKSPACE_ROOT: &str = env!("CARGO_MANIFEST_DIR"); + #[derive(Error, Debug)] pub enum TestError { #[error("Database error: {0:?}")] @@ -23,6 +23,9 @@ pub enum TestError { } pub mod assets { + use duct::cmd; + use fuel_indexer_lib::manifest::Manifest; + pub const FUEL_INDEXER_TEST_MANIFEST: &str = include_str!("./../indexers/fuel-indexer-test/fuel_indexer_test.yaml"); @@ -34,6 +37,53 @@ pub mod assets { pub const SIMPLE_WASM_WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/release/simple_wasm.wasm"); + + // NOTE: This is a hack to update the `manifest` with the proper absolute paths. + // This is already done in the #[indexer] attribute, but since these tests test + // modules that have already been compiled, we need to do this manually here. + // + // Doing this allows us to use the relative root of the the fuel-indexer/ + // repo for all test asset paths (i.e., we can simply reference all asset paths in + // in manifest files relative from 'fuel-indexer') + pub fn test_indexer_updated_manifest() -> Manifest { + // TODO: Write a CI check for this + let workspace_root = cmd!("cargo", "metadata", "--format-version=1") + .pipe(cmd!("json_pp")) + .pipe(cmd!("jq", ".workspace_root")) + .read() + .unwrap(); + let mut chars = workspace_root.chars(); + chars.next(); + chars.next_back(); + let workspace_root = chars.as_str().to_string(); + // Should mirror packages::fuel_indexer_tests::indexers::fuel_indexer_test::fuel_indexer_test.yaml + let content = format!( + r#" +namespace: fuel_indexer_test +fuel_client: ~ +schema: {workspace_root}/packages/fuel-indexer-tests/indexers/fuel-indexer-test/schema/fuel_indexer_test.graphql +start_block: ~ +end_block: ~ +contract: + abi: {workspace_root}/packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json + subscriptions: + - fuel1jjrj8zjyjc3s4qkw345mt57mwn56lnc9zwqnt5krrx9umwxacrvs2c3jyg +identifier: index1 +module: + wasm: {workspace_root}/target/wasm32-unknown-unknown/release/fuel_indexer_test.wasm +resumable: true +predicates: + templates: + - name: TestPredicate1 + abi: {workspace_root}/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-abi.json + id: 0xcfd60aa414972babde16215e0cb5a2739628831405a7ae81a9fc1d2ebce87145 + - name: TestPredicate2 + id: 0x1c83e1f094b47f14943066f6b6ca41ce5c3ae4e387c973e924564dac0227a896 + abi: {workspace_root}/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json +"# + ); + Manifest::try_from(content.as_str()).unwrap() + } } pub mod defaults { @@ -54,61 +104,3 @@ pub mod defaults { pub const MAX_BODY_SIZE: usize = 5242880; // 5MB in bytes pub const POSTGRES_URL: &str = "postgres://postgres:my-secret@localhost:5432"; } - -pub mod utils { - - use super::WORKSPACE_ROOT; - use fuel_indexer_lib::manifest::{Manifest, Module}; - use std::path::Path; - - // NOTE: This is a hack to update the `manifest` with the proper absolute paths. - // This is already done in the #[indexer] attribute, but since these tests test - // modules that have already been compiled, we need to do this manually here. - // - // Doing this allows us to use the relative root of the the fuel-indexer/ - // repo for all test asset paths (i.e., we can simply reference all asset paths in - // in manifest files relative from 'fuel-indexer') - pub fn update_test_manifest_asset_paths(manifest: &mut Manifest) { - let manifest_dir = Path::new(WORKSPACE_ROOT); - let graphql_schema = manifest_dir - .parent() - .unwrap() - .parent() - .unwrap() - .join(manifest.graphql_schema()) - .into_os_string() - .to_str() - .unwrap() - .to_string(); - - manifest.set_graphql_schema(graphql_schema); - - let abi = manifest_dir - .parent() - .unwrap() - .parent() - .unwrap() - .join(manifest.abi().unwrap()) - .into_os_string() - .to_str() - .unwrap() - .to_string(); - - manifest.set_abi(abi); - - let module = Module::Wasm( - manifest_dir - .parent() - .unwrap() - .parent() - .unwrap() - .join(manifest.module().to_string()) - .into_os_string() - .to_str() - .unwrap() - .to_string(), - ); - - manifest.set_module(module); - } -} diff --git a/packages/fuel-indexer-tests/contracts/README.md b/packages/fuel-indexer-tests/sway/README.md similarity index 100% rename from packages/fuel-indexer-tests/contracts/README.md rename to packages/fuel-indexer-tests/sway/README.md diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/.gitignore b/packages/fuel-indexer-tests/sway/simple-wasm/.gitignore similarity index 100% rename from packages/fuel-indexer-tests/contracts/simple-wasm/.gitignore rename to packages/fuel-indexer-tests/sway/simple-wasm/.gitignore diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/Forc.lock b/packages/fuel-indexer-tests/sway/simple-wasm/Forc.lock similarity index 100% rename from packages/fuel-indexer-tests/contracts/simple-wasm/Forc.lock rename to packages/fuel-indexer-tests/sway/simple-wasm/Forc.lock diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/Forc.toml b/packages/fuel-indexer-tests/sway/simple-wasm/Forc.toml similarity index 100% rename from packages/fuel-indexer-tests/contracts/simple-wasm/Forc.toml rename to packages/fuel-indexer-tests/sway/simple-wasm/Forc.toml diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json b/packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-abi.json similarity index 100% rename from packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json rename to packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-abi.json diff --git a/packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-flat-abi.json b/packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-flat-abi.json new file mode 100644 index 000000000..387305b6a --- /dev/null +++ b/packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-flat-abi.json @@ -0,0 +1,87 @@ +{ + "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 + } + ], + "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 + } + } + ] +} \ No newline at end of file diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-storage_slots.json b/packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-storage_slots.json similarity index 100% rename from packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-storage_slots.json rename to packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts-storage_slots.json diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts.bin b/packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts.bin similarity index 100% rename from packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts.bin rename to packages/fuel-indexer-tests/sway/simple-wasm/out/debug/contracts.bin diff --git a/packages/fuel-indexer-tests/contracts/simple-wasm/src/main.sw b/packages/fuel-indexer-tests/sway/simple-wasm/src/main.sw similarity index 100% rename from packages/fuel-indexer-tests/contracts/simple-wasm/src/main.sw rename to packages/fuel-indexer-tests/sway/simple-wasm/src/main.sw diff --git a/packages/fuel-indexer-tests/sway/test-contract1/.gitignore b/packages/fuel-indexer-tests/sway/test-contract1/.gitignore new file mode 100644 index 000000000..77d3844f5 --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-contract1/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/packages/fuel-indexer-tests/sway/test-contract1/Forc.lock b/packages/fuel-indexer-tests/sway/test-contract1/Forc.lock new file mode 100644 index 000000000..3248db2ba --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-contract1/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-BD9159206068AEC6" + +[[package]] +name = "std" +source = "git+https://github.com/fuellabs/sway?tag=v0.46.1#512a3386f8961185188302f391ccc96553d23a7a" +dependencies = ["core"] + +[[package]] +name = "test-contract1" +source = "member" +dependencies = ["std"] diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.toml b/packages/fuel-indexer-tests/sway/test-contract1/Forc.toml similarity index 77% rename from packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.toml rename to packages/fuel-indexer-tests/sway/test-contract1/Forc.toml index 39acf3d45..6517dfbf4 100644 --- a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/Forc.toml +++ b/packages/fuel-indexer-tests/sway/test-contract1/Forc.toml @@ -2,6 +2,6 @@ authors = ["Rashad Alston"] entry = "main.sw" license = "Apache-2.0" -name = "fuel-indexer-test" +name = "test-contract1" [dependencies] diff --git a/packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json b/packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json new file mode 100644 index 000000000..ce7b2896a --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-abi.json @@ -0,0 +1,923 @@ +{ + "types": [ + { + "typeId": 0, + "type": "()", + "components": [], + "typeParameters": null + }, + { + "typeId": 1, + "type": "(_, _)", + "components": [ + { + "name": "__tuple_element", + "type": 19, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 32, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 2, + "type": "(_, _)", + "components": [ + { + "name": "__tuple_element", + "type": 34, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 3, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 3, + "type": "(_, _, _)", + "components": [ + { + "name": "__tuple_element", + "type": 35, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 7, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 1, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 4, + "type": "(_, _, _)", + "components": [ + { + "name": "__tuple_element", + "type": 34, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 35, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 17, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 5, + "type": "[_; 3]", + "components": [ + { + "name": "__array_element", + "type": 36, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 6, + "type": "b256", + "components": null, + "typeParameters": null + }, + { + "typeId": 7, + "type": "bool", + "components": null, + "typeParameters": null + }, + { + "typeId": 8, + "type": "enum AnotherSimpleEnum", + "components": [ + { + "name": "Ping", + "type": 26, + "typeArguments": null + }, + { + "name": "Pung", + "type": 28, + "typeArguments": null + }, + { + "name": "Call", + "type": 12, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 9, + "type": "enum Identity", + "components": [ + { + "name": "Address", + "type": 20, + "typeArguments": null + }, + { + "name": "ContractId", + "type": 22, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 10, + "type": "enum NestedEnum", + "components": [ + { + "name": "Inner", + "type": 8, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 11, + "type": "enum Option", + "components": [ + { + "name": "None", + "type": 0, + "typeArguments": null + }, + { + "name": "Some", + "type": 14, + "typeArguments": null + } + ], + "typeParameters": [ + 14 + ] + }, + { + "typeId": 12, + "type": "enum SimpleEnum", + "components": [ + { + "name": "One", + "type": 0, + "typeArguments": null + }, + { + "name": "Two", + "type": 0, + "typeArguments": null + }, + { + "name": "Three", + "type": 0, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 13, + "type": "enum UserError", + "components": [ + { + "name": "Unauthorized", + "type": 0, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 14, + "type": "generic T", + "components": null, + "typeParameters": null + }, + { + "typeId": 15, + "type": "raw untyped ptr", + "components": null, + "typeParameters": null + }, + { + "typeId": 16, + "type": "str", + "components": null, + "typeParameters": null + }, + { + "typeId": 17, + "type": "str[12]", + "components": null, + "typeParameters": null + }, + { + "typeId": 18, + "type": "str[32]", + "components": null, + "typeParameters": null + }, + { + "typeId": 19, + "type": "str[5]", + "components": null, + "typeParameters": null + }, + { + "typeId": 20, + "type": "struct Address", + "components": [ + { + "name": "value", + "type": 6, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 21, + "type": "struct ComplexTupleStruct", + "components": [ + { + "name": "data", + "type": 2, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 22, + "type": "struct ContractId", + "components": [ + { + "name": "value", + "type": 6, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 23, + "type": "struct ExampleMessageStruct", + "components": [ + { + "name": "id", + "type": 35, + "typeArguments": null + }, + { + "name": "message", + "type": 18, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 24, + "type": "struct ExplicitQueryStruct", + "components": [ + { + "name": "id", + "type": 35, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 25, + "type": "struct Find", + "components": [], + "typeParameters": null + }, + { + "typeId": 26, + "type": "struct Ping", + "components": [ + { + "name": "id", + "type": 35, + "typeArguments": null + }, + { + "name": "value", + "type": 35, + "typeArguments": null + }, + { + "name": "message", + "type": 18, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 27, + "type": "struct Pong", + "components": [ + { + "name": "id", + "type": 35, + "typeArguments": null + }, + { + "name": "value", + "type": 35, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 28, + "type": "struct Pung", + "components": [ + { + "name": "id", + "type": 35, + "typeArguments": null + }, + { + "name": "value", + "type": 35, + "typeArguments": null + }, + { + "name": "is_pung", + "type": 7, + "typeArguments": null + }, + { + "name": "pung_from", + "type": 9, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 29, + "type": "struct RawVec", + "components": [ + { + "name": "ptr", + "type": 15, + "typeArguments": null + }, + { + "name": "cap", + "type": 35, + "typeArguments": null + } + ], + "typeParameters": [ + 14 + ] + }, + { + "typeId": 30, + "type": "struct SimpleQueryStruct", + "components": [ + { + "name": "id", + "type": 35, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 31, + "type": "struct SimpleTupleStruct", + "components": [ + { + "name": "data", + "type": 4, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 32, + "type": "struct TupleStructItem", + "components": [ + { + "name": "id", + "type": 35, + "typeArguments": null + }, + { + "name": "arr", + "type": 5, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 33, + "type": "struct Vec", + "components": [ + { + "name": "buf", + "type": 29, + "typeArguments": [ + { + "name": "", + "type": 14, + "typeArguments": null + } + ] + }, + { + "name": "len", + "type": 35, + "typeArguments": null + } + ], + "typeParameters": [ + 14 + ] + }, + { + "typeId": 34, + "type": "u32", + "components": null, + "typeParameters": null + }, + { + "typeId": 35, + "type": "u64", + "components": null, + "typeParameters": null + }, + { + "typeId": 36, + "type": "u8", + "components": null, + "typeParameters": null + } + ], + "functions": [ + { + "inputs": [], + "name": "trigger_burn", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": [ + { + "name": "payable", + "arguments": [] + } + ] + }, + { + "inputs": [], + "name": "trigger_callreturn", + "output": { + "name": "", + "type": 28, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_deeply_nested", + "output": { + "name": "", + "type": 30, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_enum", + "output": { + "name": "", + "type": 8, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [ + { + "name": "num", + "type": 35, + "typeArguments": null + } + ], + "name": "trigger_enum_error", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_explicit", + "output": { + "name": "", + "type": 24, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_find", + "output": { + "name": "", + "type": 25, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_generics", + "output": { + "name": "", + "type": 11, + "typeArguments": [ + { + "name": "", + "type": 26, + "typeArguments": null + } + ] + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_log", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_logdata", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_messageout", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": [ + { + "name": "payable", + "arguments": [] + } + ] + }, + { + "inputs": [], + "name": "trigger_mint", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_multiargs", + "output": { + "name": "", + "type": 26, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_panic", + "output": { + "name": "", + "type": 35, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_ping", + "output": { + "name": "", + "type": 26, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_ping_for_optional", + "output": { + "name": "", + "type": 26, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_pong", + "output": { + "name": "", + "type": 27, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_pure_function", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_revert", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_scriptresult", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_transfer", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": [ + { + "name": "payable", + "arguments": [] + } + ] + }, + { + "inputs": [], + "name": "trigger_transferout", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": [ + { + "name": "payable", + "arguments": [] + } + ] + }, + { + "inputs": [], + "name": "trigger_tuple", + "output": { + "name": "", + "type": 21, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [ + { + "name": "_v", + "type": 33, + "typeArguments": [ + { + "name": "", + "type": 36, + "typeArguments": null + } + ] + } + ], + "name": "trigger_vec_pong_calldata", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + }, + { + "inputs": [], + "name": "trigger_vec_pong_logdata", + "output": { + "name": "", + "type": 0, + "typeArguments": null + }, + "attributes": null + } + ], + "loggedTypes": [ + { + "logId": 0, + "loggedType": { + "name": "", + "type": 8, + "typeArguments": [] + } + }, + { + "logId": 1, + "loggedType": { + "name": "", + "type": 10, + "typeArguments": [] + } + }, + { + "logId": 2, + "loggedType": { + "name": "", + "type": 13, + "typeArguments": [] + } + }, + { + "logId": 3, + "loggedType": { + "name": "", + "type": 11, + "typeArguments": [ + { + "name": "", + "type": 26, + "typeArguments": [] + } + ] + } + }, + { + "logId": 4, + "loggedType": { + "name": "", + "type": 33, + "typeArguments": [ + { + "name": "", + "type": 26, + "typeArguments": [] + } + ] + } + }, + { + "logId": 5, + "loggedType": { + "name": "", + "type": 35, + "typeArguments": null + } + }, + { + "logId": 6, + "loggedType": { + "name": "", + "type": 28, + "typeArguments": [] + } + }, + { + "logId": 7, + "loggedType": { + "name": "", + "type": 28, + "typeArguments": [] + } + }, + { + "logId": 8, + "loggedType": { + "name": "", + "type": 27, + "typeArguments": [] + } + }, + { + "logId": 9, + "loggedType": { + "name": "", + "type": 35, + "typeArguments": null + } + }, + { + "logId": 10, + "loggedType": { + "name": "", + "type": 31, + "typeArguments": [] + } + }, + { + "logId": 11, + "loggedType": { + "name": "", + "type": 16, + "typeArguments": null + } + }, + { + "logId": 12, + "loggedType": { + "name": "", + "type": 33, + "typeArguments": [ + { + "name": "", + "type": 27, + "typeArguments": [] + } + ] + } + } + ], + "messagesTypes": [ + { + "messageId": 0, + "messageType": { + "name": "", + "type": 23, + "typeArguments": [] + } + } + ], + "configurables": [] +} \ No newline at end of file diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-storage_slots.json b/packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-storage_slots.json similarity index 100% rename from packages/fuel-indexer-tests/contracts/fuel-indexer-test/out/debug/fuel-indexer-test-storage_slots.json rename to packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1-storage_slots.json diff --git a/packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1.bin b/packages/fuel-indexer-tests/sway/test-contract1/out/debug/test-contract1.bin new file mode 100644 index 0000000000000000000000000000000000000000..51f75fb4287f1f5a8d6738ef4816d0b108c9366b GIT binary patch literal 5812 zcmd5=ZERat89sKL&S}?jsqM78nWmf6Nw21=auX*D9~Jk`zO%i=#J^bERC?@#4%^G?bxg9v9rdU!Ho9wnhZbq zZ*o={!@R#8RLJ(M*sSnZeuAkXa796q@%LRF^jC0%MIg-C!$d4#O-*)H= z9g-}fThS$w+YY};pZk&96uY|urq2kNjM4v#2J=u{Chxo=tBx89^Zx_k+H=P0g_HAglUu`OxZ^8e!V6>1J>#Kzkn%wv}L} z7%OC}7iXJWa*Tz{S#CSlH({==#cZr_8OAINAx4XF=D}J6Y-nqV`wZR88tO3jQO< z7E57@l{JvNR}rg-o{CpsW1DV;D@vTv{3(ogxNP3B-sXTAsz5K26%0Tp75Rs;&@(TpxBCUi`kQS83u6YzDf zo(fhFnriQ zsvZ0~y4KYx%-3sF0yTlfKI1icdZtd zT(wXa_cA1Op*t$-QWEm^I93Gr7&UOH+9`4x{_e+}r20d;CC*l$FYWCDAJ`ksVtfMQ zC_jr@w}!=hH})xVAKe?&`863ExeQBDz#DpmE2vWuS8b#C^(dx~h3W2#w}#~A${-b zsq0GP-YySw?eUx-y;Wn3^u0yiEz>*6-@aU|T&w5z;C=JwM140Y13Ubo761#XV9fnllo4CX;-`0$h8%(eQm{8VxDiSvDc_a z(sOtdz}J71J*U_0@ltH^!<`KQ(`JkCv67A$3yh6IUztbc6TCK9?bNq@jpI}!a#dk_ zt*E=M9l_mMq?&T;94K>N3!4eA*<8z&4>1RL4=QtjW5lCuOI>UckFtzU8S(i~ zo&y|{%z~6zkTPfGc-G~Q51IqR>*9N}9Oz%S=e9Y3x`X~6{duCS)$Y2A8l)zX17`G@ zdad(l=-F0a^I`eE9g%%^*jd#mf9M_P;C)9Q>T*9*^pp?%pX7`Fk1+DcqM8%+gaa5G zkm;vhPks}0QeCIHmZWb3 zj;WsjAL=I{UzfVQz~B26^J9+pBJjfscpB6jpza&(=uhQ4r18ExRy|7gyWR+!f78TZ za~w9~`vrC>xX&5z!8>?Ee}KG@J+M8?ct4w6*WYGceIxFANZeiOE2(cBM1N~^fQM?l zufH7>{cYS(p|jY_m}17M-nF{kz*h1H{4z-2bL4vkb_T3Qs;{DdMgK+d$Jk4CswEXN zLRNcieG}#eTdd$xM;v;@p+_3^rC;{116V_Us$21jn!&p_3|V47dh9s$rwZa#@1;k9 z(_;*CgS_9yyYgSuMT6XWuRKcMWdAc=BG9G&T{TMIRW4oj!7t{7v50pMxqxpB#0cNb z7>jQV#4luqYU?srd?Htph*3R{8bog312+|CYDPnh6qzrL=+EPLFUtni^x`)c2_ zYU~B3CRSKJiT)z3Q7$zGQX2i;hQINYi?G?FCh?|-^$l{}n>X3rwC18))W0(JEWMHM zDQsWW)^o86aeqktyD!c+u0Hy_Jl-H^@5xIl))}*|^Pk+sJQwo#&5s|ze>GC0 zVpi;Idr8pHcjU>4pz20B_Hp_YRc8KclypP>vHjx{2M!h{4?SL-nx2_`Vs5^)uz2{$ z(PMi$yQ1CHCgJCMJ+DnI6ibu&xr5s5YHL5Sj~y5EjXiRFhoEPs<=8Lm ztNqJ_UW_%#^;;6@7tihD=#B?5Oc)pb1t=+#+(&h^@3&nA5a%#3XF$KFdoSWC?XJ)2%uQC3#M4Kd41l{=& zIlhx<+vl?P67BlO`MpG+eDT6KAHVwaNup1^?Ee+f`!av#`(F)r|AEG*wp^bfdU*8G zd7|gud-*M*_nzL({r%nVy%MMK@n5{n!0HJyp)?dMB~NJ4}Od2Czdbr zb6;J3k?Y$%@D7jH13&xQ_i24Wd+8fQl{b?gCEEJShdxWR{H2~_L>b%4<)h+oJwdsA e&~dRI_=%vwu%!Kxa``CHZ^-dq1P%El4gM3Rj=i7& literal 0 HcmV?d00001 diff --git a/packages/fuel-indexer-tests/contracts/fuel-indexer-test/src/main.sw b/packages/fuel-indexer-tests/sway/test-contract1/src/main.sw similarity index 100% rename from packages/fuel-indexer-tests/contracts/fuel-indexer-test/src/main.sw rename to packages/fuel-indexer-tests/sway/test-contract1/src/main.sw diff --git a/packages/fuel-indexer-tests/sway/test-predicate1/.gitignore b/packages/fuel-indexer-tests/sway/test-predicate1/.gitignore new file mode 100644 index 000000000..77d3844f5 --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate1/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/packages/fuel-indexer-tests/sway/test-predicate1/Forc.lock b/packages/fuel-indexer-tests/sway/test-predicate1/Forc.lock new file mode 100644 index 000000000..23d9e0140 --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate1/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-BD9159206068AEC6" + +[[package]] +name = "std" +source = "git+https://github.com/fuellabs/sway?tag=v0.46.1#512a3386f8961185188302f391ccc96553d23a7a" +dependencies = ["core"] + +[[package]] +name = "test-predicate1" +source = "member" +dependencies = ["std"] diff --git a/packages/fuel-indexer-tests/sway/test-predicate1/Forc.toml b/packages/fuel-indexer-tests/sway/test-predicate1/Forc.toml new file mode 100644 index 000000000..9e9e77f5e --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate1/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Rashad Alston"] +entry = "main.sw" +license = "Apache-2.0" +name = "test-predicate1" + +[dependencies] diff --git a/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-abi.json b/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-abi.json new file mode 100644 index 000000000..dd932f35d --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-abi.json @@ -0,0 +1,117 @@ +{ + "types": [ + { + "typeId": 0, + "type": "()", + "components": [], + "typeParameters": null + }, + { + "typeId": 1, + "type": "bool", + "components": null, + "typeParameters": null + }, + { + "typeId": 2, + "type": "enum Predicate1SimpleEnum", + "components": [ + { + "name": "VariantOne", + "type": 0, + "typeArguments": null + }, + { + "name": "VariantTwo", + "type": 0, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 3, + "type": "struct Predicate1SimpleStruct", + "components": [ + { + "name": "field_1", + "type": 5, + "typeArguments": null + }, + { + "name": "field_2", + "type": 4, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 4, + "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, + "typeArguments": null + }, + { + "name": "some_struct", + "type": 3, + "typeArguments": null + }, + { + "name": "some_enum", + "type": 2, + "typeArguments": null + } + ], + "name": "main", + "output": { + "name": "", + "type": 1, + "typeArguments": null + }, + "attributes": null + } + ], + "loggedTypes": [], + "messagesTypes": [], + "configurables": [ + { + "name": "U8", + "configurableType": { + "name": "", + "type": 5, + "typeArguments": null + }, + "offset": 124 + }, + { + "name": "BOOL", + "configurableType": { + "name": "", + "type": 1, + "typeArguments": null + }, + "offset": 132 + } + ] +} \ No newline at end of file diff --git a/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-bin-root b/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-bin-root new file mode 100644 index 000000000..41ae03b7b --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1-bin-root @@ -0,0 +1 @@ +0x048029a18e3254862e2d4108fc9a9dcdcd2f71e0c6e37dad8f1b513845c8e685 \ No newline at end of file diff --git a/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1.bin b/packages/fuel-indexer-tests/sway/test-predicate1/out/debug/test-predicate1.bin new file mode 100644 index 0000000000000000000000000000000000000000..588bcfa96260aa7c5e02282d531cb3f4d439b0d3 GIT binary patch literal 140 zcmXS6U|@D bool { + u_8 == U8 && switch == BOOL +} diff --git a/packages/fuel-indexer-tests/sway/test-predicate2/.gitignore b/packages/fuel-indexer-tests/sway/test-predicate2/.gitignore new file mode 100644 index 000000000..77d3844f5 --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate2/.gitignore @@ -0,0 +1,2 @@ +out +target diff --git a/packages/fuel-indexer-tests/sway/test-predicate2/Forc.lock b/packages/fuel-indexer-tests/sway/test-predicate2/Forc.lock new file mode 100644 index 000000000..db0afd428 --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate2/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "core" +source = "path+from-root-BD9159206068AEC6" + +[[package]] +name = "std" +source = "git+https://github.com/fuellabs/sway?tag=v0.46.1#512a3386f8961185188302f391ccc96553d23a7a" +dependencies = ["core"] + +[[package]] +name = "test-predicate2" +source = "member" +dependencies = ["std"] diff --git a/packages/fuel-indexer-tests/sway/test-predicate2/Forc.toml b/packages/fuel-indexer-tests/sway/test-predicate2/Forc.toml new file mode 100644 index 000000000..abfb6800e --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate2/Forc.toml @@ -0,0 +1,7 @@ +[project] +authors = ["Rashad Alston"] +entry = "main.sw" +license = "Apache-2.0" +name = "test-predicate2" + +[dependencies] 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 new file mode 100644 index 000000000..bf8babc36 --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-abi.json @@ -0,0 +1,101 @@ +{ + "types": [ + { + "typeId": 0, + "type": "()", + "components": [], + "typeParameters": null + }, + { + "typeId": 1, + "type": "bool", + "components": null, + "typeParameters": null + }, + { + "typeId": 2, + "type": "enum PairAsset", + "components": [ + { + "name": "BTC", + "type": 0, + "typeArguments": null + }, + { + "name": "ETH", + "type": 0, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 3, + "type": "struct OrderPair", + "components": [ + { + "name": "bid", + "type": 2, + "typeArguments": null + }, + { + "name": "ask", + "type": 2, + "typeArguments": null + } + ], + "typeParameters": null + }, + { + "typeId": 4, + "type": "u64", + "components": null, + "typeParameters": null + } + ], + "functions": [ + { + "inputs": [ + { + "name": "amount", + "type": 4, + "typeArguments": null + }, + { + "name": "pair", + "type": 3, + "typeArguments": null + } + ], + "name": "main", + "output": { + "name": "", + "type": 1, + "typeArguments": null + }, + "attributes": null + } + ], + "loggedTypes": [], + "messagesTypes": [], + "configurables": [ + { + "name": "AMOUNT", + "configurableType": { + "name": "", + "type": 4, + "typeArguments": null + }, + "offset": 284 + }, + { + "name": "PAIR", + "configurableType": { + "name": "", + "type": 3, + "typeArguments": [] + }, + "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 new file mode 100644 index 000000000..e1bbcf0f5 --- /dev/null +++ b/packages/fuel-indexer-tests/sway/test-predicate2/out/debug/test-predicate2-bin-root @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000000000000000000000000000000000000..fc0d30a5ff08115b2aa4c7740a22817f3d54b78d GIT binary patch literal 332 zcmbV|K?=e!5Ji8iVmHD_#6=Ml%tG)I30i6}Lfi^jcmzx7J-mrW=z$ajw}MX9BDi!m z!+#lGhADt3LsMcD?vGuktn>)0mRn^W;8a`$&6l+2X z_$}hFxI%6QC>;d9%{h0-M97z**`=+6(FoZI(gN(RXN0tb#8I8Q(m;cX=-6i`3Tt-TDR?iMrK<8N#lh6O&|iMVe|&EO(Ol=J|8Sw6) 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 +} diff --git a/packages/fuel-indexer-tests/tests/indexing.rs b/packages/fuel-indexer-tests/tests/indexing.rs index 2a7c77d9d..4b0a6f88e 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 = - "f243849dbbbb53783de7ffc1ec12a1d6a42152b456d1b460e413a097694d247d"; + "60ddf75280b60e644e90fca5febaffb024882f0203b04ed8c7e9df8aa961060e"; const TRANSFER_BASE_ASSET_ID: &str = "0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/packages/fuel-indexer-tests/tests/service.rs b/packages/fuel-indexer-tests/tests/service.rs index 866ab78fb..0762f395f 100644 --- a/packages/fuel-indexer-tests/tests/service.rs +++ b/packages/fuel-indexer-tests/tests/service.rs @@ -44,11 +44,7 @@ async fn test_wasm_executor_can_meter_execution() { ..Default::default() }; - let schema_version = manifest - .graphql_schema_content() - .unwrap() - .version() - .to_string(); + let schema_version = manifest.schema_content().unwrap().version().to_string(); let mut executor = WasmIndexExecutor::new( &config, @@ -118,11 +114,7 @@ async fn test_wasm_executor_error_codes() { ); let config = IndexerConfig::default(); - let schema_version = manifest - .graphql_schema_content() - .unwrap() - .version() - .to_string(); + let schema_version = manifest.schema_content().unwrap().version().to_string(); let mut executor = WasmIndexExecutor::new( &config, diff --git a/packages/fuel-indexer-tests/tests/trybuild.rs b/packages/fuel-indexer-tests/tests/trybuild.rs index b4de3484b..a4564a9d9 100644 --- a/packages/fuel-indexer-tests/tests/trybuild.rs +++ b/packages/fuel-indexer-tests/tests/trybuild.rs @@ -37,11 +37,14 @@ fn manifest_with_contract_abi(contract_name: &str) -> String { r#" namespace: test_namespace identifier: simple_wasm_executor -abi: {abi_root_str}/{contract_name} -graphql_schema: {tests_root_str}/indexers/simple-wasm/schema/simple_wasm.graphql -contract_id: ~ +contract: + abi: {abi_root_str}/{contract_name} + subscriptions: ~ +schema: {tests_root_str}/indexers/simple-wasm/schema/simple_wasm.graphql module: - wasm: {project_root_str}/target/wasm32-unknown-unknown/release/simple_wasm.wasm"# + wasm: {project_root_str}/target/wasm32-unknown-unknown/release/simple_wasm.wasm +predicates: + templates: ~"# ) } @@ -53,11 +56,14 @@ fn test_success_and_failure_macros() { r#" namespace: test_namespace identifier: simple_wasm_executor -abi: {tests_root_str}/contracts/simple-wasm/out/debug/contracts-abi.json -graphql_schema: {tests_root_str}/indexers/simple-wasm/schema/simple_wasm.graphql -contract_id: ~ +contract: + abi: {tests_root_str}/sway/simple-wasm/out/debug/contracts-abi.json + subscriptions: ~ +schema: {tests_root_str}/indexers/simple-wasm/schema/simple_wasm.graphql module: wasm: {project_root_str}/target/wasm32-unknown-unknown/release/simple_wasm.wasm +predicates: + templates: ~ "# ); @@ -73,14 +79,17 @@ module: TestKind::Fail, format!( r#" - namespace: test_namespace - identifier: simple_wasm_executor - abi: {tests_root_str}/contracts/simple-wasm/out/debug/contracts-abi.json - # This schema file doesn't actually exist - graphql_schema: schema.graphql - contract_id: ~ - module: - wasm: {project_root_str}/target/wasm32-unknown-unknown/release/simple_wasm.wasm"# +namespace: test_namespace +identifier: simple_wasm_executor +contract: + abi: {tests_root_str}/sway/simple-wasm/out/debug/contracts-abi.json + subscriptions: ~ +# This schema file doesn't actually exist +schema: schema.graphql +module: + wasm: {project_root_str}/target/wasm32-unknown-unknown/release/simple_wasm.wasm +predicates: + templates: ~"# ), ), ( diff --git a/packages/fuel-indexer-tests/trybuild/fail_if_attribute_manifest_schema_arg_is_invalid.stderr b/packages/fuel-indexer-tests/trybuild/fail_if_attribute_manifest_schema_arg_is_invalid.stderr index c9e426f67..99ad9a000 100644 --- a/packages/fuel-indexer-tests/trybuild/fail_if_attribute_manifest_schema_arg_is_invalid.stderr +++ b/packages/fuel-indexer-tests/trybuild/fail_if_attribute_manifest_schema_arg_is_invalid.stderr @@ -1,4 +1,4 @@ -error: Could not open schema file "$DIR/schema.graphql" Os { code: 2, kind: NotFound, message: "No such file or directory" } +error: Could not open schema file "$WORKSPACE/schema.graphql" Os { code: 2, kind: NotFound, message: "No such file or directory" } --> trybuild/fail_if_attribute_manifest_schema_arg_is_invalid.rs | | #[indexer(manifest = "packages/fuel-indexer-tests/trybuild/invalid_schema_simple_wasm.yaml")] diff --git a/packages/fuel-indexer-tests/trybuild/invalid_abi_type_simple_wasm.yaml b/packages/fuel-indexer-tests/trybuild/invalid_abi_type_simple_wasm.yaml deleted file mode 100644 index 767984310..000000000 --- a/packages/fuel-indexer-tests/trybuild/invalid_abi_type_simple_wasm.yaml +++ /dev/null @@ -1,8 +0,0 @@ - -namespace: test_namespace -identifier: simple_wasm_executor -abi: /Users/rashad/dev/repos/fuel-indexer/packages/fuel-indexer-tests/trybuild/abi/contracts-abi-reserved-name.json -graphql_schema: /Users/rashad/dev/repos/fuel-indexer/packages/fuel-indexer-tests/indexers/simple-wasm/schema/simple_wasm.graphql -contract_id: ~ -module: - wasm: /Users/rashad/dev/repos/fuel-indexer/target/wasm32-unknown-unknown/release/simple_wasm.wasm \ No newline at end of file diff --git a/packages/fuel-indexer-tests/trybuild/invalid_schema_simple_wasm.yaml b/packages/fuel-indexer-tests/trybuild/invalid_schema_simple_wasm.yaml deleted file mode 100644 index 93680770e..000000000 --- a/packages/fuel-indexer-tests/trybuild/invalid_schema_simple_wasm.yaml +++ /dev/null @@ -1,9 +0,0 @@ - - namespace: test_namespace - identifier: simple_wasm_executor - abi: /Users/rashad/dev/repos/fuel-indexer/packages/fuel-indexer-tests/contracts/simple-wasm/out/debug/contracts-abi.json - # This schema file doesn't actually exist - graphql_schema: schema.graphql - contract_id: ~ - module: - wasm: /Users/rashad/dev/repos/fuel-indexer/target/wasm32-unknown-unknown/release/simple_wasm.wasm \ No newline at end of file diff --git a/packages/fuel-indexer-types/Cargo.toml b/packages/fuel-indexer-types/Cargo.toml index 4a4eabe8e..ded6d4623 100644 --- a/packages/fuel-indexer-types/Cargo.toml +++ b/packages/fuel-indexer-types/Cargo.toml @@ -10,6 +10,8 @@ rust-version = { workspace = true } description = "Fuel Indexer Types" [dependencies] +async-trait = { version = "0.1" } +bincode = { workspace = true } bytes = { version = "1.4", features = ["serde"] } fuel-tx = { workspace = true, features = ["serde"] } fuel-types = { workspace = true } diff --git a/packages/fuel-indexer-types/src/fuel.rs b/packages/fuel-indexer-types/src/fuel.rs index fb4738371..1921f4667 100644 --- a/packages/fuel-indexer-types/src/fuel.rs +++ b/packages/fuel-indexer-types/src/fuel.rs @@ -71,14 +71,14 @@ impl From for Json { fn from(metadata: CommonMetadata) -> Self { let s = serde_json::to_string(&metadata) .expect("Failed to serialize CommonMetadata."); - Self(s) + Self::new(s) } } impl From for CommonMetadata { fn from(json: Json) -> Self { - let metadata: CommonMetadata = - serde_json::from_str(&json.0).expect("Failed to deserialize CommonMetadata."); + let metadata: CommonMetadata = serde_json::from_str(&json.into_inner()) + .expect("Failed to deserialize CommonMetadata."); metadata } } @@ -107,14 +107,14 @@ impl From for Json { fn from(metadata: ScriptMetadata) -> Self { let s = serde_json::to_string(&metadata) .expect("Failed to deserialize MintMetadata."); - Self(s) + Self::new(s) } } impl From for ScriptMetadata { fn from(json: Json) -> Self { - let metadata: ScriptMetadata = - serde_json::from_str(&json.0).expect("Failed to deserialize ScriptMetadata."); + let metadata: ScriptMetadata = serde_json::from_str(&json.into_inner()) + .expect("Failed to deserialize ScriptMetadata."); metadata } } @@ -137,14 +137,14 @@ impl From for Json { fn from(metadata: MintMetadata) -> Self { let s = serde_json::to_string(&metadata).expect("Failed to serialize MintMetadata."); - Self(s) + Self::new(s) } } impl From for MintMetadata { fn from(json: Json) -> Self { - let metadata: MintMetadata = - serde_json::from_str(&json.0).expect("Failed to deserialize MintMetadata."); + let metadata: MintMetadata = serde_json::from_str(&json.into_inner()) + .expect("Failed to deserialize MintMetadata."); metadata } } @@ -215,8 +215,8 @@ impl From for Input { nonce: message_signed.nonce, witness_index: message_signed.witness_index, data: message_signed.data, - predicate: "".into(), - predicate_data: "".into(), + predicate: Bytes::new(), + predicate_data: Bytes::new(), }) } ClientInput::MessageDataPredicate(message_predicate) => { @@ -241,8 +241,8 @@ impl From for Input { tx_pointer: coin_signed.tx_pointer.into(), witness_index: coin_signed.witness_index, maturity: coin_signed.maturity, - predicate: "".into(), - predicate_data: "".into(), + predicate: Bytes::new(), + predicate_data: Bytes::new(), }), ClientInput::CoinPredicate(coin_predicate) => Input::Coin(InputCoin { utxo_id: coin_predicate.utxo_id, @@ -269,9 +269,9 @@ impl From for Input { amount: message_coin.amount, nonce: message_coin.nonce, witness_index: message_coin.witness_index, - data: "".into(), - predicate: "".into(), - predicate_data: "".into(), + data: Bytes::new(), + predicate: Bytes::new(), + predicate_data: Bytes::new(), }) } ClientInput::MessageCoinPredicate(message_coin) => { @@ -281,7 +281,7 @@ impl From for Input { amount: message_coin.amount, nonce: message_coin.nonce, witness_index: 0, - data: "".into(), + data: Bytes::new(), predicate: message_coin.predicate, predicate_data: message_coin.predicate_data, }) @@ -530,14 +530,14 @@ pub struct ProgramState { impl From for Json { fn from(state: ProgramState) -> Self { let s = serde_json::to_string(&state).expect("Failed to serialize ProgramState."); - Self(s) + Self::new(s) } } impl From for ProgramState { fn from(json: Json) -> Self { - let state: ProgramState = - serde_json::from_str(&json.0).expect("Failed to deserialize ProgramState."); + let state: ProgramState = serde_json::from_str(&json.into_inner()) + .expect("Failed to deserialize ProgramState."); state } } diff --git a/packages/fuel-indexer-types/src/graphql.rs b/packages/fuel-indexer-types/src/graphql.rs deleted file mode 100644 index 86b852ecd..000000000 --- a/packages/fuel-indexer-types/src/graphql.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::scalar::ID; -use serde::{Deserialize, Serialize}; - -/// Native GraphQL `TypeDefinition` used to keep track of chain metadata. -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct IndexMetadata { - /// Metadata identifier. - pub id: ID, - - /// Time of metadata. - pub time: u64, - - /// Block height of metadata. - pub block_height: u32, - - /// Block ID of metadata. - pub block_id: String, -} - -impl IndexMetadata { - /// Return the GraphQL schema fragment for the `IndexMetadata` type. - /// - /// The structure of this fragment should always match `fuel_indexer_types::IndexMetadata`. - pub fn schema_fragment() -> &'static str { - r#" - -type IndexMetadataEntity @entity { - id: ID! - time: U64! - block_height: U32! - block_id: Bytes32! -} -"# - } -} diff --git a/packages/fuel-indexer-types/src/indexer.rs b/packages/fuel-indexer-types/src/indexer.rs new file mode 100644 index 000000000..34cdf888e --- /dev/null +++ b/packages/fuel-indexer-types/src/indexer.rs @@ -0,0 +1,430 @@ +use crate::{ + fuel::{Output, Witness}, + scalar::{Address, AssetId, Bytes32, ID}, + type_id, TypeId, BETA4_CHAIN_ID, FUEL_TYPES_NAMESPACE, +}; +use fuels::accounts::predicate::Predicate as SDKPredicate; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +pub trait GraphQLEntity { + /// Return the GraphQL schema fragment for the entity. + fn schema_fragment() -> &'static str; +} + +/// Native GraphQL `TypeDefinition` used to keep track of chain metadata. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct IndexMetadata { + /// Metadata identifier. + pub id: ID, + + /// Time of metadata. + pub time: u64, + + /// Block height of metadata. + pub block_height: u32, + + /// Block ID of metadata. + pub block_id: String, +} + +impl GraphQLEntity for IndexMetadata { + /// Return the GraphQL schema fragment for the `IndexMetadata` type. + fn schema_fragment() -> &'static str { + r#" + +type IndexMetadataEntity @entity { + id: ID! + time: U64! + block_height: U32! + block_id: Bytes32! +} +"# + } +} + +impl TypeId for IndexMetadata { + /// Return the type ID for `IndexMetadata`. + fn type_id() -> usize { + type_id(FUEL_TYPES_NAMESPACE, "IndexMetadata") as usize + } +} + +/// Native GraphQL `TypeDefinition` used to keep track of chain metadata. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PredicateCoinOutput { + /// Owner of the predicate. + owner: Address, + + /// Amount of the predicate. + amount: u64, + + /// Asset ID of the predicate. + asset_id: AssetId, +} + +impl PredicateCoinOutput { + /// Create a new `PredicateCoinOutput`. + pub fn new(owner: Address, amount: u64, asset_id: AssetId) -> Self { + Self { + owner, + amount, + asset_id, + } + } + + /// Get the owner of the UTXO. + pub fn owner(&self) -> &Address { + &self.owner + } + + /// Get the amount of the UTXO. + pub fn amount(&self) -> u64 { + self.amount + } + + /// Get the asset ID of the UTXO. + pub fn asset_id(&self) -> &AssetId { + &self.asset_id + } +} + +impl GraphQLEntity for PredicateCoinOutput { + /// Return the GraphQL schema fragment for the `PredicateCoinOutput` type. + fn schema_fragment() -> &'static str { + r#" + +type PredicateCoinOutputEntity @entity { + id: ID! + owner: Address! + amount: U64! + asset_id: AssetId! +} +"# + } +} + +impl TypeId for PredicateCoinOutput { + /// Return the type ID for `PredicateCoinOutput`. + fn type_id() -> usize { + type_id(FUEL_TYPES_NAMESPACE, "PredicateCoinOutput") as usize + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PredicateWitnessData { + /// Hash of associated predicate bytecode + template_id: Bytes32, + + /// Configurable constants in predicates. + output_index: u64, + + /// Configurables injected into the predicate. + configurables: Vec, + + /// Namespace in which predicate lives. + template_name: String, + + /// Predicate bytecode. + bytecode: Vec, +} + +impl PredicateWitnessData { + /// Return the template ID of this associated predicate. + pub fn template_id(&self) -> &Bytes32 { + &self.template_id + } + + /// Return the output index of the UTXO associated with this predicate. + pub fn output_index(&self) -> u64 { + self.output_index + } + + /// Return the configuration schema of the associated predicate. + pub fn configurables(&self) -> &Vec { + &self.configurables + } + + /// Return the template_name of the associated predicate. + pub fn template_name(&self) -> &String { + &self.template_name + } + + /// Return the bytecode of the associated predicate. + pub fn bytecode(&self) -> &Vec { + &self.bytecode + } +} +impl TryFrom for PredicateWitnessData { + type Error = bincode::Error; + /// Convert from `Witness` to `PredicateWitnessData`. + fn try_from(witness: Witness) -> Result { + let data: Vec = witness.into_inner(); + bincode::deserialize(&data) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct IndexerPredicate { + /// Hash of associated predicate bytecode. + template_id: Bytes32, + + /// Namespace in which predicate lives. + template_name: String, + + /// configurables to the predicate. + configurables: Vec, + + /// Relevant TX output indices of predicates that need to be watched. + output_index: u64, + + /// UTXO held by predicate. + coin_output: PredicateCoinOutput, + + /// ID of transaction in which predicate was created. + unspent_tx_id: Bytes32, + + /// ID of transaction in which predicate was spent. + spent_tx_id: Option, + + /// Bytecode of the predicate. + bytecode: Vec, +} + +impl IndexerPredicate { + /// Create a new `IndexerPredicate`. + #[allow(clippy::too_many_arguments)] + pub fn new( + output_index: u64, + configurables: Vec, + template_name: String, + template_id: Bytes32, + coin_output: PredicateCoinOutput, + unspent_tx_id: Bytes32, + spent_tx_id: Option, + bytecode: Vec, + ) -> Self { + Self { + template_id, + configurables, + template_name, + output_index, + coin_output, + unspent_tx_id, + spent_tx_id, + bytecode, + } + } + + /// Get the predicate bytecode hash. + pub fn template_id(&self) -> &Bytes32 { + &self.template_id + } + + /// Get the configuration schema of the predicate. + pub fn configurables(&self) -> &Vec { + &self.configurables + } + + /// Get the predicate data output index. + pub fn output_index(&self) -> u64 { + self.output_index + } + + /// Get the UTXO held by the predicate. + pub fn coin_output(&self) -> &PredicateCoinOutput { + &self.coin_output + } + + /// Get the output transaction ID of the predicate. + pub fn unspent_tx_id(&self) -> &Bytes32 { + &self.unspent_tx_id + } + + /// Get the output transaction ID of the predicate. + pub fn spent_tx_id(&self) -> &Option { + &self.spent_tx_id + } + + /// Get the template_name of the predicate. + pub fn template_name(&self) -> &String { + &self.template_name + } + + /// Get the bytecode of the predicate. + pub fn bytecode(&self) -> &Vec { + &self.bytecode + } + + /// Create a new `IndexerPredicate` from a `PredicateWitnessData` and the corresponding + /// UTXO associated with that `Witness` data. + pub fn from_witness( + data: PredicateWitnessData, + tx_id: Bytes32, + output: Output, + ) -> Self { + let coin_output = match output { + Output::CoinOutput(coin_output) => PredicateCoinOutput::new( + coin_output.to, + coin_output.amount, + coin_output.asset_id, + ), + // FIXME: What to do here? + _ => todo!(), + }; + + let PredicateWitnessData { + template_id, + output_index, + configurables, + template_name, + bytecode, + } = data; + + Self { + template_id, + configurables, + output_index, + template_name, + coin_output, + unspent_tx_id: tx_id, + spent_tx_id: None, + bytecode, + } + } +} + +impl TypeId for IndexerPredicate { + /// Return the type ID for `IndexerPredicate`. + fn type_id() -> usize { + type_id(FUEL_TYPES_NAMESPACE, "IndexerPredicate") as usize + } +} + +impl GraphQLEntity for IndexerPredicate { + /// Return the GraphQL schema fragment for the `IndexerPredicate` type. + /// + /// The structure of this fragment should always match `fuel_indexer_types::Predicate`. + fn schema_fragment() -> &'static str { + r#" + +type IndexerPredicateEntity @entity { + id: ID! + template_name: String! + configurables: Bytes! + template_id: Bytes32! + output_index: U64! + coin_output: PredicateCoinOutputEntity! + unspent_tx_id: Bytes32! + spent_tx_id: Bytes32 + bytecode: Bytes! +}"# + } +} + +/// The indexer's public representation of a predicate. +/// +/// This is a manual copy of `fuels::accounts::predicate::Predicate` due to the +/// fact that `fuels::accounts::predicate::Predicate` is not serializable. +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct Predicate { + /// Address of the predicate. + /// + /// Using `Address` because `Bech32Address` is not serializable. + address: Address, + + /// Bytecode of the predicate. + code: Vec, + + /// configurables injected into the predicate. + /// + /// Using `Vec because `UnresolvedBytes` is not serializable. + data: Vec, + + /// Chain ID of the predicate. + chain_id: u64, +} + +impl From for Predicate { + /// Convert from `SDKPredicate` to `Predicate`. + fn from(predicate: SDKPredicate) -> Self { + Self { + address: predicate.address().into(), + code: predicate.code().to_vec(), + data: predicate.data().clone().resolve(0), + chain_id: BETA4_CHAIN_ID, + } + } +} + +impl Predicate { + /// Address of the predicate. + pub fn address(&self) -> &Address { + &self.address + } + + /// Bytecode of the predicate. + pub fn code(&self) -> &[u8] { + &self.code + } + + /// configurables injected into the predicate. + pub fn data(&self) -> &[u8] { + &self.data + } + + /// Chain ID of the predicate. + pub fn chain_id(&self) -> u64 { + self.chain_id + } +} + +impl TypeId for Predicate { + /// Return the type ID for `Predicate`. + fn type_id() -> usize { + type_id(FUEL_TYPES_NAMESPACE, "Predicate") as usize + } +} + +/// Container of all predicates extracted from a block for a given indexer. +#[derive(Default, Debug, Clone)] +pub struct Predicates { + /// List of predicates. + items: HashMap, +} + +impl Predicates { + /// Create a new `Predicates` instance. + pub fn new() -> Self { + Self { + items: HashMap::new(), + } + } + + /// Add a predicate to the index. + pub fn add(&mut self, id: String, predicate: IndexerPredicate) { + self.items.insert(id, predicate); + } + + /// Get a predicate by ID. + pub fn get(&self, id: &str) -> Option<&IndexerPredicate> { + self.items.get(id) + } + + /// Get the number of predicates in the index. + pub fn len(&self) -> usize { + self.items.len() + } + + /// Check if the index is empty. + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +impl TypeId for Predicates { + /// Return the type ID for `Predicates`. + fn type_id() -> usize { + type_id(FUEL_TYPES_NAMESPACE, "Predicates") as usize + } +} diff --git a/packages/fuel-indexer-types/src/lib.rs b/packages/fuel-indexer-types/src/lib.rs index eb4c6a491..87bc3eacf 100644 --- a/packages/fuel-indexer-types/src/lib.rs +++ b/packages/fuel-indexer-types/src/lib.rs @@ -1,6 +1,6 @@ pub mod ffi; pub mod fuel; -pub mod graphql; +pub mod indexer; pub mod receipt; pub mod scalar; @@ -16,6 +16,13 @@ pub use fuels::{ pub const FUEL_TYPES_NAMESPACE: &str = "fuel"; +/// Chain ID for the beta4 network. +pub const BETA4_CHAIN_ID: u64 = 0x00; + +/// 256-bit alias. +#[allow(non_camel_case_types)] +pub type b256 = Bits256; + pub trait TypeId { fn type_id() -> usize; } @@ -23,12 +30,12 @@ pub trait TypeId { pub mod prelude { pub use crate::ffi::*; pub use crate::fuel; - pub use crate::graphql::*; + pub use crate::indexer::*; pub use crate::receipt::*; pub use crate::scalar::*; pub use crate::{ - type_id, Bech32Address, Bech32ContractId, Bits256, Identity, SizedAsciiString, - TypeId, FUEL_TYPES_NAMESPACE, + b256, type_id, Bech32Address, Bech32ContractId, Bits256, Identity, + SizedAsciiString, TypeId, BETA4_CHAIN_ID, FUEL_TYPES_NAMESPACE, }; } diff --git a/packages/fuel-indexer-types/src/scalar.rs b/packages/fuel-indexer-types/src/scalar.rs index eb6c23d4b..750e7daa5 100644 --- a/packages/fuel-indexer-types/src/scalar.rs +++ b/packages/fuel-indexer-types/src/scalar.rs @@ -52,7 +52,23 @@ pub type Bytes = Vec; /// JSON type used to store arbitrary object payloads. #[derive(Deserialize, Serialize, Clone, Eq, PartialEq, Debug, Hash)] -pub struct Json(pub String); +pub struct Json(String); + +impl Json { + pub fn into_inner(self) -> String { + self.0 + } + + pub fn new(s: String) -> Self { + Json(s) + } +} + +impl AsRef for Json { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} impl Default for Json { fn default() -> Self { diff --git a/packages/fuel-indexer/src/README.md b/packages/fuel-indexer/src/README.md index c2a3cec6b..36e594bd8 100644 --- a/packages/fuel-indexer/src/README.md +++ b/packages/fuel-indexer/src/README.md @@ -1,23 +1 @@ # Fuel Indexer - -## Runtime Components - -- database.rs - Database connections, schema management for metadata - - Database: interfaces ffi functions with database queries for indexer WASM - - SchemaManager: sets up, validates new graphql schemas -- executor.rs - wasm runtime environment - - IndexEnv: holds references to objects that need to be available to ffi functions - - IndexExecutor: load WASM, execute event triggers -- ffi.rs - functions callable from WASM, loading data structures to/from WASM -- manifest.rs - the yaml format for a graphql instance - - namespace: The unique namespace this graphql schema lives in. This will correspond to the SQL database schema as well. - - graphql_schema: file path for the graphql schema. - - wasm_module: file path for the indexer WASM. - - list of test events to run through the indexer -- schema.rs - SQL table schema builder - -## Indexer Components - -- fuel-indexer/lib - Crate with traits/types used in a WASM indexer -- fuel-indexer/derive - Derive macros to generate rust types from schema, and handler functions -- fuel-indexer/schema - Crate for common types between runtime and indexer, sql types, serialization, etc. diff --git a/packages/fuel-indexer/src/executor.rs b/packages/fuel-indexer/src/executor.rs index e5cb657d5..9d0703fc0 100644 --- a/packages/fuel-indexer/src/executor.rs +++ b/packages/fuel-indexer/src/executor.rs @@ -765,7 +765,7 @@ impl WasmIndexExecutor { let config = config.unwrap_or_default(); let manifest = Manifest::from_file(p)?; let bytes = manifest.module_bytes()?; - let schema_version = manifest.graphql_schema_content()?.version().to_string(); + let schema_version = manifest.schema_content()?.version().to_string(); Self::new(&config, &manifest, bytes, pool, schema_version).await } diff --git a/packages/fuel-indexer/src/service.rs b/packages/fuel-indexer/src/service.rs index a05569e27..38e8a2c22 100644 --- a/packages/fuel-indexer/src/service.rs +++ b/packages/fuel-indexer/src/service.rs @@ -111,7 +111,7 @@ impl IndexerService { ) .await?; - let schema = manifest.graphql_schema_content()?; + let schema = manifest.schema_content()?; let schema_version = schema.version().to_string(); let schema_bytes = Vec::::from(&schema); diff --git a/plugins/forc-index/src/defaults.rs b/plugins/forc-index/src/defaults.rs index 7807e2277..5b3dcbb8d 100644 --- a/plugins/forc-index/src/defaults.rs +++ b/plugins/forc-index/src/defaults.rs @@ -58,12 +58,17 @@ pub fn default_indexer_manifest( # as an organization identifier namespace: {namespace} -# The identifier field is used to identify the given index. +# Unique identifier for this indexer. identifier: {indexer_name} -# The abi option is used to provide a link to the Sway JSON ABI that is generated when you -# build your project. -abi: ~ +# Indexer contract configuration. +contract: ~ + + # File paths to the contract JSON ABIs that are generated when you build your Sway contracts. + abis: ~ + + # Specifies which particular contracts you would like your indexer to subscribe to. + subscriptions: ~ # The particular start block after which you'd like your indexer to start indexing events. start_block: ~ @@ -77,12 +82,8 @@ end_block: ~ # with the `--indexer_net_config` option. fuel_client: ~ -# The contract_id specifies which particular contract you would like your index to subscribe to. -contract_id: ~ - -# The graphql_schema field contains the file path that points to the GraphQL schema for the -# given index. -graphql_schema: {schema_path} +# A file path that points to the GraphQL schema for the given indexer. +schema: {schema_path} # The module field contains a file path that points to code that will be run as an executor inside # of the indexer. @@ -92,6 +93,12 @@ module: # The resumable field contains a boolean that specifies whether or not the indexer should, synchronise # with the latest block if it has fallen out of sync. resumable: true + +# Indexer predicate configuration. +predicates: + + # Template commitments (hashes) of the bytecode of predicates used by this indexer. + templates: ~ "# ) } diff --git a/plugins/forc-index/src/ops/forc_index_build.rs b/plugins/forc-index/src/ops/forc_index_build.rs index 6c83b72ba..e0596afcd 100644 --- a/plugins/forc-index/src/ops/forc_index_build.rs +++ b/plugins/forc-index/src/ops/forc_index_build.rs @@ -59,7 +59,7 @@ pub fn init(command: BuildCommand) -> anyhow::Result<()> { let manifest_schema_file = { let workspace_root: std::path::PathBuf = crate::utils::cargo_workspace_root_dir(path.as_path()).unwrap(); - Path::new(&workspace_root).join(manifest.graphql_schema()) + Path::new(&workspace_root).join(manifest.schema()) }; // Rebuild the WASM module even if only the schema has changed. diff --git a/plugins/forc-index/src/ops/forc_index_deploy.rs b/plugins/forc-index/src/ops/forc_index_deploy.rs index 5a5f476db..e5f363411 100644 --- a/plugins/forc-index/src/ops/forc_index_deploy.rs +++ b/plugins/forc-index/src/ops/forc_index_deploy.rs @@ -66,7 +66,7 @@ pub async fn init(command: DeployCommand) -> anyhow::Result<()> { let workspace_root = crate::utils::cargo_workspace_root_dir(path.as_path()).unwrap(); let manifest_schema_file = Path::new(&workspace_root) - .join(manifest.graphql_schema()) + .join(manifest.schema()) .to_str() .unwrap() .to_string(); diff --git a/plugins/forc-postgres/src/pg.rs b/plugins/forc-postgres/src/pg.rs index 8f0e4f384..8ea502775 100644 --- a/plugins/forc-postgres/src/pg.rs +++ b/plugins/forc-postgres/src/pg.rs @@ -130,7 +130,7 @@ impl PostgresVersion { "v11" => Self::V11, "v10" => Self::V10, "v9" => Self::V9, - _ => unimplemented!(), + _ => unimplemented!("Postgres version unimplemented."), } } } diff --git a/scripts/utils/README.md b/scripts/utils/README.md deleted file mode 100644 index a664edf96..000000000 --- a/scripts/utils/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# scripts/utils - -General utilty scripts used to improve devx - -```text -. -├── README.md -├── build_test_wasm_module.bash -├── kill_test_components.bash -├── refresh_test_db.bash -└── start_test_components.bash - -0 directories, 5 files -``` - -- build_test_wasm_module - - Build the default `fuel-indexer-test` WASM module and add it to `fuel-indexer-tests/assets` -- kill_test_components - - Kill all processes for test components (Fuel node, Web API) -- refresh_test_db - - Drop the default testing database, recreate it, and run migrations -- start_test_components - - Start all testing components (Fuel node, Web API)