From 9bbad4977c5a9a4b7d1beae8dc9bf546805cfc84 Mon Sep 17 00:00:00 2001 From: remiroyc Date: Tue, 17 Dec 2024 16:06:24 +0100 Subject: [PATCH 1/2] chore: remove pontos create --- .github/workflows/arkproject-rs.yml | 2 +- Cargo.lock | 189 +++--- Cargo.toml | 3 - README.md | 2 +- crates/diri/README.md | 2 +- crates/pontos/Cargo.toml | 29 - crates/pontos/README.md | 38 -- crates/pontos/src/event_handler.rs | 32 - crates/pontos/src/lib.rs | 604 ------------------ crates/pontos/src/managers/block_manager.rs | 232 ------- .../pontos/src/managers/contract_manager.rs | 277 -------- crates/pontos/src/managers/event_manager.rs | 537 ---------------- crates/pontos/src/managers/mod.rs | 11 - crates/pontos/src/managers/token_manager.rs | 140 ---- crates/pontos/src/storage/mod.rs | 75 --- .../src/storage/sqlx/default_storage.rs | 394 ------------ .../src/storage/sqlx/migrations/0_default.sql | 51 -- crates/pontos/src/storage/sqlx/mod.rs | 9 - crates/pontos/src/storage/sqlx/types.rs | 59 -- crates/pontos/src/storage/types.rs | 383 ----------- crates/pontos/src/storage/utils.rs | 3 - crates/sana/src/managers/block_manager.rs | 2 +- skip_examples/{pontos.rs => sana.rs} | 24 +- .../{pontos_pending.rs => sana_pending.rs} | 14 +- .../{pontos_sqlx.rs => sana_sqlx.rs} | 31 +- src/lib.rs | 4 - 26 files changed, 118 insertions(+), 3029 deletions(-) delete mode 100644 crates/pontos/Cargo.toml delete mode 100644 crates/pontos/README.md delete mode 100644 crates/pontos/src/event_handler.rs delete mode 100644 crates/pontos/src/lib.rs delete mode 100644 crates/pontos/src/managers/block_manager.rs delete mode 100644 crates/pontos/src/managers/contract_manager.rs delete mode 100644 crates/pontos/src/managers/event_manager.rs delete mode 100644 crates/pontos/src/managers/mod.rs delete mode 100644 crates/pontos/src/managers/token_manager.rs delete mode 100644 crates/pontos/src/storage/mod.rs delete mode 100644 crates/pontos/src/storage/sqlx/default_storage.rs delete mode 100644 crates/pontos/src/storage/sqlx/migrations/0_default.sql delete mode 100644 crates/pontos/src/storage/sqlx/mod.rs delete mode 100644 crates/pontos/src/storage/sqlx/types.rs delete mode 100644 crates/pontos/src/storage/types.rs delete mode 100644 crates/pontos/src/storage/utils.rs rename skip_examples/{pontos.rs => sana.rs} (89%) rename skip_examples/{pontos_pending.rs => sana_pending.rs} (92%) rename skip_examples/{pontos_sqlx.rs => sana_sqlx.rs} (76%) diff --git a/.github/workflows/arkproject-rs.yml b/.github/workflows/arkproject-rs.yml index 4c4efe521..b02d8f4fe 100644 --- a/.github/workflows/arkproject-rs.yml +++ b/.github/workflows/arkproject-rs.yml @@ -89,4 +89,4 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: -p ark-starknet -p ark-metadata -p pontos -- -D warnings + args: -p ark-starknet -p ark-metadata -p sana -- -D warnings diff --git a/Cargo.lock b/Cargo.lock index c9e788425..dfdf2e2fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,12 +30,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -244,7 +243,6 @@ dependencies = [ "log", "mockall", "orderbook", - "pontos", "sana", "sqlx", "starknet 0.7.0", @@ -281,16 +279,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic-write-file" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" -dependencies = [ - "nix", - "rand", -] - [[package]] name = "auto_impl" version = "1.2.0" @@ -514,11 +502,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -589,7 +577,7 @@ version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn 2.0.64", @@ -607,6 +595,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -888,9 +885,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.52.0", @@ -958,15 +955,20 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "finl_unicode" @@ -1205,9 +1207,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -1215,20 +1217,11 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown 0.14.3", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", + "hashbrown 0.14.5", ] [[package]] @@ -1462,7 +1455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -1573,9 +1566,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libm" @@ -1585,9 +1578,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -1596,9 +1589,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -1718,17 +1711,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -1934,6 +1916,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.1" @@ -2026,29 +2014,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "pontos" -version = "0.1.0" -dependencies = [ - "anyhow", - "ark-metadata", - "ark-starknet", - "async-trait", - "dotenv", - "futures", - "log", - "mockall", - "num-bigint", - "serde", - "serde_json", - "sqlx", - "starknet 0.10.0", - "thiserror", - "tokio", - "tracing", - "version-compare", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -2370,9 +2335,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.4.1", "errno", @@ -2669,6 +2634,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2702,6 +2673,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -2761,9 +2735,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2774,17 +2748,15 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ - "ahash", "atoi", "byteorder", "bytes", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener", "futures-channel", @@ -2792,6 +2764,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.14.5", "hashlink", "hex", "indexmap 2.1.0", @@ -2814,27 +2787,26 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.64", ] [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ - "atomic-write-file", "dotenvy", "either", - "heck 0.4.1", + "heck", "hex", "once_cell", "proc-macro2", @@ -2846,7 +2818,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.64", "tempfile", "tokio", "url", @@ -2854,12 +2826,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", - "base64 0.21.5", + "base64 0.22.1", "bitflags 2.4.1", "byteorder", "bytes", @@ -2896,12 +2868,12 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", - "base64 0.21.5", + "base64 0.22.1", "bitflags 2.4.1", "byteorder", "crc", @@ -2923,7 +2895,6 @@ dependencies = [ "rand", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -2935,9 +2906,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "flume", @@ -2950,10 +2921,10 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "tracing", "url", - "urlencoding", ] [[package]] @@ -3459,15 +3430,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "once_cell", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d0736b3d8..3b954947c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ ArkProject libraries written in Rust members = [ "crates/ark-metadata", "crates/ark-starknet", - "crates/pontos", "crates/diri", "crates/sana", "crates/orderbook", @@ -25,7 +24,6 @@ members = [ [workspace.dependencies] ark-starknet = { path = "./crates/ark-starknet" } ark-metadata = { path = "./crates/ark-metadata" } -pontos = { path = "./crates/pontos" } sana = { path = "./crates/sana" } diri = { path = "./crates/diri" } orderbook = { path = "./crates/orderbook" } @@ -46,7 +44,6 @@ anyhow.workspace = true tokio.workspace = true ark-starknet.workspace = true ark-metadata.workspace = true -pontos = { path = "./crates/pontos", features = ["sqlxdb"] } sana = { path = "./crates/sana", features = ["sqlxdb"] } diri.workspace = true orderbook.workspace = true diff --git a/README.md b/README.md index b458351ef..d4398ac34 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Check out our demo app showcasing a mini marketplace: [ArkProject SDK Demo](http ### Crates for NFT Indexation -- [Pontos (NFT Indexer Library)](https://github.com/ArkProjectNFTs/ark-project/tree/main/crates/pontos) +- [Sana (NFT Indexer Library)](https://github.com/ArkProjectNFTs/ark-project/tree/main/crates/sana) - [Metadata](https://github.com/ArkProjectNFTs/ark-project/tree/main/crates/ark-metadata) - [Starknet Utilities](https://github.com/ArkProjectNFTs/ark-project/tree/main/crates/ark-starknet) - [Diri (Indexer Library for Solis and Arkchain)](https://github.com/ArkProjectNFTs/ark-project/tree/main/crates/diri) diff --git a/crates/diri/README.md b/crates/diri/README.md index b67a78ddf..2a384cf16 100644 --- a/crates/diri/README.md +++ b/crates/diri/README.md @@ -1,7 +1,7 @@ # Diri: arckchain indexer Diri is the Arkchain index library. -By implementing the same logic as `pontos`, by having a `Storage` +By implementing the same logic as `sana`, by having a `Storage` trait that defines all operations on the database, Diri collects the events from the Arkchain. diff --git a/crates/pontos/Cargo.toml b/crates/pontos/Cargo.toml deleted file mode 100644 index 8a1b4ff75..000000000 --- a/crates/pontos/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "pontos" -version = "0.1.0" -edition = "2021" - -[dependencies] -dotenv = "0.15.0" -futures = "0.3" -log = "0.4" -num-bigint = { version = "0.4.3", default-features = false } -serde = { version = "1.0.130", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0.32" -version-compare = "0.2.0" -tracing = "0.1" -sqlx = { version = "0.8.2", optional = true } -anyhow.workspace = true -tokio.workspace = true -ark-starknet.workspace = true -ark-metadata.workspace = true -starknet.workspace = true -async-trait.workspace = true - -[dev-dependencies] -ark-starknet = { path = "../ark-starknet", features = ["mock"] } -mockall = "0.12.1" - -[features] -sqlxdb = ["sqlx"] diff --git a/crates/pontos/README.md b/crates/pontos/README.md deleted file mode 100644 index c742ffd06..000000000 --- a/crates/pontos/README.md +++ /dev/null @@ -1,38 +0,0 @@ -## Pontos - -Pontos is a library that is designed to index NFTs on Starknet in the most generic manner, but still being configurable. - -## Overview - -The indexation process is made by the code inside `lib.rs`, with two principal functions: - -1. `index_pending` to index the pending block and the latest once the pending block is validated. -2. `index_block_range` to index a range of given block. - -During the indexation process, Pontos relies on two mecanisms that can be fully customized, by implementing those two traits: - -1. First, a `Storage` trait that you can derive to decide how to store the data that will be gathered by Pontos on chain. You can find an example using with `sqlx` (Sqlite, Postgres, MySql compatible) in the `storage/sqlx` module. -2. Second, you can initialize a new Pontos instance with an `EventHandler`, which are events that Pontos will emit without directly being associated with a `Storage`. - -## Code organization - -Pontos is organized the following way: - -1. `lib.rs` contains the main logic and types related to Pontos. -2. `managers`: to split the processing logic of each part of the indexing process, Pontos has several `managers` that implement the logic and data processing associated with the data to index. The main managers are `token`, `event`, `contract` and `block`. -3. `storage`: the storage module with definition of the types related to any database store. Implementing the `Storage` trait you will receive the data emitted from Pontos. The data that Pontos passes to the storage are voluntarily agnostic of Starknet, to ensure any database system without prior knowledge of Starknet types can handle the data. -4. Metadata are separated from Pontos as they don't belong to the core indexing logic and are not essential for a good indexaction of the contracts and tokens. - -## Pontos usage - -Pontos is part of the `arkproject` crate, and can be imported as follow: - -```rust -use arkproject::pontos::... -``` - -You can find examples of Pontos usages in the examples: - -- `examples/pontos.rs`: a simple example without any database, to see how a range of block can be indexed. -- `examples/pontos_pending.rs`: an example without any database, to illustrate how to index the head of the chain. -- `examples/pontos_sqlx.rs`: an example using the default storage implementation of `sqlx`, with in-memory Sqlite. diff --git a/crates/pontos/src/event_handler.rs b/crates/pontos/src/event_handler.rs deleted file mode 100644 index 82bd574ae..000000000 --- a/crates/pontos/src/event_handler.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Trait related to any events that Pontos can emit to be handled. -use crate::storage::types::{TokenEvent, TokenInfo}; -use async_trait::async_trait; - -/// A trait to be implemented in order to handle -/// events emitted by Pontos, in an external code. -/// -/// Any long computation in the code of those functions -/// will directly impact Pontos performances. -/// Please consider spawning tasks if some work may -/// be too heavy and impact negatively Pontos performances. -#[async_trait] -#[allow(unused)] -pub trait EventHandler { - /// Pontos has normally terminated the indexation of the given blocks. - async fn on_block_processed(&self, block_number: u64, indexation_progress: f64) {} - - /// Block is processing by Pontos. - async fn on_block_processing(&self, block_timestamp: u64, block_number: Option) {} - - /// Invoked when Pontos has successfully indexed a range of blocks up to the given block number. - async fn on_indexation_range_completed(&self) {} - - /// A new token has be registered. - async fn on_token_registered(&self, token: TokenInfo) {} - - /// A new event has be registered. - async fn on_event_registered(&self, event: TokenEvent) {} - - // A new latest block has been detected. - async fn on_new_latest_block(&self, block_number: u64) {} -} diff --git a/crates/pontos/src/lib.rs b/crates/pontos/src/lib.rs deleted file mode 100644 index e0b28a2f2..000000000 --- a/crates/pontos/src/lib.rs +++ /dev/null @@ -1,604 +0,0 @@ -pub mod event_handler; -pub mod managers; -pub mod storage; - -use crate::storage::types::BlockIndexingStatus; -use anyhow::Result; -use ark_starknet::client::{StarknetClient, StarknetClientError}; -use ark_starknet::format::to_hex_str; -use event_handler::EventHandler; -use managers::{BlockManager, ContractManager, EventManager, PendingBlockData, TokenManager}; -use starknet::core::types::*; -use std::fmt; -use std::sync::Arc; -use storage::types::{ContractType, StorageError}; -use storage::Storage; -use tokio::sync::RwLock as AsyncRwLock; -use tracing::{debug, error, info, trace, warn}; - -pub type IndexerResult = Result; - -const ELEMENT_MARKETPLACE_EVENT_HEX: &str = - "0x351e5a57ea6ca22e3e3cd212680ef7f3b57404609bda942a5e75ba4724b55e0"; - -const VENTORY_MARKETPLACE_EVENT_HEX: &str = - "0x1b43f40d55364e989b3a8674460f61ba8f327542298ee6240a54ee2bf7b55bb"; // EventListingBought - -const VENTORY_MARKETPLACE_OFFER_ACCEPTED_EVENT_HEX: &str = - "0xe214ba50bf9d17a50de9ab9f433295bd671144999d5258dbc261cbf1e1c2cc"; // EventOfferAccepted - -/// Generic errors for Pontos. -#[derive(Debug)] -pub enum IndexerError { - StorageError(StorageError), - Starknet(StarknetClientError), - Anyhow(String), -} - -impl From for IndexerError { - fn from(e: StorageError) -> Self { - IndexerError::StorageError(e) - } -} - -impl From for IndexerError { - fn from(e: StarknetClientError) -> Self { - IndexerError::Starknet(e) - } -} - -impl From for IndexerError { - fn from(e: anyhow::Error) -> Self { - IndexerError::Anyhow(e.to_string()) - } -} - -impl fmt::Display for IndexerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - IndexerError::StorageError(e) => write!(f, "Storage Error occurred: {}", e), - IndexerError::Starknet(e) => write!(f, "Starknet Error occurred: {}", e), - IndexerError::Anyhow(s) => write!(f, "An error occurred: {}", s), - } - } -} - -impl std::error::Error for IndexerError {} - -pub struct PontosConfig { - pub indexer_version: String, - pub indexer_identifier: String, -} - -pub struct Pontos { - client: Arc, - event_handler: Arc, - config: PontosConfig, - block_manager: Arc>, - event_manager: Arc>, - token_manager: Arc>, - contract_manager: Arc>>, - pending_cache: Arc>, -} - -impl Pontos { - pub fn new( - client: Arc, - storage: Arc, - event_handler: Arc, - config: PontosConfig, - ) -> Self { - Pontos { - config, - client: Arc::clone(&client), - event_handler: Arc::clone(&event_handler), - block_manager: Arc::new(BlockManager::new(Arc::clone(&storage))), - event_manager: Arc::new(EventManager::new(Arc::clone(&storage))), - token_manager: Arc::new(TokenManager::new(Arc::clone(&storage), Arc::clone(&client))), - // Contract manager has internal cache, so some functions are using `&mut self`. - // For this reason, we must protect the write operations in order to share - // the cache with any possible thread using `index_block_range` of this instance. - contract_manager: Arc::new(AsyncRwLock::new(ContractManager::new( - Arc::clone(&storage), - Arc::clone(&client), - ))), - pending_cache: Arc::new(AsyncRwLock::new(PendingBlockData::new())), - } - } - - /// Starts a loop to only index the pending block. - pub async fn index_pending(&self) -> IndexerResult<()> { - loop { - let mut cache = self.pending_cache.write().await; - - let (pending_ts, txs) = match self - .client - .block_txs_hashes(BlockId::Tag(BlockTag::Pending)) - .await - { - Ok((ts, txs)) => (ts, txs), - Err(e) => { - error!("Error while fetching pending block txs: {:?}", e); - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - continue; - } - }; - - if cache.get_timestamp() == 0 { - cache.set_timestamp(pending_ts); - } - - debug!("Pending block {} with {} txs", pending_ts, txs.len()); - - let previous_loop_ts = cache.get_timestamp(); - - // If the timestamp is different from the previous loop, - // we must first ensure we've fetched and processed all the transactions - // of the previous pending block, which is now the "Latest". - if pending_ts != previous_loop_ts { - debug!("ts differ! {} {}", pending_ts, previous_loop_ts); - // Get the latest block number, generated by the sequencer, which is - // expected to be the one we just processed. - let block_number = match self.client.block_number().await { - Ok(n) => n, - Err(e) => { - error!("Error while fetching latest block number: {:?}", e); - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - continue; - } - }; - - self.event_handler.on_new_latest_block(block_number).await; - - info!( - "Pending block {} is now latest block number #{}", - previous_loop_ts, block_number - ); - - // Setup the local variables to directly start the pending block - // indexation instead of waiting the next tick. - cache.set_timestamp(pending_ts); - cache.clear_tx_hashes(); - } - - // TODO: make this configurable? - tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; - } - } - - pub async fn index_contract_events( - &self, - from_block: Option, - to_block: Option, - contract_address: FieldElement, - chain_id: &str, - ) -> IndexerResult<()> { - let mut continuation_token: Option = None; - - loop { - let result = self - .client - .fetch_events( - from_block, - to_block, - self.event_manager.keys_selector(), - Some(contract_address), - continuation_token, - ) - .await?; - - let mut current_block_number: u64 = 0; - let mut current_block_timestamp: u64 = 0; - - for (block_number, events) in result.events { - if current_block_number != block_number { - current_block_number = block_number; - - match self.client.block_time(BlockId::Number(block_number)).await { - Ok(ts) => { - current_block_timestamp = ts; - self.process_events(events, current_block_timestamp, chain_id) - .await?; - } - Err(e) => { - error!("Error while fetching block timestamp: {:?}", e); - } - }; - } else { - self.process_events(events, current_block_timestamp, chain_id) - .await?; - } - } - - if result.continuation_token.is_none() { - break; - } else { - continuation_token = result.continuation_token; - continue; - } - } - - Ok(()) - } - - /// If "Latest" is used for the `to_block`, - /// this function will only index the latest block - /// that is not pending. - /// If you use this on latest, be sure to don't have any - /// other pontos instance running `index_pending` as you may - /// deal with overlaps or at least check db registers first. - pub async fn index_block_range( - &self, - from_block: BlockId, - to_block: BlockId, - do_force: bool, - chain_id: &str, - ) -> IndexerResult<()> { - let mut current_u64 = self.client.block_id_to_u64(&from_block).await?; - let to_u64 = self.client.block_id_to_u64(&to_block).await?; - let from_u64 = current_u64; - - // Some contracts are causing too much recursion for the Cairo VM. - // This is restarting the full node (Juno) as it is OOM and is shutdown by the OS. - // To mitigate this problem before scaling the full node up, - // we setup a `max_attempt` to reach the full node before skipping - // the entire block. - // Currently, we observed that the node almost always reponds after the - // second attempt. - let max_attempt = 5; - let mut attempt = 0; - - loop { - trace!("Indexing block range: {} {}", current_u64, to_u64); - - if current_u64 > to_u64 { - info!("End of indexing block range"); - break; - } - - let block_ts = match self.client.block_time(BlockId::Number(current_u64)).await { - Ok(ts) => ts, - Err(e) => { - error!( - "Attempt #{} - Couldn't get timestamp for block {}: {:?}", - attempt + 1, - current_u64, - e - ); - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - attempt += 1; - - if attempt > max_attempt { - warn!( - "Skipping block {} as timestamp is not available", - current_u64 - ); - current_u64 += 1; - } - - continue; - } - }; - - if self - .block_manager - .should_skip_indexing( - current_u64, - block_ts, - self.config.indexer_version.clone(), - do_force, - ) - .await? - { - info!("Skipping block {}", current_u64); - current_u64 += 1; - continue; - } - - self.event_handler - .on_block_processing(block_ts, Some(current_u64)) - .await; - - // Set block as processing. - self.block_manager - .set_block_info( - current_u64, - block_ts, - self.config.indexer_version.clone(), - self.config.indexer_identifier.clone(), - BlockIndexingStatus::Processing, - ) - .await?; - - let blocks_events = match self - .client - .fetch_all_block_events( - BlockId::Number(current_u64), - self.event_manager.keys_selector(), - ) - .await - { - Ok(events) => events, - Err(e) => { - error!("Error while fetching events: {:?}", e); - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - continue; - } - }; - - let total_events_count: usize = blocks_events.values().map(|events| events.len()).sum(); - info!( - "✨ Processing block {}. Total Events Count: {}.", - current_u64, total_events_count - ); - - for (_, events) in blocks_events { - self.process_events(events, block_ts, chain_id).await?; - } - - self.block_manager - .set_block_info( - current_u64, - block_ts, - self.config.indexer_version.clone(), - self.config.indexer_identifier.clone(), - BlockIndexingStatus::Terminated, - ) - .await?; - - let progress = if to_u64 == from_u64 { - if current_u64 == to_u64 { - 100.0 - } else { - 0.0 - } - } else { - ((current_u64 - from_u64) as f64 / (to_u64 - from_u64) as f64) * 100.0 - }; - - self.event_handler - .on_block_processed(current_u64, progress) - .await; - - current_u64 += 1; - } - - self.event_handler.on_indexation_range_completed().await; - - Ok(()) - } - - async fn process_element_sale( - &self, - event: EmittedEvent, - block_timestamp: u64, - chain_id: &str, - ) -> Result<()> { - let mut token_sale_event = self - .event_manager - .format_element_sale_event(&event, block_timestamp) - .await?; - - let contract_addr = FieldElement::from_hex_be( - token_sale_event.nft_contract_address.as_str(), - ) - .map_err(|e| { - error!("Invalid NFT contract address format: {:?}", e); - e - })?; - - let contract_type = match self - .contract_manager - .write() - .await - .identify_contract(contract_addr, block_timestamp, chain_id) - .await - { - Ok(info) => info, - Err(e) => { - error!( - "Error while identifying contract {}: {:?}", - token_sale_event.nft_contract_address, e - ); - return Ok(()); - } - }; - - if contract_type == ContractType::Other { - debug!( - "Contract identified as OTHER: {}", - token_sale_event.nft_contract_address - ); - return Ok(()); - } - - token_sale_event.nft_type = Some(contract_type.to_string()); - self.event_manager - .register_sale_event(&token_sale_event, block_timestamp) - .await?; - - Ok(()) - } - - async fn process_ventory_sale_or_accepted_offer_event( - &self, - event: EmittedEvent, - block_timestamp: u64, - chain_id: &str, - ) -> Result<()> { - info!("Processing Ventory Sale or Accepted Offer event..."); - - let mut token_sale_event = self - .event_manager - .format_ventory_sale_or_accepted_offer_event(&event, block_timestamp) - .await?; - - let contract_addr = FieldElement::from_hex_be( - token_sale_event.nft_contract_address.as_str(), - ) - .map_err(|e| { - error!("Invalid NFT contract address format: {:?}", e); - e - })?; - - let contract_type = match self - .contract_manager - .write() - .await - .identify_contract(contract_addr, block_timestamp, chain_id) - .await - { - Ok(info) => info, - Err(e) => { - error!( - "Error while identifying contract {}: {:?}", - token_sale_event.nft_contract_address, e - ); - return Ok(()); - } - }; - - if contract_type == ContractType::Other { - debug!( - "Contract identified as OTHER: {}", - token_sale_event.nft_contract_address - ); - return Ok(()); - } - - token_sale_event.nft_type = Some(contract_type.to_string()); - self.event_manager - .register_sale_event(&token_sale_event, block_timestamp) - .await?; - - Ok(()) - } - - async fn process_marketplace_event( - &self, - event: EmittedEvent, - block_timestamp: u64, - chain_id: &str, - ) -> Result<()> { - let element_sale_event_name = FieldElement::from_hex_be(ELEMENT_MARKETPLACE_EVENT_HEX)?; - let ventory_sale_event_name = FieldElement::from_hex_be(VENTORY_MARKETPLACE_EVENT_HEX)?; - let ventory_offer_accepted_event_name = - FieldElement::from_hex_be(VENTORY_MARKETPLACE_OFFER_ACCEPTED_EVENT_HEX)?; - - if let Some(event_name) = event.keys.first() { - info!("Processing marketplace event: {:?}", event_name); - - match event_name { - name if name == &element_sale_event_name => { - self.process_element_sale(event, block_timestamp, chain_id) - .await? - } - name if name == &ventory_sale_event_name - || name == &ventory_offer_accepted_event_name => - { - self.process_ventory_sale_or_accepted_offer_event( - event, - block_timestamp, - chain_id, - ) - .await? - } - _ => (), - } - } - - Ok(()) - } - - async fn process_nft_transfers( - &self, - event: EmittedEvent, - block_timestamp: u64, - contract_address: FieldElement, - chain_id: &str, - ) -> Result<()> { - let contract_address_hex = to_hex_str(&contract_address); - let contract_type = self - .contract_manager - .write() - .await - .identify_contract(contract_address, block_timestamp, chain_id) - .await - .map_err(|e| { - error!( - "Error while identifying contract {}: {:?}", - contract_address_hex, e - ); - e - })?; - - if contract_type == ContractType::Other { - debug!("Contract identified as OTHER: {}", contract_address_hex); - return Ok(()); - } - - info!( - "Processing event... Block Id: {:?}, Tx Hash: 0x{:064x}, contract_type: {:?}", - event.block_number, event.transaction_hash, contract_type - ); - - let (token_id, token_event) = self - .event_manager - .format_and_register_event(&event, contract_type, block_timestamp) - .await - .map_err(|err| { - error!("Error while registering event {:?}\n{:?}", err, event); - err - })?; - - self.token_manager - .format_and_register_token(&token_id, &token_event, block_timestamp, event.block_number) - .await - .map_err(|err| { - error!("Can't format token {:?}\ntevent: {:?}", err, token_event); - err - })?; - - Ok(()) - } - - /// Inner function to process events. - async fn process_events( - &self, - events: Vec, - block_timestamp: u64, - chain_id: &str, - ) -> IndexerResult<()> { - let marketplace_contracts = [ - FieldElement::from_hex_be( - "0x04d8bb956e6bd7a50fcb8b49d8e9fd8269cfadbeb73f457fd6d3fc1dff4b879e", // Element Marketplace - ) - .unwrap(), - FieldElement::from_hex_be( - "0x008755a98ccf7d25e69aa90ef3b73b07c470ba4ec6391b0b0c7c598f992c3fee", // Ventory Marketplace - ) - .unwrap(), - ]; - - for e in events { - let contract_address = e.from_address; - let is_marketplace_event = marketplace_contracts.contains(&contract_address); - - if is_marketplace_event { - if let Err(e) = self - .process_marketplace_event(e, block_timestamp, chain_id) - .await - { - error!("Error while processing marketplace event: {:?}", e); - } - } else if let Err(e) = self - .process_nft_transfers(e, block_timestamp, contract_address, chain_id) - .await - { - error!("Error while processing NFT transfers: {:?}", e); - } - } - - Ok(()) - } -} diff --git a/crates/pontos/src/managers/block_manager.rs b/crates/pontos/src/managers/block_manager.rs deleted file mode 100644 index f029baa72..000000000 --- a/crates/pontos/src/managers/block_manager.rs +++ /dev/null @@ -1,232 +0,0 @@ -use crate::storage::types::{BlockIndexingStatus, BlockInfo, StorageError}; -use crate::storage::Storage; -use starknet::core::types::FieldElement; -use std::sync::Arc; -use tracing::{debug, trace}; -use version_compare::{compare, Cmp}; - -#[derive(Debug)] -pub struct BlockManager { - storage: Arc, -} - -impl BlockManager { - pub fn new(storage: Arc) -> Self { - Self { - storage: Arc::clone(&storage), - } - } - - pub async fn clean_block( - &self, - block_timestamp: u64, - block_number: Option, - ) -> Result<(), StorageError> { - self.storage - .clean_block(block_timestamp, block_number) - .await - } - - /// Returns false if the given block number must be indexed. - /// True otherwise. - pub async fn should_skip_indexing( - &self, - block_number: u64, - block_timestamp: u64, - indexer_version: String, - do_force: bool, - ) -> Result { - if do_force { - // Force indexing by cleaning the block, and return true. - match self - .storage - .clean_block(block_timestamp, Some(block_number)) - .await - { - Ok(()) => Ok(false), - Err(_) => Ok(true), - } - } else { - match self.storage.get_block_info(block_number).await { - Ok(info) => { - trace!("Block {} already indexed", block_number); - debug!( - "Checking indexation version: current={:?}, last={:?}", - indexer_version, - info.indexer_version.clone() - ); - - // Compare the indexer versions. - match compare(indexer_version.clone(), info.indexer_version.clone()) { - // if the current version is greater, clean the block & return false we index the block - Ok(Cmp::Gt) => self - .storage - .clean_block(block_timestamp, Some(block_number)) - .await - .map(|_| false), - // if the current version is equal, return false we skip the block indexation - _ => Ok(true), - } - } - Err(StorageError::NotFound(_s)) => Ok(false), - Err(e) => Err(e), - } - } - } - - pub async fn set_block_info( - &self, - block_number: u64, - block_timestamp: u64, - indexer_version: String, - indexer_identifier: String, - status: BlockIndexingStatus, - ) -> Result<(), StorageError> { - self.storage - .set_block_info( - block_number, - block_timestamp, - BlockInfo { - indexer_version, - indexer_identifier, - status, - block_number, - }, - ) - .await?; - Ok(()) - } -} - -/// Data of the pending block being indexed. -/// The vector of txs hashes are the hashes -/// of the transactions already processed by the indexer. -#[derive(Debug)] -pub struct PendingBlockData { - timestamp: u64, - txs_hashes: Vec, -} - -impl PendingBlockData { - pub fn new() -> Self { - PendingBlockData { - timestamp: 0, - txs_hashes: vec![], - } - } - - pub fn get_timestamp(&self) -> u64 { - self.timestamp - } - - pub fn set_timestamp(&mut self, ts: u64) { - self.timestamp = ts; - } - - pub fn add_tx_as_processed(&mut self, tx_hash: &FieldElement) { - self.txs_hashes.push(*tx_hash); - } - - pub fn is_tx_processed(&self, tx_hash: &FieldElement) -> bool { - self.txs_hashes.contains(tx_hash) - } - - pub fn clear_tx_hashes(&mut self) { - self.txs_hashes.clear(); - } -} - -impl Default for PendingBlockData { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - use crate::storage::{ - types::{BlockIndexingStatus, BlockInfo}, - MockStorage, - }; - - #[tokio::test] - async fn test_should_skip_indexing_not_found() { - let mut mock_storage = MockStorage::default(); - - // Mock the get_block_info to return NotFound. - mock_storage - .expect_get_block_info() - .returning(|_| Box::pin(async { Err(StorageError::NotFound("".to_string())) })); - - let block_number = 3; - - // Mock the clean_block method to return Ok(()). - mock_storage - .expect_clean_block() - .returning(|_, _| Box::pin(async { Ok(()) })); - - let manager = BlockManager { - storage: Arc::new(mock_storage), - }; - - // Should return false as the block is not found. - let result = manager - .should_skip_indexing(block_number, 0, "v0.0.2".to_string(), false) - .await - .unwrap(); - - assert!(result == false); - } - - #[tokio::test] - async fn test_should_skip_indexing() { - let mut mock_storage = MockStorage::default(); - - // Mock the clean_block method to return Ok(()). - mock_storage - .expect_clean_block() - .returning(|_, _| Box::pin(futures::future::ready(Ok(())))); - - // Mock the get_block_info to return an indexed block with an older version. - mock_storage - .expect_get_block_info() - .returning(|block_number| { - Box::pin(futures::future::ready(if block_number == 1 { - Ok(BlockInfo { - status: BlockIndexingStatus::Processing, - indexer_version: String::from("v0.0.1"), - indexer_identifier: String::from("TASK#123"), - block_number: 123, - }) - } else { - Err(StorageError::NotFound("".to_string())) - })) - }); - - // Mock the clean_block method to return Ok(()). - mock_storage - .expect_clean_block() - .returning(|_, _| Box::pin(async { Ok(()) })); - - let manager = BlockManager { - storage: Arc::new(mock_storage), - }; - - // New version, should return true for indexing. - let result = manager - .should_skip_indexing(1, 0, "v0.0.2".to_string(), false) - .await - .unwrap(); - assert!(result == false); - - // Force but same version, should return true for indexing. - let result = manager - .should_skip_indexing(2, 0, "v0.0.1".to_string(), true) - .await - .unwrap(); - assert!(result == false); - } -} diff --git a/crates/pontos/src/managers/contract_manager.rs b/crates/pontos/src/managers/contract_manager.rs deleted file mode 100644 index 00b7877d6..000000000 --- a/crates/pontos/src/managers/contract_manager.rs +++ /dev/null @@ -1,277 +0,0 @@ -use crate::storage::{ - types::{ContractInfo, ContractType, StorageError}, - Storage, -}; -use anyhow::Result; -use ark_starknet::{ - cairo_string_parser::parse_cairo_string, - client::{StarknetClient, StarknetClientError}, - format::to_hex_str, -}; -use starknet::core::{ - types::{BlockId, BlockTag, FieldElement}, - utils::get_selector_from_name, -}; -use std::collections::HashMap; -use std::sync::Arc; -use tracing::{error, info, trace}; - -pub struct ContractManager { - storage: Arc, - client: Arc, - /// A cache with contract address mapped to its type. - cache: HashMap, -} - -impl ContractManager { - /// Initializes a new instance. - pub fn new(storage: Arc, client: Arc) -> Self { - Self { - storage, - client, - cache: HashMap::new(), - } - } - - /// Gets the contract info from local cache, or fetch is from the DB. - async fn get_cached_or_fetch_info( - &mut self, - address: FieldElement, - chain_id: &str, - ) -> Result { - if let Some(contract_type) = self.cache.get(&address) { - return Ok(contract_type.clone()); - } - - trace!("Cache miss for contract {:#064x}", address); - - let contract_type = self - .storage - .get_contract_type(&to_hex_str(&address), chain_id) - .await?; - - self.cache.insert(address, contract_type.clone()); // Adding to the cache - - Ok(contract_type) - } - - /// Identifies a contract from its address and caches its info. - /// - /// This function attempts to identify a contract by its address, - /// fetching its type, name, and symbol, and caching these details for future use. - /// - /// # Arguments - /// * `address` - The address of the contract as a `FieldElement`. - /// * `block_timestamp` - The timestamp of the current block. - /// - /// # Returns - /// * `Result` - The type of the contract if identified successfully. - pub async fn identify_contract( - &mut self, - address: FieldElement, - block_timestamp: u64, - chain_id: &str, - ) -> Result { - match self.get_cached_or_fetch_info(address, chain_id).await { - Ok(contract_type) => Ok(contract_type), - Err(_) => { - if let Ok(contract_type) = self.get_cached_or_fetch_info(address, chain_id).await { - return Ok(contract_type); - } - - // If the contract info is not cached, identify and cache it. - let contract_type = self.get_contract_type(address).await?; - - self.cache.insert(address, contract_type.clone()); - - let name = self - .get_contract_property_string( - address, - "name", - vec![], - BlockId::Tag(BlockTag::Pending), - ) - .await - .ok(); - - let symbol = self - .get_contract_property_string( - address, - "symbol", - vec![], - BlockId::Tag(BlockTag::Pending), - ) - .await - .ok(); - - info!( - "Contract [0x{:064x}] details - Type: {}, Name: {:?}, Symbol: {:?}", - address, - contract_type.to_string(), - name, - symbol - ); - - let info = ContractInfo { - contract_address: to_hex_str(&address), - contract_type: contract_type.to_string(), - name, - symbol, - image: None, - chain_id: chain_id.to_string(), - }; - - if let Err(e) = self - .storage - .register_contract_info(&info, block_timestamp, chain_id) - .await - { - error!( - "Failed to store contract info for [0x{:064x}]: {:?}", - address, e - ); - } - - Ok(contract_type) - } - } - } - - /// Verifies if the contract is an ERC721, ERC1155 or an other type. - /// `owner_of` is specific to ERC721. - /// `balance_of` is specific to ERC1155 and different from ERC20 as 2 arguments are expected. - pub async fn get_contract_type(&self, contract_address: FieldElement) -> Result { - let _block = BlockId::Tag(BlockTag::Pending); - - if self.is_erc721(contract_address).await? { - Ok(ContractType::ERC721) - } else if self.is_erc1155(contract_address).await? { - Ok(ContractType::ERC1155) - } else { - Ok(ContractType::Other) - } - } - - /// Returns true if the contract is ERC721, false otherwise. - pub async fn is_erc721(&self, contract_address: FieldElement) -> Result { - let block = BlockId::Tag(BlockTag::Pending); - let token_id = vec![FieldElement::ONE, FieldElement::ZERO]; // u256. - - match self - .get_contract_response(contract_address, "ownerOf", token_id.clone(), block) - .await - { - Ok(_) => return Ok(true), - Err(e) => match e { - StarknetClientError::Contract(s) => { - // Token ID may not exist, but the entrypoint was hit. - if s.contains("not found in contract") { - // do nothing and go to the next selector. - } else { - return Ok(true); - } - } - StarknetClientError::EntrypointNotFound(_) => (), - _ => return Ok(false), - }, - }; - - match self - .get_contract_response(contract_address, "owner_of", token_id, block) - .await - { - Ok(_) => Ok(true), - Err(e) => match e { - StarknetClientError::Contract(s) => { - // Token ID may not exist, but the entrypoint was hit. - if s.contains("not found in contract") { - Ok(false) - } else { - Ok(true) - } - } - StarknetClientError::EntrypointNotFound(_) => Ok(false), - _ => Ok(false), - }, - } - } - - /// Returns true if the contract is ERC1155, false otherwise. - pub async fn is_erc1155(&self, contract_address: FieldElement) -> Result { - let block = BlockId::Tag(BlockTag::Pending); - // felt and u256 expected. - let address_and_token_id = vec![FieldElement::ZERO, FieldElement::ONE, FieldElement::ZERO]; - - match self - .get_contract_response( - contract_address, - "balanceOf", - address_and_token_id.clone(), - block, - ) - .await - { - Ok(_) => return Ok(true), - Err(e) => match e { - StarknetClientError::EntrypointNotFound(_) => (), - StarknetClientError::InputTooLong => return Ok(false), // ERC20. - _ => return Ok(false), - }, - }; - - match self - .get_contract_response(contract_address, "balance_of", address_and_token_id, block) - .await - { - Ok(_) => Ok(true), - Err(e) => match e { - StarknetClientError::EntrypointNotFound(_) => Ok(false), - StarknetClientError::InputTooLong => Ok(false), // ERC20. - _ => Ok(false), - }, - } - } - - pub async fn get_contract_response( - &self, - contract_address: FieldElement, - selector_name: &str, - calldata: Vec, - block: BlockId, - ) -> Result, StarknetClientError> { - self.client - .call_contract( - contract_address, - get_selector_from_name(selector_name).map_err(|_| { - StarknetClientError::Other(format!("Invalid selector: {}", selector_name)) - })?, - calldata, - block, - ) - .await - } - - pub async fn get_contract_property_string( - &self, - contract_address: FieldElement, - selector_name: &str, - calldata: Vec, - block: BlockId, - ) -> Result { - let response = self - .client - .call_contract( - contract_address, - get_selector_from_name(selector_name).map_err(|_| { - StarknetClientError::Other(format!("Invalid selector: {}", selector_name)) - })?, - calldata, - block, - ) - .await?; - - parse_cairo_string(response).map_err(|e| { - StarknetClientError::Other(format!("Impossible to decode response string: {:?}", e)) - }) - } -} diff --git a/crates/pontos/src/managers/event_manager.rs b/crates/pontos/src/managers/event_manager.rs deleted file mode 100644 index 76bddd6d3..000000000 --- a/crates/pontos/src/managers/event_manager.rs +++ /dev/null @@ -1,537 +0,0 @@ -use crate::storage::types::{EventType, TokenSaleEvent, TokenTransferEvent}; -use crate::storage::Storage; -use crate::{ - ContractType, VENTORY_MARKETPLACE_EVENT_HEX, VENTORY_MARKETPLACE_OFFER_ACCEPTED_EVENT_HEX, -}; -use anyhow::{anyhow, Result}; -use ark_starknet::{format::to_hex_str, CairoU256}; -use starknet::core::types::{EmittedEvent, FieldElement}; -use starknet::core::utils::starknet_keccak; -use starknet::macros::selector; -use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; -use tracing::trace; - -const TRANSFER_SELECTOR: FieldElement = selector!("Transfer"); -const ELEMENT_NFT_MARKETPLACE_HEX: &str = - "0x351e5a57ea6ca22e3e3cd212680ef7f3b57404609bda942a5e75ba4724b55e0"; - -#[derive(Debug)] -pub struct EventManager { - storage: Arc, -} - -impl EventManager { - /// Initializes a new instance. - pub fn new(storage: Arc) -> Self { - EventManager { - storage: Arc::clone(&storage), - } - } - - /// Returns the selectors used to filter events. - pub fn keys_selector(&self) -> Option>> { - let element_nft_marketplace = FieldElement::from_hex_be(ELEMENT_NFT_MARKETPLACE_HEX) - .expect("Failed to parse element nft marketplace hex"); - - let ventory_nft_marketplace = FieldElement::from_hex_be(VENTORY_MARKETPLACE_EVENT_HEX) - .expect("Failed to parse ventory nft marketplace hex"); - - let ventory_accepted_offer_event = - FieldElement::from_hex_be(VENTORY_MARKETPLACE_OFFER_ACCEPTED_EVENT_HEX) - .expect("Failed to parse ventory accepted offer selector"); - - Some(vec![vec![ - TRANSFER_SELECTOR, - element_nft_marketplace, - ventory_nft_marketplace, - ventory_accepted_offer_event, - ]]) - } - - pub async fn register_sale_event( - &self, - event: &TokenSaleEvent, - block_timestamp: u64, - ) -> Result<()> { - self.storage - .register_sale_event(event, block_timestamp) - .await?; - Ok(()) - } - - pub async fn format_ventory_sale_or_accepted_offer_event( - &self, - event: &EmittedEvent, - block_timestamp: u64, - ) -> Result { - let _listing_counter = event - .data - .first() - .ok_or_else(|| anyhow!("Listing counter not found"))?; - let token_id = event - .data - .get(1) - .ok_or_else(|| anyhow!("Token id not found"))?; - let price = event - .data - .get(2) - .ok_or_else(|| anyhow!("Price not found"))?; - let asset_contract = event - .data - .get(3) - .ok_or_else(|| anyhow!("Asset contract not found"))?; - let seller = event - .data - .get(4) - .ok_or_else(|| anyhow!("Seller not found"))?; - let buyer = event - .data - .get(5) - .ok_or_else(|| anyhow!("Buyer not found"))?; - let _status = event - .data - .get(6) - .ok_or_else(|| anyhow!("Status not found"))?; - - let token_id = CairoU256 { - low: (*token_id) - .try_into() - .map_err(|_| anyhow!("Failed to parse token id"))?, - high: 0, - }; - - let event_id = Self::get_event_id(&token_id, seller, buyer, block_timestamp, event); - - Ok(TokenSaleEvent { - event_id: to_hex_str(&event_id), - event_type: EventType::Sale, - block_number: event.block_number, - from_address: to_hex_str(seller), - to_address: to_hex_str(buyer), - nft_contract_address: to_hex_str(asset_contract), - nft_type: None, - transaction_hash: to_hex_str(&event.transaction_hash), - token_id_hex: token_id.to_hex(), - token_id: token_id.to_decimal(false), - timestamp: block_timestamp, - updated_at: Some(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()), - quantity: 1, - currency_address: None, - marketplace_contract_address: to_hex_str(&event.from_address), - marketplace_name: "Ventory".to_string(), - price: price.to_big_decimal(0).to_string(), - }) - } - - pub async fn format_element_sale_event( - &self, - event: &EmittedEvent, - block_timestamp: u64, - ) -> Result { - if event.keys.len() < 4 { - return Err(anyhow!("Can't find event data into this event")); - } - - let maker_address = event - .keys - .get(3) - .ok_or_else(|| anyhow!("Maker address not found"))?; - - let taker_address = event - .data - .first() - .ok_or_else(|| anyhow!("Taker address not found"))?; - let currency_address = event - .data - .get(1) - .ok_or_else(|| anyhow!("Currency address not found"))?; - let price = event - .data - .get(2) - .ok_or_else(|| anyhow!("Price not found"))?; - - let number_of_fee_recipients = event - .data - .get(3) - .ok_or_else(|| anyhow!("Number of fee recipients not found"))?; - - let number_of_fee_recipients_u64: u32 = (*number_of_fee_recipients) - .try_into() - .map_err(|_| anyhow!("Failed to parse number of fee recipients"))?; - - let mut index = 4; - // for loop with number_of_fee_recipients_u64 iterations - for _ in 0..number_of_fee_recipients_u64 { - let _fee0_recipient = event.data.get(index); - index += 1; - let _fee0_value = event.data.get(index); - index += 1; - } - - let nft_contract_address = event - .data - .get(index) - .ok_or_else(|| anyhow!("NFT contract address not found"))?; - - index += 1; - - let token_id_low = event - .data - .get(index) - .ok_or_else(|| anyhow!("Token id low not found"))?; - - index += 1; - - let token_id_high = event - .data - .get(index) - .ok_or_else(|| anyhow!("Token id high not found"))?; - - index += 1; - - let quantity = event - .data - .get(index) - .ok_or_else(|| anyhow!("Quantity not found"))?; - - let token_id = CairoU256 { - low: (*token_id_low) - .try_into() - .map_err(|_| anyhow!("Failed to parse token id low"))?, - high: (*token_id_high) - .try_into() - .map_err(|_| anyhow!("Failed to parse token id high"))?, - }; - - let event_id = Self::get_event_id( - &token_id, - maker_address, - taker_address, - block_timestamp, - event, - ); - - Ok(TokenSaleEvent { - event_id: to_hex_str(&event_id), - event_type: EventType::Sale, - block_number: event.block_number, - from_address: to_hex_str(maker_address), - to_address: to_hex_str(taker_address), - nft_contract_address: to_hex_str(nft_contract_address), - nft_type: None, - transaction_hash: to_hex_str(&event.transaction_hash), - token_id_hex: token_id.to_hex(), - token_id: token_id.to_decimal(false), - timestamp: block_timestamp, - updated_at: Some(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()), - quantity: (*quantity) - .try_into() - .map_err(|_| anyhow!("Failed to parse quantity"))?, - currency_address: Some(to_hex_str(currency_address)), - marketplace_contract_address: to_hex_str(&event.from_address), - marketplace_name: "Element".to_string(), - price: price.to_big_decimal(0).to_string(), - }) - } - - /// Formats & register a token event based on the event content. - /// Returns the token_id if the event were identified. - pub async fn format_and_register_event( - &self, - event: &EmittedEvent, - contract_type: ContractType, - block_timestamp: u64, - ) -> Result<(CairoU256, TokenTransferEvent)> { - let mut token_event = TokenTransferEvent::default(); - - trace!( - "Format transfer event to insert: event={:?}, contract_type={:?}, timestamp={}", - event, - contract_type, - block_timestamp - ); - - // As cairo didn't have keys before, we first check if the data - // contains the info. If not, we check into the keys, skipping the first - // element which is the selector. - let event_info: (FieldElement, FieldElement, CairoU256) = - if let Some(d_info) = Self::get_event_info_from_felts(&event.data) { - d_info - } else if let Some(k_info) = Self::get_event_info_from_felts(&event.keys[1..]) { - k_info - } else { - return Err(anyhow!("Can't find event data into this event")); - }; - - let (from, to, token_id) = event_info; - - let event_id = Self::get_event_id(&token_id, &from, &to, block_timestamp, event); - - token_event.from_address = to_hex_str(&from); - token_event.to_address = to_hex_str(&to); - token_event.contract_address = to_hex_str(&event.from_address); - token_event.transaction_hash = to_hex_str(&event.transaction_hash); - token_event.token_id_hex = token_id.to_hex(); - token_event.token_id = token_id.to_decimal(false); - token_event.timestamp = block_timestamp; - token_event.event_type = Self::get_event_type(from, to); - token_event.event_id = to_hex_str(&event_id); - token_event.block_number = event.block_number; - token_event.contract_type = contract_type.to_string(); - token_event.updated_at = Some( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - ); - - trace!("Registering event: {:?}", token_event); - - self.storage - .register_transfer_event(&token_event, block_timestamp) - .await?; - - Ok((token_id, token_event.clone())) - } - - pub fn get_event_type(from: FieldElement, to: FieldElement) -> EventType { - if from == FieldElement::ZERO { - EventType::Mint - } else if to == FieldElement::ZERO { - EventType::Burn - } else { - EventType::Transfer - } - } - - /// Returns the event id as a field element. - /// We enforce everything to be a field element to have fix - /// bytes lengths, and ease the re-computation of this value - /// from else where. - pub fn get_event_id( - token_id: &CairoU256, - from: &FieldElement, - to: &FieldElement, - timestamp: u64, - event: &EmittedEvent, - ) -> FieldElement { - let mut bytes = Vec::new(); - bytes.extend_from_slice(&FieldElement::from(token_id.low).to_bytes_be()); - bytes.extend_from_slice(&FieldElement::from(token_id.high).to_bytes_be()); - bytes.extend_from_slice(&from.to_bytes_be()); - bytes.extend_from_slice(&to.to_bytes_be()); - bytes.extend_from_slice(&event.from_address.to_bytes_be()); - bytes.extend_from_slice(&event.transaction_hash.to_bytes_be()); - bytes.extend_from_slice(&FieldElement::from(timestamp).to_bytes_be()); - starknet_keccak(&bytes) - } - - /// Returns the event info from vector of felts. - /// Event info are (from, to, token_id). - /// - /// This methods considers that the info of the - /// event is starting at index 0 of the input vector. - fn get_event_info_from_felts( - felts: &[FieldElement], - ) -> Option<(FieldElement, FieldElement, CairoU256)> { - if felts.len() < 4 { - return None; - } - let from = felts[0]; - let to = felts[1]; - - // Safe to unwrap, as emitted events follow cairo sequencer specification. - let token_id = CairoU256 { - low: felts[2].try_into().unwrap(), - high: felts[3].try_into().unwrap(), - }; - - Some((from, to, token_id)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::storage::MockStorage; - - /// Sets up sample data and event for testing purposes. - fn setup_sample_event() -> EmittedEvent { - let block_hash = match FieldElement::from_dec_str("786") { - Ok(value) => value, - Err(e) => panic!("Failed to parse string to FieldElement: {}", e), - }; - - let transaction_hash = match FieldElement::from_dec_str("5432") { - Ok(value) => value, - Err(e) => panic!("Failed to parse string to FieldElement: {}", e), - }; - - EmittedEvent { - from_address: FieldElement::from_hex_be("0x0").unwrap(), - block_hash: Some(block_hash), - transaction_hash: transaction_hash, - block_number: Some(111), - keys: vec![ - TRANSFER_SELECTOR, - FieldElement::from_hex_be("0x1234").unwrap(), - FieldElement::from_hex_be("0x5678").unwrap(), - ], - data: vec![ - FieldElement::from_hex_be("0x1234").unwrap(), - FieldElement::from_hex_be("0x5678").unwrap(), - FieldElement::from_dec_str("91011").unwrap(), - FieldElement::from_dec_str("121314").unwrap(), - ], - } - } - - #[tokio::test] - async fn test_format_event_successfully() { - let mut storage = MockStorage::default(); - - storage - .expect_register_transfer_event() - .returning(|_, _| Box::pin(futures::future::ready(Ok(())))); - - let manager = EventManager::new(Arc::new(storage)); - - let sample_event = setup_sample_event(); - let contract_type = ContractType::ERC721; - let timestamp = 1234567890; - - let result = manager - .format_and_register_event(&sample_event, contract_type, timestamp) - .await; - - assert!(result.is_ok()); - - let (_, token_event) = result.unwrap(); - - assert_eq!( - token_event.from_address, - to_hex_str(&FieldElement::from_hex_be("0x1234").unwrap()) - ); - } - - #[tokio::test] - async fn test_format_event_data_extraction_from_data() { - // Initialize a MockStorage and the EventManager - let mut storage = MockStorage::default(); - - storage - .expect_register_transfer_event() - .returning(|_, _| Box::pin(futures::future::ready(Ok(())))); - - let manager = EventManager::new(Arc::new(storage)); - - // Construct an event where the event data is only present in `event.data` - // and not in `event.keys`. - let sample_event = EmittedEvent { - from_address: FieldElement::from_hex_be("0x0").unwrap(), - block_hash: Some(FieldElement::from_dec_str("786").unwrap()), - transaction_hash: FieldElement::from_dec_str("5432").unwrap(), - block_number: Some(111), - keys: vec![ - TRANSFER_SELECTOR, // This is the selector, so it's not used to extract event data - ], - data: vec![ - FieldElement::from_hex_be("0x1234").unwrap(), // from - FieldElement::from_hex_be("0x5678").unwrap(), // to - FieldElement::from_dec_str("91011").unwrap(), // token_id_low - FieldElement::from_dec_str("121314").unwrap(), // token_id_high - ], - }; - - let contract_type = ContractType::ERC721; - let timestamp = 1234567890; - - // Call the `format_event` function - let result = manager - .format_and_register_event(&sample_event, contract_type, timestamp) - .await; - - // Assertions - assert!(result.is_ok()); - let (token_id, token_event) = result.unwrap(); - - // Check if the extracted data matches the data from `event.data` - assert_eq!( - token_event.from_address, - to_hex_str(&FieldElement::from_hex_be("0x1234").unwrap()) - ); - assert_eq!( - token_event.to_address, - to_hex_str(&FieldElement::from_hex_be("0x5678").unwrap()) - ); - assert_eq!(token_id.low, 91011_u128); - assert_eq!(token_id.high, 121314_u128); - } - - #[test] - fn test_keys_selector() { - let storage = Arc::new(MockStorage::default()); - let manager = EventManager::new(storage); - - // Call the method - let result = manager.keys_selector().unwrap(); - - // Define expected result - let expected = vec![vec![ - selector!("Transfer"), - FieldElement::from_hex_be(ELEMENT_NFT_MARKETPLACE_HEX).unwrap(), - FieldElement::from_hex_be(VENTORY_MARKETPLACE_EVENT_HEX).unwrap(), - FieldElement::from_hex_be(VENTORY_MARKETPLACE_OFFER_ACCEPTED_EVENT_HEX).unwrap(), - ]]; - - // Assert the output - assert_eq!(result, expected); - } - - /// Tests the `get_event_info_from_felts` method with correct input format and length. - /// Ensures that the method correctly extracts and returns the event info. - #[test] - fn test_get_event_info_from_felts() { - // Create sample data for the test - let from_value = FieldElement::from_dec_str("1234").unwrap(); - let to_value = FieldElement::from_dec_str("5678").unwrap(); - let token_id_low = 91011_u128; - let token_id_high = 121314_u128; - - let sample_data = vec![ - from_value, - to_value, - token_id_low.into(), - token_id_high.into(), - ]; - - // Call the method - let result = EventManager::::get_event_info_from_felts(&sample_data); - - // Assert the output - assert_eq!(result.is_some(), true); - let (from, to, token_id) = result.unwrap(); - assert_eq!(from, from_value); - assert_eq!(to, to_value); - assert_eq!(token_id.low, token_id_low); - assert_eq!(token_id.high, token_id_high); - } - - /// Tests the `get_event_info_from_felts` method with insufficient FieldElements. - /// Ensures that the method returns None when not provided enough data. - #[test] - fn test_get_event_info_from_felts_insufficient_data() { - // Create sample data for the test with insufficient FieldElements - let sample_data = vec![ - FieldElement::from_dec_str("1234").unwrap(), - FieldElement::from_dec_str("5678").unwrap(), - ]; - - // Call the method - let result = EventManager::::get_event_info_from_felts(&sample_data); - - // Assert the output - assert_eq!(result.is_none(), true); - } -} diff --git a/crates/pontos/src/managers/mod.rs b/crates/pontos/src/managers/mod.rs deleted file mode 100644 index e394cb944..000000000 --- a/crates/pontos/src/managers/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod contract_manager; -pub use contract_manager::ContractManager; - -pub mod event_manager; -pub use event_manager::EventManager; - -pub mod token_manager; -pub use token_manager::TokenManager; - -pub mod block_manager; -pub use block_manager::{BlockManager, PendingBlockData}; diff --git a/crates/pontos/src/managers/token_manager.rs b/crates/pontos/src/managers/token_manager.rs deleted file mode 100644 index 50b601e77..000000000 --- a/crates/pontos/src/managers/token_manager.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::storage::types::{EventType, TokenInfo, TokenMintInfo, TokenTransferEvent}; -use crate::storage::Storage; -use anyhow::{anyhow, Result}; -use ark_starknet::client::StarknetClient; -use ark_starknet::format::to_hex_str; -use ark_starknet::CairoU256; -use starknet::core::types::*; -use starknet::macros::selector; -use std::sync::Arc; - -#[derive(Debug)] -pub struct TokenManager { - storage: Arc, - client: Arc, -} - -impl TokenManager { - /// Initializes a new instance. - pub fn new(storage: Arc, client: Arc) -> Self { - Self { - storage: Arc::clone(&storage), - client: Arc::clone(&client), - } - } - - /// Formats a token registry from the token event data. - pub async fn format_and_register_token( - &self, - token_id: &CairoU256, - event: &TokenTransferEvent, - block_timestamp: u64, - block_number: Option, - ) -> Result<()> { - let mut token = TokenInfo { - contract_address: event.contract_address.clone(), - token_id: event.token_id.clone(), - chain_id: event.chain_id.clone(), - token_id_hex: event.token_id_hex.clone(), - ..Default::default() - }; - - let token_owner_raw_result = self - .get_token_owner( - FieldElement::from_hex_be(&event.contract_address) - .expect("Contract address bad format"), - token_id.low.into(), - token_id.high.into(), - ) - .await; - - token.owner = token_owner_raw_result - .ok() - .and_then(|owner| owner.first().map(to_hex_str)) - .unwrap_or_default(); - - self.storage.register_token(&token, block_timestamp).await?; - - if event.event_type == EventType::Mint { - let info = TokenMintInfo { - address: event.to_address.clone(), - timestamp: event.timestamp, - transaction_hash: event.transaction_hash.clone(), - block_number, - }; - - self.storage - .register_mint( - &token.contract_address, - &token.token_id_hex, - &token.token_id, - &info, - ) - .await?; - } - - Ok(()) - } - - /// Retrieves the token owner for the last block. - pub async fn get_token_owner( - &self, - contract_address: FieldElement, - token_id_low: FieldElement, - token_id_high: FieldElement, - ) -> Result> { - let block = BlockId::Tag(BlockTag::Pending); - let selectors = vec![selector!("owner_of"), selector!("ownerOf")]; - - for selector in selectors { - if let Ok(res) = self - .client - .call_contract( - contract_address, - selector, - vec![token_id_low, token_id_high], - block, - ) - .await - { - return Ok(res); - } - } - - Err(anyhow!("Failed to get token owner from chain")) - } -} - -#[cfg(test)] -mod tests { - use crate::storage::MockStorage; - use ark_starknet::client::MockStarknetClient; - - use super::*; - - #[tokio::test] - async fn test_get_token_owner() { - let mock_storage = MockStorage::default(); - let mut mock_client = MockStarknetClient::default(); - - let contract_address = FieldElement::from_dec_str("12345").unwrap(); - let token_id_low = FieldElement::from_dec_str("23456").unwrap(); - let token_id_high = FieldElement::from_dec_str("34567").unwrap(); - - mock_client - .expect_call_contract() - .returning(|_, _, _, _| Ok(vec![FieldElement::from_dec_str("1").unwrap()])); - - let token_manager = TokenManager::new(Arc::new(mock_storage), Arc::new(mock_client)); - - let result = token_manager - .get_token_owner(contract_address, token_id_low, token_id_high) - .await; - - assert!(result.is_ok()); - let owners = result.unwrap(); - - assert_eq!(owners.len(), 1); - assert_eq!(owners[0], FieldElement::from_dec_str("1").unwrap()); - } -} diff --git a/crates/pontos/src/storage/mod.rs b/crates/pontos/src/storage/mod.rs deleted file mode 100644 index ef4a658f6..000000000 --- a/crates/pontos/src/storage/mod.rs +++ /dev/null @@ -1,75 +0,0 @@ -#[cfg(feature = "sqlxdb")] -pub mod sqlx; -pub mod types; -pub mod utils; -use self::types::TokenSaleEvent; -use crate::storage::types::{ - BlockInfo, ContractInfo, ContractType, StorageError, TokenInfo, TokenMintInfo, - TokenTransferEvent, -}; -use async_trait::async_trait; -#[cfg(test)] -use mockall::automock; -#[cfg(feature = "sqlxdb")] -pub use sqlx::DefaultSqlxStorage; - -#[async_trait] -#[cfg_attr(test, automock)] -pub trait Storage { - async fn register_mint( - &self, - contract_address: &str, - token_id_hex: &str, - token_id: &str, - info: &TokenMintInfo, - ) -> Result<(), StorageError>; - - async fn register_token( - &self, - token: &TokenInfo, - block_timestamp: u64, - ) -> Result<(), StorageError>; - - async fn register_sale_event( - &self, - event: &TokenSaleEvent, - block_timestamp: u64, - ) -> Result<(), StorageError>; - - async fn register_transfer_event( - &self, - event: &TokenTransferEvent, - block_timestamp: u64, - ) -> Result<(), StorageError>; - - async fn get_contract_type( - &self, - contract_address: &str, - chain_id: &str, - ) -> Result; - - async fn register_contract_info( - &self, - info: &ContractInfo, - block_timestamp: u64, - chain_id: &str, - ) -> Result<(), StorageError>; - - /// A block info is only set if the block has a number and a timestamp. - async fn set_block_info( - &self, - block_number: u64, - block_timestamp: u64, - info: BlockInfo, - ) -> Result<(), StorageError>; - - async fn get_block_info(&self, block_number: u64) -> Result; - - /// The block timestamps is always present. But the number can be missing - /// for the pending block support. - async fn clean_block( - &self, - block_timestamp: u64, - block_number: Option, - ) -> Result<(), StorageError>; -} diff --git a/crates/pontos/src/storage/sqlx/default_storage.rs b/crates/pontos/src/storage/sqlx/default_storage.rs deleted file mode 100644 index 63c91379f..000000000 --- a/crates/pontos/src/storage/sqlx/default_storage.rs +++ /dev/null @@ -1,394 +0,0 @@ -//! Implementation of default storage using sqlx crate. -//! -//! The implementation in this file is very naive, and mostly -//! used for testing and as an example of implementation. -//! No optimization was done for indexing or PK/FK managment. -use async_trait::async_trait; - -use log::trace; -use sqlx::{any::AnyPoolOptions, AnyPool, Error as SqlxError, FromRow}; -use std::str::FromStr; - -use super::types::*; -use crate::storage::types::*; -use crate::Storage; - -impl From for StorageError { - fn from(e: SqlxError) -> Self { - StorageError::DatabaseError(e.to_string()) - } -} - -pub struct DefaultSqlxStorage { - pool: AnyPool, -} - -impl DefaultSqlxStorage { - pub fn get_pool_ref(&self) -> &AnyPool { - &self.pool - } - - pub async fn new_any(db_url: &str) -> Result { - Ok(Self { - pool: AnyPoolOptions::new() - .max_connections(1) - .connect(db_url) - .await?, - }) - } - - pub async fn dump_tables(&self) -> Result<(), StorageError> { - let q = "SELECT * FROM token"; - let rows = sqlx::query(q).fetch_all(&self.pool).await?; - - rows.iter().for_each(|r| { - println!("{:?}", TokenData::from_row(r).unwrap()); - }); - - Ok(()) - } - - async fn get_token_by_id( - &self, - contract_address: &str, - _token_id_hex: &str, - token_id: &str, - ) -> Result, StorageError> { - let q = "SELECT * FROM token WHERE contract_address = $1 AND token_id = $2"; - - match sqlx::query(q) - .bind(contract_address) - .bind(token_id) - .fetch_all(&self.pool) - .await - { - Ok(rows) => { - if rows.is_empty() { - Ok(None) - } else { - Ok(Some(TokenData::from_row(&rows[0])?)) - } - } - Err(e) => Err(StorageError::DatabaseError(e.to_string())), - } - } - - async fn get_event_by_id(&self, event_id: &str) -> Result, StorageError> { - let q = "SELECT * FROM event WHERE event_id = $1"; - - match sqlx::query(q).bind(event_id).fetch_all(&self.pool).await { - Ok(rows) => { - if rows.is_empty() { - Ok(None) - } else { - Ok(Some(EventData::from_row(&rows[0])?)) - } - } - Err(e) => Err(StorageError::DatabaseError(e.to_string())), - } - } - - async fn get_contract_by_address( - &self, - contract_address: &str, - chain_id: &str, - ) -> Result, StorageError> { - let q = "SELECT * FROM contract WHERE contract_address = $1 AND chain_id = $2"; - - match sqlx::query(q) - .bind(contract_address.to_string()) - .bind(chain_id.to_string()) - .fetch_all(&self.pool) - .await - { - Ok(rows) => { - if rows.is_empty() { - Ok(None) - } else { - Ok(Some(ContractData::from_row(&rows[0])?)) - } - } - Err(e) => Err(StorageError::DatabaseError(e.to_string())), - } - } - - async fn get_block_by_timestamp(&self, ts: u64) -> Result, StorageError> { - let q = "SELECT block_number, block_status, block_timestamp, indexer_identifier FROM block WHERE block_timestamp = $1"; - - match sqlx::query(q) - .bind(ts.to_string()) - .fetch_all(&self.pool) - .await - { - Ok(rows) => { - if rows.is_empty() { - Ok(None) - } else { - Ok(Some(Block::from_row(&rows[0])?)) - } - } - Err(e) => Err(StorageError::DatabaseError(e.to_string())), - } - } -} - -#[async_trait] -impl Storage for DefaultSqlxStorage { - async fn register_mint( - &self, - contract_address: &str, - _token_id_hex: &str, - token_id: &str, - info: &TokenMintInfo, - ) -> Result<(), StorageError> { - trace!( - "Registering mint {} {} {:?}", - contract_address, - token_id, - info - ); - - let q = "UPDATE token SET mint_address = $1, mint_timestamp = $2, mint_transaction_hash = $3 WHERE token_id = $4"; - - let _r = sqlx::query(q) - .bind(info.address.clone()) - .bind(info.timestamp.to_string()) - .bind(info.transaction_hash.clone()) - .bind(token_id) - .execute(&self.pool) - .await?; - - Ok(()) - } - - async fn register_token( - &self, - token: &TokenInfo, - block_timestamp: u64, - ) -> Result<(), StorageError> { - trace!("Registering token {:?}", token); - - if (self - .get_token_by_id( - &token.contract_address, - &token.token_id_hex, - &token.token_id, - ) - .await?) - .is_some() - { - return Err(StorageError::AlreadyExists(format!( - "token id = {}", - token.token_id_hex - ))); - } - - let q = "INSERT INTO token (contract_address, token_id, chain_id, owner, block_timestamp) VALUES ($1, $2, $3, $4, $5)"; - - let _r = sqlx::query(q) - .bind(token.contract_address.clone()) - .bind(token.token_id.clone()) - .bind(token.chain_id.clone()) - .bind(token.owner.clone()) - .bind(block_timestamp.to_string()) - .execute(&self.pool) - .await?; - - Ok(()) - } - - async fn register_sale_event( - &self, - _event: &TokenSaleEvent, - _block_timestamp: u64, - ) -> Result<(), StorageError> { - Ok(()) - } - - async fn register_transfer_event( - &self, - event: &TokenTransferEvent, - _block_timestamp: u64, - ) -> Result<(), StorageError> { - trace!("Registering event {:?}", event); - - if (self.get_event_by_id(&event.event_id).await?).is_some() { - return Err(StorageError::AlreadyExists(format!( - "event id = {}", - event.event_id - ))); - } - - let q = "INSERT INTO token_event (block_timestamp, contract_address, from_address, to_address, transaction_hash, token_id, contract_type, event_type, event_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)"; - - let _r = sqlx::query(q) - .bind(event.timestamp.to_string()) - .bind(event.from_address.clone()) - .bind(event.to_address.clone()) - .bind(event.contract_address.clone()) - .bind(event.transaction_hash.clone()) - .bind(event.token_id.clone()) - .bind(event.contract_type.clone()) - .bind(event.event_type.to_string()) - .bind(event.event_id.clone()) - .execute(&self.pool) - .await?; - - Ok(()) - } - - async fn get_contract_type( - &self, - contract_address: &str, - chain_id: &str, - ) -> Result { - trace!("Getting contract info for contract {}", contract_address); - - if let Some(c) = self - .get_contract_by_address(contract_address, chain_id) - .await? - { - Ok(ContractType::from_str(&c.contract_type).unwrap()) - } else { - Err(StorageError::NotFound(format!( - "contract_address: {contract_address}" - ))) - } - } - - async fn register_contract_info( - &self, - info: &ContractInfo, - block_timestamp: u64, - chain_id: &str, - ) -> Result<(), StorageError> { - trace!( - "Registering contract info {:?} for contract {}", - info.contract_type, - info.contract_address - ); - - if (self - .get_contract_by_address(&info.contract_address, chain_id) - .await?) - .is_some() - { - return Err(StorageError::AlreadyExists(format!( - "contract addr = {}", - info.contract_address - ))); - } - - let q = "INSERT INTO contract (contract_address, contract_type, deployed_timestamp) VALUES ($1, $2, $3)"; - - let _r = sqlx::query(q) - .bind(info.contract_address.clone()) - .bind(info.contract_type.to_string()) - .bind(block_timestamp.to_string()) - .execute(&self.pool) - .await?; - - Ok(()) - } - - async fn set_block_info( - &self, - block_number: u64, - block_timestamp: u64, - info: BlockInfo, - ) -> Result<(), StorageError> { - trace!("Setting block info {:?} for block #{}", info, block_number); - - let exists = sqlx::query("SELECT 1 FROM indexer WHERE indexer_identifier = $1") - .bind(info.indexer_identifier.clone()) - .fetch_optional(&self.pool) - .await? - .is_some(); - - if !exists { - let q = "INSERT INTO indexer (indexer_identifier, indexer_version) VALUES ($1, $2)"; - sqlx::query(q) - .bind(info.indexer_identifier.clone()) - .bind(info.indexer_version.clone()) - .execute(&self.pool) - .await?; - } - - let _r = if (self.get_block_by_timestamp(block_timestamp).await?).is_some() { - let q = "UPDATE block SET block_number = $1, block_status = $2, indexer_identifier = $3 WHERE block_timestamp = $4"; - sqlx::query(q) - .bind(block_number.to_string()) - .bind(info.status.to_string()) - .bind(info.indexer_identifier.clone()) - .bind(block_timestamp.to_string()) - .execute(&self.pool) - .await? - } else { - let q = "INSERT INTO block (block_timestamp, block_number, block_status, indexer_identifier) VALUES ($1, $2, $3, $4) ON CONFLICT (block_number) DO NOTHING"; - - sqlx::query(q) - .bind(block_timestamp.to_string()) - .bind(block_number.to_string()) - .bind(info.status.to_string()) - .bind(info.indexer_identifier.clone()) - .execute(&self.pool) - .await? - }; - - Ok(()) - } - - async fn get_block_info(&self, block_number: u64) -> Result { - trace!("Getting block info for block #{}", block_number); - - let q = "SELECT * FROM block WHERE block_number = $1"; - - match sqlx::query(q) - .bind(block_number.to_string()) - .fetch_all(&self.pool) - .await - { - Ok(rows) => { - if rows.is_empty() { - Err(StorageError::NotFound(format!( - "block number {block_number}" - ))) - } else { - let d = BlockData::from_row(&rows[0])?; - Ok(BlockInfo { - indexer_version: d.indexer_version.clone(), - indexer_identifier: d.indexer_identifier.clone(), - status: BlockIndexingStatus::from_str(&d.status).unwrap(), - block_number, - }) - } - } - Err(e) => Err(StorageError::DatabaseError(e.to_string())), - } - } - - async fn clean_block( - &self, - block_timestamp: u64, - block_number: Option, - ) -> Result<(), StorageError> { - trace!( - "Cleaning block #{:?} [ts: {}]", - block_number, - block_timestamp.to_string() - ); - let q = "DELETE FROM block WHERE block_timestamp = $1::bigint"; - sqlx::query(q) - .bind(block_timestamp.to_string()) - .fetch_all(&self.pool) - .await?; - - let q = "DELETE FROM token_event WHERE block_timestamp = $1::bigint"; - sqlx::query(q) - .bind(block_timestamp.to_string()) - .fetch_all(&self.pool) - .await?; - - Ok(()) - } -} diff --git a/crates/pontos/src/storage/sqlx/migrations/0_default.sql b/crates/pontos/src/storage/sqlx/migrations/0_default.sql deleted file mode 100644 index b59ee4319..000000000 --- a/crates/pontos/src/storage/sqlx/migrations/0_default.sql +++ /dev/null @@ -1,51 +0,0 @@ --- SQL default migration with simple tables. --- --- TODO: investigate why sqlx is complaining for --- NULL not being compatible with `Option`... --- Supposed to be fixed in older versions of sqlx. - -CREATE TABLE token ( - contract_address TEXT NOT NULL, - token_id TEXT NOT NULL, - token_id_hex TEXT NOT NULL, - owner TEXT NOT NULL, - mint_address TEXT DEFAULT '', - mint_timestamp BIGINT DEFAULT 0, - mint_transaction_hash TEXT DEFAULT '', - block_timestamp BIGINT NOT NULL, - - PRIMARY KEY (contract_address, token_id_hex) -); - -CREATE TABLE event ( - block_timestamp BIGINT NOT NULL, - from_address TEXT NOT NULL, - to_address TEXT NOT NULL, - contract_address TEXT NOT NULL, - transaction_hash TEXT NOT NULL, - token_id TEXT NOT NULL, - token_id_hex TEXT NOT NULL, - contract_type TEXT NOT NULL, - event_type TEXT NOT NULL, - event_id TEXT NOT NULL, - - PRIMARY KEY (event_id) -); - -CREATE TABLE block ( - block_timestamp BIGINT NOT NULL, - block_number BIGINT NOT NULL, - status TEXT NOT NULL, - indexer_version TEXT NOT NULL, - indexer_identifier TEXT NOT NULL, - - PRIMARY KEY (block_timestamp) -); - -CREATE TABLE contract ( - contract_address TEXT NOT NULL, - contract_type TEXT NOT NULL, - block_timestamp BIGINT NOT NULL, - - PRIMARY KEY (contract_address) -); diff --git a/crates/pontos/src/storage/sqlx/mod.rs b/crates/pontos/src/storage/sqlx/mod.rs deleted file mode 100644 index 4ca496c5f..000000000 --- a/crates/pontos/src/storage/sqlx/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Module implementing Sqlx backend for Pontos. -//! -//! The main objective of this module is to add a default -//! implementation for examples and testing. -//! No optimization was made at database level. -pub mod default_storage; -pub use default_storage::DefaultSqlxStorage; - -pub mod types; diff --git a/crates/pontos/src/storage/sqlx/types.rs b/crates/pontos/src/storage/sqlx/types.rs deleted file mode 100644 index a0e7a97a2..000000000 --- a/crates/pontos/src/storage/sqlx/types.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Those types are decoupling the actual pontos -//! storage types and the data annotations required -//! for sqlx code generation. - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct TokenData { - pub contract_address: String, - pub token_id: String, - pub token_id_hex: String, - pub owner: String, - pub block_timestamp: i64, - pub mint_address: Option, - pub mint_timestamp: Option, - pub mint_transaction_hash: Option, -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct EventData { - pub block_timestamp: i64, - pub contract_address: String, - pub from_address: String, - pub to_address: String, - pub transaction_hash: String, - pub token_id: String, - pub token_id_hex: String, - pub contract_type: String, - pub event_type: String, - pub event_id: String, -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct Block { - #[sqlx(rename = "block_timestamp")] - pub timestamp: i64, - #[sqlx(rename = "block_number")] - pub number: i64, - #[sqlx(rename = "block_status")] - pub status: String, - pub indexer_identifier: String, -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct BlockData { - #[sqlx(rename = "block_timestamp")] - pub timestamp: i64, - #[sqlx(rename = "block_number")] - pub number: i64, - #[sqlx(rename = "block_status")] - pub status: String, - pub indexer_version: String, - pub indexer_identifier: String, -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ContractData { - pub block_timestamp: i64, - pub contract_address: String, - pub contract_type: String, -} diff --git a/crates/pontos/src/storage/types.rs b/crates/pontos/src/storage/types.rs deleted file mode 100644 index bad7032b3..000000000 --- a/crates/pontos/src/storage/types.rs +++ /dev/null @@ -1,383 +0,0 @@ -use serde::{Deserialize, Serialize, Serializer}; -use std::collections::HashMap; -use std::fmt; -use std::str::FromStr; - -#[derive(Debug, Clone)] -pub enum StorageError { - DatabaseError(String), - NotFound(String), - InvalidStatus(String), - DuplicateToken(String), - InvalidMintData(String), - AlreadyExists(String), -} - -impl fmt::Display for StorageError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Note the lifetime parameter <'_> - match self { - StorageError::DatabaseError(s) => write!(f, "Database error occurred: {s}"), - StorageError::NotFound(s) => write!(f, "Item not found in storage: {s}"), - StorageError::InvalidStatus(s) => write!(f, "Invalid status: {s}"), - StorageError::DuplicateToken(s) => write!(f, "Token already exists in storage: {s}"), - StorageError::InvalidMintData(s) => write!(f, "Provided mint data is invalid: {s}"), - StorageError::AlreadyExists(s) => write!(f, "Item already exists in storage: {s}"), - } - } -} - -impl std::error::Error for StorageError {} - -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum EventType { - Mint, - Burn, - Transfer, - Uninitialized, - Sale, -} - -impl fmt::Display for EventType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EventType::Mint => write!(f, "MINT"), - EventType::Burn => write!(f, "BURN"), - EventType::Transfer => write!(f, "TRANSFER"), - EventType::Uninitialized => write!(f, "UNINITIALIZED"), - EventType::Sale => write!(f, "SALE"), - } - } -} - -impl FromStr for EventType { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "MINT" => Ok(EventType::Mint), - "BURN" => Ok(EventType::Burn), - "TRANSFER" => Ok(EventType::Transfer), - "UNINITIALIZED" => Ok(EventType::Uninitialized), - "SALE" => Ok(EventType::Sale), - _ => Err(()), - } - } -} - -impl Serialize for TokenEvent { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let fields_to_serialize = match self { - TokenEvent::Transfer(event) => { - let mut map = HashMap::new(); - map.insert("timestamp", event.timestamp.to_string()); - map.insert("from_address", event.from_address.clone()); - map.insert("to_address", event.to_address.clone()); - map.insert("contract_address", event.contract_address.clone()); - map.insert("transaction_hash", event.transaction_hash.clone()); - map.insert("token_id", event.token_id.clone()); - map.insert("token_id_hex", event.token_id_hex.clone()); - map.insert("contract_type", event.contract_type.clone()); - map.insert("event_type", "transfer".to_string()); - map.insert("event_id", event.event_id.clone()); - map.insert( - "block_number", - event - .block_number - .map_or("".to_string(), |block_number| block_number.to_string()), - ); - - map - } - TokenEvent::Sale(event) => { - let mut map = HashMap::new(); - map.insert("event_id", event.token_id_hex.clone()); - map.insert("event_type", "sale".to_string()); - map.insert("from_address", event.from_address.clone()); - map.insert("timestamp", event.timestamp.to_string()); - map.insert("to_address", event.to_address.clone()); - map.insert("nft_contract_address", event.nft_contract_address.clone()); - map.insert( - "marketplace_contract_address", - event.marketplace_contract_address.clone(), - ); - map.insert("marketplace_name", event.marketplace_name.clone()); - map.insert("transaction_hash", event.transaction_hash.clone()); - map.insert("token_id", event.token_id.clone()); - map.insert("token_id_hex", event.token_id_hex.clone()); - map.insert("quantity", event.quantity.to_string()); - - if let Some(currency_address) = event.currency_address.clone() { - map.insert("currency_address", currency_address); - } - - map.insert("price", event.price.clone()); - map.insert( - "block_number", - event - .block_number - .map_or("".to_string(), |block_number| block_number.to_string()), - ); - - map - } - }; - - fields_to_serialize.serialize(serializer) - } -} - -#[derive(Debug, Clone, PartialEq, Deserialize)] -pub enum TokenEvent { - Transfer(TokenTransferEvent), - Sale(TokenSaleEvent), -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct TokenTransferEvent { - pub timestamp: u64, - pub from_address: String, - pub to_address: String, - pub contract_address: String, - pub chain_id: String, - pub contract_type: String, - pub transaction_hash: String, - pub token_id: String, - pub token_id_hex: String, - pub event_type: EventType, - pub event_id: String, - pub block_number: Option, - pub updated_at: Option, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct TokenSaleEvent { - pub timestamp: u64, - pub from_address: String, - pub to_address: String, - pub nft_contract_address: String, - pub nft_type: Option, - pub marketplace_contract_address: String, - pub marketplace_name: String, - pub transaction_hash: String, - pub token_id: String, - pub token_id_hex: String, - pub event_type: EventType, - pub event_id: String, - pub block_number: Option, - pub updated_at: Option, - pub quantity: u64, - pub currency_address: Option, - pub price: String, -} - -impl Default for TokenTransferEvent { - fn default() -> Self { - TokenTransferEvent { - timestamp: 0, - from_address: String::new(), - to_address: String::new(), - contract_address: String::new(), - contract_type: String::new(), - transaction_hash: String::new(), - token_id: String::new(), - token_id_hex: String::new(), - event_type: EventType::Uninitialized, - event_id: "0".to_string(), - block_number: None, - updated_at: None, - chain_id: "0x534e5f4d41494e".to_string(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] -pub struct TokenInfo { - pub contract_address: String, - pub token_id: String, - pub chain_id: String, - pub token_id_hex: String, - pub owner: String, -} - -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] -pub struct TokenMintInfo { - pub address: String, - pub timestamp: u64, - pub transaction_hash: String, - pub block_number: Option, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum BlockIndexingStatus { - None, - Processing, - Terminated, -} - -#[allow(clippy::to_string_trait_impl)] -impl ToString for BlockIndexingStatus { - fn to_string(&self) -> String { - match self { - BlockIndexingStatus::None => "None".to_string(), - BlockIndexingStatus::Processing => "Processing".to_string(), - BlockIndexingStatus::Terminated => "Terminated".to_string(), - } - } -} - -impl FromStr for BlockIndexingStatus { - type Err = (); - - fn from_str(s: &str) -> Result { - match s { - "None" => Ok(BlockIndexingStatus::None), - "Processing" => Ok(BlockIndexingStatus::Processing), - "Terminated" => Ok(BlockIndexingStatus::Terminated), - _ => Err(()), - } - } -} - -#[derive(Debug)] -pub enum IndexerStatus { - Requested, - Running, - Stopped, -} - -impl fmt::Display for IndexerStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - IndexerStatus::Requested => write!(f, "requested"), - IndexerStatus::Running => write!(f, "running"), - IndexerStatus::Stopped => write!(f, "stopped"), - } - } -} - -pub struct Range { - pub start: u64, - pub end: u64, -} - -pub struct BlockIndexing { - pub range: Range, - pub percentage: u64, - pub status: IndexerStatus, - pub identifier: String, - pub indexer_version: u64, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct BlockInfo { - pub indexer_version: String, - pub indexer_identifier: String, - pub status: BlockIndexingStatus, - pub block_number: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ContractType { - Other, - ERC721, - ERC1155, -} - -#[allow(clippy::to_string_trait_impl)] -impl ToString for ContractType { - fn to_string(&self) -> String { - match self { - ContractType::Other => "OTHER".to_string(), - ContractType::ERC721 => "ERC721".to_string(), - ContractType::ERC1155 => "ERC1155".to_string(), - } - } -} - -impl FromStr for ContractType { - type Err = (); // You can use a more descriptive error type if needed - - fn from_str(s: &str) -> Result { - match s { - "ERC721" => Ok(ContractType::ERC721), - "ERC1155" => Ok(ContractType::ERC1155), - _ => Ok(ContractType::Other), - } - } -} - -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)] -pub struct ContractInfo { - pub contract_address: String, - pub chain_id: String, - pub contract_type: String, - pub name: Option, - pub symbol: Option, - pub image: Option, -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json::{json, Value}; - - #[test] - fn test_token_event_transfer_serialization() { - let event = TokenEvent::Transfer(TokenTransferEvent { - timestamp: 1625097600, - from_address: "0xfrom".to_string(), - to_address: "0xto".to_string(), - contract_address: "0xcontract".to_string(), - contract_type: "ERC721".to_string(), - transaction_hash: "0xhash".to_string(), - token_id: "123".to_string(), - token_id_hex: "0x123".to_string(), - event_type: EventType::Transfer, - event_id: "evt123".to_string(), - block_number: Some(123), - updated_at: Some(1625101200), - chain_id: "0x534e5f4d41494e".to_string(), - }); - - let serialized = serde_json::to_string(&event).expect("Failed to serialize TokenEvent"); - - let expected_json = json!({ - "block_number": "123", - "event_type": "transfer", - "timestamp": "1625097600", - "from_address": "0xfrom", - "to_address": "0xto", - "contract_address": "0xcontract", - "transaction_hash": "0xhash", - "token_id": "123", - "token_id_hex": "0x123", - "contract_type": "ERC721", - "event_id": "evt123" - }); - - let expected = expected_json.to_string(); - - let serialized_value: Result = serde_json::from_str(&serialized); - if serialized_value.is_err() { - println!("`serialized` is not a valid json"); - return; - } - let serialized_value = serialized_value.unwrap(); - - let expected_value: Result = serde_json::from_str(&expected); - if expected_value.is_err() { - println!("`expected` is not a valid json"); - return; - } - let expected_value = expected_value.unwrap(); - - assert_eq!(serialized_value, expected_value, "json are not equal"); - } -} diff --git a/crates/pontos/src/storage/utils.rs b/crates/pontos/src/storage/utils.rs deleted file mode 100644 index 954b6ea1f..000000000 --- a/crates/pontos/src/storage/utils.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub fn format_token_id(token_id: String) -> String { - format!("{:0>width$}", token_id, width = 78) -} diff --git a/crates/sana/src/managers/block_manager.rs b/crates/sana/src/managers/block_manager.rs index 5a56660d5..323abb8fd 100644 --- a/crates/sana/src/managers/block_manager.rs +++ b/crates/sana/src/managers/block_manager.rs @@ -32,7 +32,7 @@ impl BlockManager { &self, block_number: u64, block_timestamp: u64, - indexer_version: String, + _indexer_version: String, do_force: bool, ) -> Result { if do_force { diff --git a/skip_examples/pontos.rs b/skip_examples/sana.rs similarity index 89% rename from skip_examples/pontos.rs rename to skip_examples/sana.rs index e22bed349..4253f88d0 100644 --- a/skip_examples/pontos.rs +++ b/skip_examples/sana.rs @@ -1,11 +1,11 @@ //! How to start a NFT indexer. //! -//! Can be run with `cargo run --example pontos`. +//! Can be run with `cargo run --example sana`. //! use anyhow::Result; use ark_starknet::client::{StarknetClient, StarknetClientHttp}; -use arkproject::pontos::{ - event_handler::EventHandler, storage::types::*, storage::Storage, Pontos, PontosConfig, +use arkproject::sana::{ + event_handler::EventHandler, storage::types::*, storage::Storage, Sana, SanaConfig, }; use async_trait::async_trait; use starknet::core::types::BlockId; @@ -21,12 +21,12 @@ async fn main() -> Result<()> { ); // Typically loaded from env. - let config = PontosConfig { + let config = SanaConfig { indexer_version: Some(String::from("0.0.1")), indexer_identifier: "task_1234".to_string(), }; - let pontos = Arc::new(Pontos::new( + let sana = Arc::new(Sana::new( Arc::clone(&client), Arc::new(DefaultStorage::new()), Arc::new(DefaultEventHandler::new()), @@ -37,7 +37,7 @@ async fn main() -> Result<()> { let do_force = false; for i in 0..3 { - let indexer = Arc::clone(&pontos); + let indexer = Arc::clone(&sana); let handle = tokio::spawn(async move { let from = BlockId::Number(i * 10_000); let to = BlockId::Number(i * 10_000 + 3); @@ -69,34 +69,34 @@ impl DefaultEventHandler { impl EventHandler for DefaultEventHandler { async fn on_block_processed(&self, block_number: u64, indexation_progress: f64) { println!( - "pontos: block processed: block_number={}, indexation_progress={}", + "sana: block processed: block_number={}, indexation_progress={}", block_number, indexation_progress ); } async fn on_indexation_range_completed(&self) { - println!("pontos: indexation range completed"); + println!("sana: indexation range completed"); } async fn on_new_latest_block(&self, block_number: u64) { - println!("pontos: new latest block {:?}", block_number); + println!("sana: new latest block {:?}", block_number); } async fn on_block_processing(&self, block_timestamp: u64, block_number: Option) { // TODO: here we want to call some storage if needed from an other object. // But it's totally unrelated to the core process, so we can do whatever we want here. println!( - "pontos: processing block: block_timestamp={}, block_number={:?}", + "sana: processing block: block_timestamp={}, block_number={:?}", block_timestamp, block_number ); } async fn on_token_registered(&self, token: TokenInfo) { - println!("pontos: token registered {:?}", token); + println!("sana: token registered {:?}", token); } async fn on_event_registered(&self, event: TokenEvent) { - println!("pontos: event registered {:?}", event); + println!("sana: event registered {:?}", event); } } diff --git a/skip_examples/pontos_pending.rs b/skip_examples/sana_pending.rs similarity index 92% rename from skip_examples/pontos_pending.rs rename to skip_examples/sana_pending.rs index 127762dd0..45f50b32f 100644 --- a/skip_examples/pontos_pending.rs +++ b/skip_examples/sana_pending.rs @@ -1,11 +1,11 @@ -//! How to run pontos on pending block only. +//! How to run sana on pending block only. //! -//! Can be run with `cargo run --example pontos_pending`. +//! Can be run with `cargo run --example sana_pending`. //! use anyhow::Result; use ark_starknet::client::{StarknetClient, StarknetClientHttp}; -use arkproject::pontos::{ - event_handler::EventHandler, storage::types::*, storage::Storage, Pontos, PontosConfig, +use arkproject::sana::{ + event_handler::EventHandler, storage::types::*, storage::Storage, Sana, SanaConfig, }; use async_trait::async_trait; use std::sync::Arc; @@ -20,12 +20,12 @@ async fn main() -> Result<()> { ); // Typically loaded from env. - let config = PontosConfig { + let config = SanaConfig { indexer_version: Some(String::from("0.0.1")), indexer_identifier: "TASK#123".to_string(), }; - let pontos = Arc::new(Pontos::new( + let sana = Arc::new(Sana::new( Arc::clone(&client), Arc::new(DefaultStorage::new()), Arc::new(DefaultEventHandler::new()), @@ -33,7 +33,7 @@ async fn main() -> Result<()> { )); let task = tokio::spawn(async move { - if let Err(err) = Arc::clone(&pontos).index_pending().await { + if let Err(err) = Arc::clone(&sana).index_pending().await { log::error!("Error in the spawned task: {:?}", err); } else { log::info!("End task"); diff --git a/skip_examples/pontos_sqlx.rs b/skip_examples/sana_sqlx.rs similarity index 76% rename from skip_examples/pontos_sqlx.rs rename to skip_examples/sana_sqlx.rs index 2ec1d4e43..df22113ce 100644 --- a/skip_examples/pontos_sqlx.rs +++ b/skip_examples/sana_sqlx.rs @@ -1,12 +1,11 @@ //! How to start a NFT indexer. //! -//! Can be run with `cargo run --example pontos`. +//! Can be run with `cargo run --example sana_sqlx`. //! use anyhow::Result; use ark_starknet::client::{StarknetClient, StarknetClientHttp}; -use arkproject::pontos::{ - event_handler::EventHandler, storage::types::*, storage::DefaultSqlxStorage, Pontos, - PontosConfig, +use arkproject::sana::{ + event_handler::EventHandler, storage::types::*, storage::DefaultSqlxStorage, Sana, SanaConfig, }; use async_trait::async_trait; use starknet::core::types::BlockId; @@ -37,18 +36,18 @@ async fn main() -> Result<()> { ); // Typically loaded from env. - let config = PontosConfig { + let config = SanaConfig { indexer_version: Some(String::from("0.0.1")), indexer_identifier: "task_1234".to_string(), }; let storage = Arc::new(DefaultSqlxStorage::new_any("sqlite::memory:").await?); - sqlx::migrate!("./crates/pontos/src/storage/sqlx/migrations") + sqlx::migrate!("./crates/sana/src/storage/sqlx/migrations") .run(storage.get_pool_ref()) .await?; - let pontos = Arc::new(Pontos::new( + let sana = Arc::new(Sana::new( Arc::clone(&client), Arc::clone(&storage), Arc::new(DefaultEventHandler::new()), @@ -60,12 +59,12 @@ async fn main() -> Result<()> { let do_force = false; println!("Indexer [{:?} - {:?}] started!", from, to); - match pontos.index_block_range(from, to, do_force).await { + match sana.index_block_range(from, to, do_force).await { Ok(_) => { storage.dump_tables().await.unwrap(); - println!("Pontos task completed!"); + println!("Sana task completed!"); } - Err(e) => println!("Pontos task failed! [{:?}]", e), + Err(e) => println!("Sana task failed! [{:?}]", e), } Ok(()) @@ -84,7 +83,7 @@ impl DefaultEventHandler { impl EventHandler for DefaultEventHandler { async fn on_block_processed(&self, block_number: u64, indexation_progress: f64) { println!( - "pontos: block processed: block_number={}, indexation_progress={}", + "sana: block processed: block_number={}, indexation_progress={}", block_number, indexation_progress ); } @@ -93,24 +92,24 @@ impl EventHandler for DefaultEventHandler { // TODO: here we want to call some storage if needed from an other object. // But it's totally unrelated to the core process, so we can do whatever we want here. println!( - "pontos: processing block: block_timestamp={}, block_number={:?}", + "sana: processing block: block_timestamp={}, block_number={:?}", block_timestamp, block_number ); } async fn on_indexation_range_completed(&self) { - println!("pontos: indexation range completed"); + println!("sana: indexation range completed"); } async fn on_token_registered(&self, token: TokenInfo) { - println!("pontos: token registered {:?}", token); + println!("sana: token registered {:?}", token); } async fn on_event_registered(&self, event: TokenEvent) { - println!("pontos: event registered {:?}", event); + println!("sana: event registered {:?}", event); } async fn on_new_latest_block(&self, block_number: u64) { - println!("pontos: new latest block {:?}", block_number); + println!("sana: new latest block {:?}", block_number); } } diff --git a/src/lib.rs b/src/lib.rs index 902b6ac87..24f7c35f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,6 @@ pub mod metadata { pub use ark_metadata::*; } -pub mod pontos { - pub use pontos::*; -} - pub mod diri { pub use diri::*; } From 9abc1183356aa13269bfe9e0fe066c35c5884fc1 Mon Sep 17 00:00:00 2001 From: remiroyc Date: Tue, 17 Dec 2024 16:34:16 +0100 Subject: [PATCH 2/2] fix: cargo clippy --- crates/diri/src/lib.rs | 1 - crates/sana/src/lib.rs | 1 - crates/sana/src/storage/sqlx/default_storage.rs | 8 ++++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/diri/src/lib.rs b/crates/diri/src/lib.rs index c3d4ff509..ecd651d78 100644 --- a/crates/diri/src/lib.rs +++ b/crates/diri/src/lib.rs @@ -40,7 +40,6 @@ pub struct Diri { } impl Diri { - /// pub fn new(provider: Arc, storage: Arc, event_handler: Arc) -> Self { Diri { provider: Arc::clone(&provider), diff --git a/crates/sana/src/lib.rs b/crates/sana/src/lib.rs index 11c2bf6df..647f21c28 100644 --- a/crates/sana/src/lib.rs +++ b/crates/sana/src/lib.rs @@ -79,7 +79,6 @@ pub struct Sana { } impl Sana { - /// pub fn new(client: Arc, storage: Arc, event_handler: Arc, config: SanaConfig) -> Self { Sana { config, diff --git a/crates/sana/src/storage/sqlx/default_storage.rs b/crates/sana/src/storage/sqlx/default_storage.rs index c0f92ecfd..c817f539d 100644 --- a/crates/sana/src/storage/sqlx/default_storage.rs +++ b/crates/sana/src/storage/sqlx/default_storage.rs @@ -314,10 +314,10 @@ impl Storage for PostgresStorage { let insert_query = "INSERT INTO token_event (token_event_id, contract_address, chain_id, token_id, token_id_hex, event_type, block_timestamp, transaction_hash, to_address, from_address) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (token_event_id) DO NOTHING"; - let event_type = event.event_type.as_ref().map(|e| { - let res = self.to_title_case(&e.to_string().to_lowercase()); - res - }); + let event_type = event + .event_type + .as_ref() + .map(|e| self.to_title_case(&e.to_string().to_lowercase())); info!("Inserting transfer event... {:?}", event_type);