diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ea5a14368..e7738a5379 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ env: jobs: build: runs-on: ubuntu-latest-4-cores + needs: [fmt, cairofmt] steps: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 @@ -55,6 +56,7 @@ jobs: ensure-wasm: runs-on: ubuntu-latest + needs: [fmt, cairofmt] container: image: ghcr.io/dojoengine/dojo-dev:v1.0.9 steps: @@ -153,6 +155,7 @@ jobs: clippy: runs-on: ubuntu-latest-4-cores + needs: [fmt, cairofmt] container: image: ghcr.io/dojoengine/dojo-dev:v1.0.9 steps: @@ -171,6 +174,7 @@ jobs: docs: runs-on: ubuntu-latest + needs: [fmt, cairofmt] container: image: ghcr.io/dojoengine/dojo-dev:v1.0.9 steps: diff --git a/Cargo.lock b/Cargo.lock index 91e54f006c..5f3a4ad91c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2001,7 +2001,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.11.0", "lazy_static", "lazycell", "log", @@ -2497,16 +2497,16 @@ dependencies = [ [[package]] name = "cainome" -version = "0.4.8" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.10#6fefd8bf4370c77d8834d18a2ae3c59d3a5e8dd5" +version = "0.4.11" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" dependencies = [ "anyhow", "async-trait", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", - "cainome-cairo-serde-derive 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", - "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", - "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", + "cainome-cairo-serde-derive 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", + "cainome-rs-macro 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", "camino", "clap", "clap_complete", @@ -2524,7 +2524,7 @@ dependencies = [ [[package]] name = "cainome-cairo-serde" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.10#6fefd8bf4370c77d8834d18a2ae3c59d3a5e8dd5" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" dependencies = [ "num-bigint", "serde", @@ -2556,7 +2556,7 @@ dependencies = [ [[package]] name = "cainome-cairo-serde-derive" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.10#6fefd8bf4370c77d8834d18a2ae3c59d3a5e8dd5" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" dependencies = [ "proc-macro2", "quote", @@ -2578,7 +2578,7 @@ dependencies = [ [[package]] name = "cainome-parser" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.10#6fefd8bf4370c77d8834d18a2ae3c59d3a5e8dd5" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" dependencies = [ "convert_case 0.6.0", "quote", @@ -2617,11 +2617,11 @@ dependencies = [ [[package]] name = "cainome-rs" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.10#6fefd8bf4370c77d8834d18a2ae3c59d3a5e8dd5" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" dependencies = [ "anyhow", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", "camino", "prettyplease", "proc-macro2", @@ -2671,12 +2671,12 @@ dependencies = [ [[package]] name = "cainome-rs-macro" version = "0.1.0" -source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.10#6fefd8bf4370c77d8834d18a2ae3c59d3a5e8dd5" +source = "git+https://github.com/cartridge-gg/cainome?tag=v0.4.11#355b88b7b808656d729e9dfd16f81d80c5c30fbf" dependencies = [ "anyhow", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", - "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", - "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", + "cainome-parser 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", + "cainome-rs 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", "proc-macro-error", "proc-macro2", "quote", @@ -3797,7 +3797,6 @@ dependencies = [ "anstyle", "clap_lex", "strsim 0.11.1", - "terminal_size", ] [[package]] @@ -3809,20 +3808,6 @@ dependencies = [ "clap", ] -[[package]] -name = "clap_config" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46efb9cbf691f5505d0b7b2c8055aec0c9a770eaac8a06834b6d84b5be93279a" -dependencies = [ - "clap", - "heck 0.5.0", - "proc-macro2", - "quote", - "serde", - "syn 2.0.90", -] - [[package]] name = "clap_derive" version = "4.5.18" @@ -4853,12 +4838,12 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dojo-bindgen" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "assert_matches", "async-trait", - "cainome 0.4.8", + "cainome 0.4.11", "camino", "chrono", "convert_case 0.6.0", @@ -4876,11 +4861,11 @@ dependencies = [ [[package]] name = "dojo-contracts" -version = "1.0.9" +version = "1.0.10" [[package]] name = "dojo-examples-spawn-and-move" -version = "1.0.9" +version = "1.0.10" [[package]] name = "dojo-lang" @@ -4922,7 +4907,7 @@ dependencies = [ [[package]] name = "dojo-lang" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "cairo-lang-defs", @@ -4932,7 +4917,7 @@ dependencies = [ "cairo-lang-semantic", "cairo-lang-syntax", "cairo-lang-utils", - "dojo-types 1.0.9", + "dojo-types 1.0.10", "itertools 0.12.1", "regex", "serde", @@ -4944,16 +4929,16 @@ dependencies = [ [[package]] name = "dojo-language-server" -version = "1.0.9" +version = "1.0.10" dependencies = [ "cairo-lang-language-server", "clap", - "dojo-lang 1.0.9", + "dojo-lang 1.0.10", ] [[package]] name = "dojo-metrics" -version = "1.0.9" +version = "1.0.10" dependencies = [ "hyper 0.14.30", "jemalloc-ctl", @@ -4969,7 +4954,7 @@ dependencies = [ [[package]] name = "dojo-test-utils" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "assert_fs", @@ -5013,10 +4998,10 @@ dependencies = [ [[package]] name = "dojo-types" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", - "cainome 0.4.8", + "cainome 0.4.11", "crypto-bigint", "hex", "indexmap 2.5.0", @@ -5034,7 +5019,7 @@ dependencies = [ [[package]] name = "dojo-utils" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "assert_matches", @@ -5052,13 +5037,13 @@ dependencies = [ [[package]] name = "dojo-world" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "async-trait", - "cainome 0.4.8", + "cainome 0.4.11", "cairo-lang-starknet-classes", - "dojo-types 1.0.9", + "dojo-types 1.0.10", "futures", "hex", "hex-literal", @@ -5079,10 +5064,10 @@ dependencies = [ [[package]] name = "dojo-world-abigen" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", - "cainome 0.4.8", + "cainome 0.4.11", "cairo-lang-starknet", "cairo-lang-starknet-classes", "camino", @@ -8439,12 +8424,12 @@ dependencies = [ [[package]] name = "katana" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "assert_matches", "byte-unit", - "cainome 0.4.8", + "cainome 0.4.11", "clap", "clap_complete", "comfy-table", @@ -8467,7 +8452,7 @@ dependencies = [ [[package]] name = "katana-cairo" -version = "1.0.9" +version = "1.0.10" dependencies = [ "cairo-lang-casm", "cairo-lang-runner", @@ -8482,12 +8467,12 @@ dependencies = [ [[package]] name = "katana-cli" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-primitives", "anyhow", "assert_matches", - "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.10)", + "cainome-cairo-serde 0.1.0 (git+https://github.com/cartridge-gg/cainome?tag=v0.4.11)", "clap", "console", "dojo-utils", @@ -8510,7 +8495,7 @@ dependencies = [ [[package]] name = "katana-codecs" -version = "1.0.9" +version = "1.0.10" dependencies = [ "bytes", "katana-primitives", @@ -8518,7 +8503,7 @@ dependencies = [ [[package]] name = "katana-codecs-derive" -version = "1.0.9" +version = "1.0.10" dependencies = [ "proc-macro2", "quote", @@ -8528,7 +8513,7 @@ dependencies = [ [[package]] name = "katana-core" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-contract 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "alloy-network 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -8559,6 +8544,7 @@ dependencies = [ "serde", "serde_json", "starknet 0.12.0", + "starknet-crypto 0.7.2", "starknet-types-core", "tempfile", "thiserror 1.0.63", @@ -8569,7 +8555,7 @@ dependencies = [ [[package]] name = "katana-db" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "arbitrary", @@ -8596,7 +8582,7 @@ dependencies = [ [[package]] name = "katana-executor" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-primitives", "anyhow", @@ -8624,7 +8610,7 @@ dependencies = [ [[package]] name = "katana-feeder-gateway" -version = "1.0.9" +version = "1.0.10" dependencies = [ "katana-primitives", "katana-rpc-types", @@ -8639,7 +8625,7 @@ dependencies = [ [[package]] name = "katana-grpc" -version = "1.0.9" +version = "1.0.10" dependencies = [ "tonic 0.11.0", "tonic-build 0.11.0", @@ -8648,7 +8634,7 @@ dependencies = [ [[package]] name = "katana-node" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "clap", @@ -8689,7 +8675,7 @@ dependencies = [ [[package]] name = "katana-node-bindings" -version = "1.0.9" +version = "1.0.10" dependencies = [ "regex", "serde", @@ -8704,7 +8690,7 @@ dependencies = [ [[package]] name = "katana-pipeline" -version = "1.0.9" +version = "1.0.10" dependencies = [ "async-trait", "futures", @@ -8718,7 +8704,7 @@ dependencies = [ [[package]] name = "katana-pool" -version = "1.0.9" +version = "1.0.10" dependencies = [ "futures", "futures-util", @@ -8734,7 +8720,7 @@ dependencies = [ [[package]] name = "katana-primitives" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-primitives", "anyhow", @@ -8765,7 +8751,7 @@ dependencies = [ [[package]] name = "katana-provider" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-primitives", "anyhow", @@ -8793,13 +8779,13 @@ dependencies = [ [[package]] name = "katana-rpc" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy", "alloy-primitives", "anyhow", "assert_matches", - "cainome 0.4.8", + "cainome 0.4.11", "dojo-metrics", "dojo-test-utils", "dojo-utils", @@ -8838,7 +8824,7 @@ dependencies = [ [[package]] name = "katana-rpc-api" -version = "1.0.9" +version = "1.0.10" dependencies = [ "jsonrpsee 0.16.3", "katana-core", @@ -8849,7 +8835,7 @@ dependencies = [ [[package]] name = "katana-rpc-types" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-primitives", "anyhow", @@ -8877,7 +8863,7 @@ dependencies = [ [[package]] name = "katana-rpc-types-builder" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "katana-executor", @@ -8889,7 +8875,7 @@ dependencies = [ [[package]] name = "katana-runner" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "assert_fs", @@ -8902,7 +8888,7 @@ dependencies = [ [[package]] name = "katana-runner-macro" -version = "1.0.9" +version = "1.0.10" dependencies = [ "proc-macro2", "quote", @@ -8911,7 +8897,7 @@ dependencies = [ [[package]] name = "katana-slot-controller" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-primitives", "anyhow", @@ -8929,7 +8915,7 @@ dependencies = [ [[package]] name = "katana-stage" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "async-trait", @@ -8952,7 +8938,7 @@ dependencies = [ [[package]] name = "katana-tasks" -version = "1.0.9" +version = "1.0.10" dependencies = [ "futures", "rayon", @@ -8965,7 +8951,7 @@ dependencies = [ [[package]] name = "katana-trie" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "bitvec", @@ -11610,8 +11596,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.5.0", - "itertools 0.12.1", + "heck 0.4.1", + "itertools 0.11.0", "log", "multimap", "once_cell", @@ -11631,7 +11617,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", - "heck 0.5.0", + "heck 0.4.1", "itertools 0.13.0", "log", "multimap", @@ -11652,7 +11638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.90", @@ -12970,7 +12956,7 @@ dependencies = [ [[package]] name = "saya" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "clap", @@ -12988,7 +12974,7 @@ dependencies = [ [[package]] name = "saya-core" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "async-trait", @@ -13021,7 +13007,7 @@ dependencies = [ [[package]] name = "saya-provider" -version = "1.0.9" +version = "1.0.10" dependencies = [ "alloy-primitives", "anyhow", @@ -13211,7 +13197,7 @@ dependencies = [ [[package]] name = "scheduler" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "clap", @@ -13960,11 +13946,11 @@ dependencies = [ [[package]] name = "sozo" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "async-trait", - "cainome 0.4.8", + "cainome 0.4.11", "cairo-lang-compiler", "cairo-lang-filesystem", "cairo-lang-project", @@ -13977,9 +13963,9 @@ dependencies = [ "clap-verbosity-flag", "colored", "dojo-bindgen", - "dojo-lang 1.0.9", + "dojo-lang 1.0.10", "dojo-test-utils", - "dojo-types 1.0.9", + "dojo-types 1.0.10", "dojo-utils", "dojo-world", "itertools 0.12.1", @@ -14011,16 +13997,16 @@ dependencies = [ [[package]] name = "sozo-ops" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "assert_fs", "async-trait", - "cainome 0.4.8", + "cainome 0.4.11", "colored", "colored_json", "dojo-test-utils", - "dojo-types 1.0.9", + "dojo-types 1.0.10", "dojo-utils", "dojo-world", "futures", @@ -14043,7 +14029,7 @@ dependencies = [ [[package]] name = "sozo-scarbext" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "camino", @@ -14056,7 +14042,7 @@ dependencies = [ [[package]] name = "sozo-signers" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "starknet 0.12.0", @@ -14064,17 +14050,17 @@ dependencies = [ [[package]] name = "sozo-walnut" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", + "clap", "console", - "dojo-world", + "dojo-utils", "reqwest 0.11.27", "scarb", "scarb-ui", "serde", "serde_json", - "sozo-scarbext", "starknet 0.12.0", "thiserror 1.0.63", "url", @@ -15135,16 +15121,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "terminal_size" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix 0.38.37", - "windows-sys 0.48.0", -] - [[package]] name = "termtree" version = "0.4.1" @@ -15649,57 +15625,18 @@ dependencies = [ [[package]] name = "torii" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", - "assert_matches", - "async-trait", - "base64 0.21.7", - "camino", - "chrono", "clap", - "clap_complete", - "clap_config", - "ctrlc", - "dojo-metrics", - "dojo-types 1.0.9", - "dojo-utils", - "dojo-world", - "either", - "futures", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "hyper-reverse-proxy", - "indexmap 2.5.0", - "lazy_static", - "serde", - "serde_json", - "sqlx", - "starknet 0.12.0", - "starknet-crypto 0.7.2", - "tempfile", "tokio", - "tokio-stream", - "tokio-util", - "toml 0.8.19", "torii-cli", - "torii-core", - "torii-graphql", - "torii-grpc", - "torii-relay", - "torii-server", - "tower 0.4.13", - "tower-http 0.4.4", - "tracing", - "tracing-subscriber", - "url", - "webbrowser", + "torii-runner", ] [[package]] name = "torii-cli" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "assert_matches", @@ -15709,18 +15646,18 @@ dependencies = [ "serde", "starknet 0.12.0", "toml 0.8.19", - "torii-core", + "torii-sqlite", "url", ] [[package]] name = "torii-client" -version = "1.0.9" +version = "1.0.10" dependencies = [ "async-trait", "camino", "crypto-bigint", - "dojo-types 1.0.9", + "dojo-types 1.0.10", "dojo-world", "futures", "futures-util", @@ -15741,48 +15678,9 @@ dependencies = [ "url", ] -[[package]] -name = "torii-core" -version = "1.0.9" -dependencies = [ - "anyhow", - "async-trait", - "base64 0.21.7", - "bitflags 2.6.0", - "cainome 0.4.8", - "chrono", - "crypto-bigint", - "data-url", - "dojo-test-utils", - "dojo-types 1.0.9", - "dojo-utils", - "dojo-world", - "futures-channel", - "futures-util", - "hashlink", - "ipfs-api-backend-hyper", - "katana-runner", - "num-traits 0.2.19", - "once_cell", - "reqwest 0.11.27", - "scarb", - "serde", - "serde_json", - "slab", - "sozo-scarbext", - "sqlx", - "starknet 0.12.0", - "starknet-crypto 0.7.2", - "tempfile", - "thiserror 1.0.63", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "torii-graphql" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "async-graphql", @@ -15793,7 +15691,7 @@ dependencies = [ "chrono", "convert_case 0.6.0", "dojo-test-utils", - "dojo-types 1.0.9", + "dojo-types 1.0.10", "dojo-utils", "dojo-world", "katana-runner", @@ -15814,7 +15712,8 @@ dependencies = [ "tokio", "tokio-stream", "toml 0.8.19", - "torii-core", + "torii-indexer", + "torii-sqlite", "tracing", "url", "warp", @@ -15822,13 +15721,13 @@ dependencies = [ [[package]] name = "torii-grpc" -version = "1.0.9" +version = "1.0.10" dependencies = [ - "cainome 0.4.8", + "cainome 0.4.11", "camino", "crypto-bigint", "dojo-test-utils", - "dojo-types 1.0.9", + "dojo-types 1.0.10", "dojo-utils", "dojo-world", "futures", @@ -15861,21 +15760,60 @@ dependencies = [ "tonic-reflection", "tonic-web", "tonic-web-wasm-client", - "torii-core", + "torii-indexer", + "torii-sqlite", "tower 0.4.13", "tower-http 0.4.4", "tracing", ] [[package]] -name = "torii-relay" -version = "1.0.9" +name = "torii-indexer" +version = "1.0.10" dependencies = [ "anyhow", - "cainome 0.4.8", + "async-trait", + "base64 0.21.7", + "bitflags 2.6.0", + "cainome 0.4.11", "chrono", "crypto-bigint", - "dojo-types 1.0.9", + "data-url", + "dojo-test-utils", + "dojo-types 1.0.10", + "dojo-utils", + "dojo-world", + "futures-channel", + "futures-util", + "hashlink", + "ipfs-api-backend-hyper", + "katana-runner", + "num-traits 0.2.19", + "once_cell", + "reqwest 0.11.27", + "scarb", + "serde", + "serde_json", + "slab", + "sozo-scarbext", + "sqlx", + "starknet 0.12.0", + "starknet-crypto 0.7.2", + "tempfile", + "thiserror 1.0.63", + "tokio", + "tokio-util", + "torii-sqlite", + "tracing", +] + +[[package]] +name = "torii-relay" +version = "1.0.10" +dependencies = [ + "anyhow", + "chrono", + "dojo-types 1.0.10", "dojo-world", "futures", "indexmap 2.5.0", @@ -15893,7 +15831,8 @@ dependencies = [ "tempfile", "thiserror 1.0.63", "tokio", - "torii-core", + "torii-sqlite", + "torii-typed-data", "tracing", "tracing-subscriber", "tracing-wasm", @@ -15902,9 +15841,57 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "torii-runner" +version = "1.0.10" +dependencies = [ + "anyhow", + "assert_matches", + "async-trait", + "base64 0.21.7", + "camino", + "chrono", + "ctrlc", + "dojo-metrics", + "dojo-types 1.0.10", + "dojo-utils", + "dojo-world", + "either", + "futures", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-reverse-proxy", + "indexmap 2.5.0", + "lazy_static", + "serde", + "serde_json", + "sqlx", + "starknet 0.12.0", + "starknet-crypto 0.7.2", + "tempfile", + "tokio", + "tokio-stream", + "tokio-util", + "toml 0.8.19", + "torii-cli", + "torii-graphql", + "torii-grpc", + "torii-indexer", + "torii-relay", + "torii-server", + "torii-sqlite", + "tower 0.4.13", + "tower-http 0.4.4", + "tracing", + "tracing-subscriber", + "url", + "webbrowser", +] + [[package]] name = "torii-server" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "async-trait", @@ -15929,13 +15916,67 @@ dependencies = [ "tokio", "tokio-tungstenite 0.20.1", "tokio-util", - "torii-core", + "torii-sqlite", "tower 0.4.13", "tower-http 0.4.4", "tracing", "warp", ] +[[package]] +name = "torii-sqlite" +version = "1.0.10" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.21.7", + "bitflags 2.6.0", + "cainome 0.4.11", + "chrono", + "crypto-bigint", + "data-url", + "dojo-test-utils", + "dojo-types 1.0.10", + "dojo-utils", + "dojo-world", + "futures-channel", + "futures-util", + "hashlink", + "ipfs-api-backend-hyper", + "katana-runner", + "once_cell", + "reqwest 0.11.27", + "scarb", + "serde", + "serde_json", + "slab", + "sozo-scarbext", + "sqlx", + "starknet 0.12.0", + "starknet-crypto 0.7.2", + "tempfile", + "thiserror 1.0.63", + "tokio", + "tokio-util", + "torii-indexer", + "tracing", +] + +[[package]] +name = "torii-typed-data" +version = "1.0.10" +dependencies = [ + "cainome 0.4.11", + "crypto-bigint", + "dojo-types 1.0.10", + "indexmap 2.5.0", + "serde", + "serde_json", + "starknet 0.12.0", + "starknet-crypto 0.7.2", + "thiserror 1.0.63", +] + [[package]] name = "tower" version = "0.4.13" @@ -16307,7 +16348,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "types-test" -version = "1.0.9" +version = "1.0.10" [[package]] name = "u256-literal" @@ -16609,7 +16650,7 @@ dependencies = [ [[package]] name = "verify_db_balances" -version = "1.0.9" +version = "1.0.10" dependencies = [ "clap", "num-traits 0.2.19", @@ -17685,7 +17726,7 @@ checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" [[package]] name = "xtask-generate-test-db" -version = "1.0.9" +version = "1.0.10" dependencies = [ "anyhow", "dojo-test-utils", diff --git a/Cargo.toml b/Cargo.toml index b6c0ae31f0..6e7869b9fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ edition = "2021" license = "Apache-2.0" license-file = "LICENSE" repository = "https://github.com/dojoengine/dojo/" -version = "1.0.9" +version = "1.0.10" [profile.performance] codegen-units = 1 @@ -73,8 +73,8 @@ debug = true inherits = "release" [workspace.dependencies] -cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.4.10", features = [ "abigen-rs" ] } -cainome-cairo-serde = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.4.10" } +cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.4.11", features = [ "abigen-rs" ] } +cainome-cairo-serde = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.4.11" } dojo-utils = { path = "crates/dojo/utils" } # metrics @@ -119,11 +119,14 @@ katana-trie = { path = "crates/katana/trie" } # torii torii-cli = { path = "crates/torii/cli" } torii-client = { path = "crates/torii/client" } -torii-core = { path = "crates/torii/core" } +torii-indexer = { path = "crates/torii/indexer" } +torii-sqlite = { path = "crates/torii/sqlite" } torii-graphql = { path = "crates/torii/graphql" } torii-grpc = { path = "crates/torii/grpc" } torii-relay = { path = "crates/torii/libp2p" } torii-server = { path = "crates/torii/server" } +torii-runner = { path = "crates/torii/runner" } +torii-typed-data = { path = "crates/torii/typed-data" } # saya saya-core = { path = "crates/saya/core" } diff --git a/README.md b/README.md index 8ebe1c5cf5..89c4e5d5f6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ ![Dojo Feature Matrix](.github/feature_matrix.png) -# Dojo: Provable Games and Applications [![discord](https://img.shields.io/badge/join-dojo-green?logo=discord&logoColor=white)](https://discord.com/invite/dojoengine) [![Telegram Chat][tg-badge]][tg-url] ![Github Actions][gha-badge] +# Dojo: Provable Games and Applications [![discord](https://img.shields.io/badge/join-dojo-green?logo=discord&logoColor=white)](https://discord.com/invite/dojoengine) [![Telegram Chat][tg-badge]][tg-url] [![Github Actions][gha-badge]][gha-url] [gha-badge]: https://img.shields.io/github/actions/workflow/status/dojoengine/dojo/ci.yml?branch=main +[gha-url]: https://github.com/dojoengine/dojo/actions/workflows/ci.yml?query=branch%3Amain [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fdojoengine [tg-url]: https://t.me/dojoengine diff --git a/bin/saya/README.md b/bin/saya/README.md index c7c1e51987..050f77c141 100644 --- a/bin/saya/README.md +++ b/bin/saya/README.md @@ -37,7 +37,7 @@ cargo run --bin saya -- \ export DOJO_ACCOUNT_ADDRESS="" export DOJO_PRIVATE_KEY="" ``` - * Setup variables in or use enviroment variables ```bin/saya/scripts/0_account_setup.sh```, and run script + * Setup variables in or use environment variables ```bin/saya/scripts/0_account_setup.sh```, and run script * `sncast` doesn't support environment variables, for now, so you may have to set the options manually. **During this tutorial, we will export environment variables, so you must remain in the same shell session**. @@ -166,7 +166,7 @@ cargo run --bin saya -- \ If not (this includes Apple Silicon), some emulation will take place to run the prover on your machine, and this is very very slow. It's important that the `--start-block` of Saya is the first block produced by Katana as for now Katana is not fetching events from the forked network. To get this value, you can add one to the `SAYA_FORK_BLOCK_NUMBER` value. - **Currently saya supports only persistant mode, ephermal will be implemented in future + **Currently saya supports only persistent mode, ephermal will be implemented in future ```bash cargo run -r --bin saya -- \ diff --git a/bin/saya/saya.sh b/bin/saya/saya.sh index b94da4fd3f..0fba3c7831 100755 --- a/bin/saya/saya.sh +++ b/bin/saya/saya.sh @@ -23,7 +23,7 @@ SAYA_PILTOVER_STARTING_STATE_ROOT=0 SAYA_CONFIG_HASH=42 SAYA_PROGRAM_HASH=0x2aa9e430c145b26d681a8087819ed5bff93f5596105d0e74f00fc7caa46fa18 #need to be reupdated -# Set after runnig the script +# Set after running the script SAYA_WORLD_ADDRESS="" SAYA_WORLD_PREPARED="" # Set to anything after preparing the world successfully for the first time diff --git a/bin/sozo/src/commands/execute.rs b/bin/sozo/src/commands/execute.rs index c8510d1d16..fe5e37f62c 100644 --- a/bin/sozo/src/commands/execute.rs +++ b/bin/sozo/src/commands/execute.rs @@ -5,6 +5,7 @@ use dojo_world::config::calldata_decoder; use scarb::core::Config; use sozo_ops::resource_descriptor::ResourceDescriptor; use sozo_scarbext::WorkspaceExt; +#[cfg(feature = "walnut")] use sozo_walnut::WalnutDebugger; use starknet::core::types::Call; use starknet::core::utils as snutils; @@ -67,7 +68,7 @@ impl ExecuteArgs { let descriptor = self.tag_or_address.ensure_namespace(&profile_config.namespace.default); #[cfg(feature = "walnut")] - let _walnut_debugger = WalnutDebugger::new_from_flag( + let walnut_debugger = WalnutDebugger::new_from_flag( self.transaction.walnut, self.starknet.url(profile_config.env.as_ref())?, ); @@ -132,9 +133,13 @@ impl ExecuteArgs { .await?; let invoker = Invoker::new(&account, txn_config); - // TODO: add walnut back, perhaps at the invoker level. let tx_result = invoker.invoke(call).await?; + #[cfg(feature = "walnut")] + if let Some(walnut_debugger) = walnut_debugger { + walnut_debugger.debug_transaction(&config.ui(), &tx_result)?; + } + println!("{}", tx_result); Ok(()) }) diff --git a/bin/sozo/src/commands/mod.rs b/bin/sozo/src/commands/mod.rs index 9ca42277f3..0170c6bc30 100644 --- a/bin/sozo/src/commands/mod.rs +++ b/bin/sozo/src/commands/mod.rs @@ -32,6 +32,8 @@ use init::InitArgs; use inspect::InspectArgs; use migrate::MigrateArgs; use model::ModelArgs; +#[cfg(feature = "walnut")] +use sozo_walnut::walnut::WalnutArgs; use test::TestArgs; pub(crate) const LOG_TARGET: &str = "sozo::cli"; @@ -65,6 +67,9 @@ pub enum Commands { Model(Box), #[command(about = "Inspect events emitted by the world")] Events(Box), + #[cfg(feature = "walnut")] + #[command(about = "Interact with walnut.dev - transactions debugger and simulator")] + Walnut(Box), } impl fmt::Display for Commands { @@ -83,6 +88,8 @@ impl fmt::Display for Commands { Commands::Init(_) => write!(f, "Init"), Commands::Model(_) => write!(f, "Model"), Commands::Events(_) => write!(f, "Events"), + #[cfg(feature = "walnut")] + Commands::Walnut(_) => write!(f, "WalnutVerify"), } } } @@ -109,6 +116,8 @@ pub fn run(command: Commands, config: &Config) -> Result<()> { Commands::Init(args) => args.run(config), Commands::Model(args) => args.run(config), Commands::Events(args) => args.run(config), + #[cfg(feature = "walnut")] + Commands::Walnut(args) => args.run(config), } } diff --git a/bin/sozo/tests/test_data/policies.json b/bin/sozo/tests/test_data/policies.json index 26ec67d783..2094e3d30f 100644 --- a/bin/sozo/tests/test_data/policies.json +++ b/bin/sozo/tests/test_data/policies.json @@ -1,48 +1,4 @@ [ - { - "target": "0x72a9f501c260b2d13f8988ea172680c5c1fdc085c5b44bdcac8477362ed5290", - "method": "upgrade" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "spawn" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "move" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "set_player_config" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "reset_player_config" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "set_player_server_profile" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "set_models" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "enter_dungeon" - }, - { - "target": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "method": "upgrade" - }, - { - "target": "0x50b1497d463d52cbeb5919a35a82360ea6702db2b9c62c2d69c167995f34c08", - "method": "upgrade" - }, - { - "target": "0x4b41a2abaeff170f3a04acb0144790a5a812e25e7a735dfef959247cfeb527", - "method": "upgrade" - }, { "target": "0x52ee4d3cba58d1a0462bbfb6813bf5aa1b35078c3b859cded2b727c1d9469ea", "method": "uuid" @@ -127,6 +83,54 @@ "target": "0x52ee4d3cba58d1a0462bbfb6813bf5aa1b35078c3b859cded2b727c1d9469ea", "method": "upgrade" }, + { + "target": "0x50b1497d463d52cbeb5919a35a82360ea6702db2b9c62c2d69c167995f34c08", + "method": "upgrade" + }, + { + "target": "0x72a9f501c260b2d13f8988ea172680c5c1fdc085c5b44bdcac8477362ed5290", + "method": "upgrade" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "spawn" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "move" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "set_player_config" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "update_player_config_name" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "reset_player_config" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "set_player_server_profile" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "set_models" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "enter_dungeon" + }, + { + "target": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "method": "upgrade" + }, + { + "target": "0x4b41a2abaeff170f3a04acb0144790a5a812e25e7a735dfef959247cfeb527", + "method": "upgrade" + }, { "target": "0x2af9427c5a277474c079a1283c880ee8a6f0f8fbf73ce969c08d88befec1bba", "method": "__declare_transaction__" diff --git a/bin/torii/Cargo.toml b/bin/torii/Cargo.toml index efa4339e0a..fb200c89a5 100644 --- a/bin/torii/Cargo.toml +++ b/bin/torii/Cargo.toml @@ -3,59 +3,9 @@ edition.workspace = true name = "torii" version.workspace = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +torii-cli.workspace = true +torii-runner.workspace = true +tokio.workspace = true anyhow.workspace = true -async-trait.workspace = true -base64.workspace = true -camino.workspace = true -chrono.workspace = true clap.workspace = true -clap_complete.workspace = true -ctrlc = { version = "3.4", features = [ "termination" ] } -dojo-metrics.workspace = true -dojo-types.workspace = true -dojo-utils.workspace = true -dojo-world.workspace = true -either = "1.9.0" -futures.workspace = true -http-body = "0.4.5" -http.workspace = true -hyper-reverse-proxy = { git = "https://github.com/tarrencev/hyper-reverse-proxy" } -hyper.workspace = true -indexmap.workspace = true -lazy_static.workspace = true -serde.workspace = true -serde_json.workspace = true -sqlx.workspace = true -starknet-crypto.workspace = true -starknet.workspace = true -tokio-stream = "0.1.11" -tokio-util = "0.7.7" -tokio.workspace = true -toml.workspace = true -torii-cli.workspace = true -torii-core.workspace = true -torii-graphql.workspace = true -torii-grpc = { workspace = true, features = [ "server" ] } -torii-relay.workspace = true -torii-server.workspace = true -tower.workspace = true - -clap_config = "0.1.1" -tempfile.workspace = true -tower-http.workspace = true -tracing-subscriber.workspace = true -tracing.workspace = true -url.workspace = true -webbrowser = "0.8" - -[dev-dependencies] -assert_matches.workspace = true -camino.workspace = true - -[features] -default = [ "jemalloc", "sqlite" ] -jemalloc = [ "dojo-metrics/jemalloc" ] -sqlite = [ "sqlx/sqlite" ] diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index 4eafafbe26..62c4c83d6d 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -10,286 +10,16 @@ //! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) //! for more info. -use std::cmp; -use std::net::SocketAddr; -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; - -use camino::Utf8PathBuf; use clap::Parser; use cli::Cli; -use dojo_metrics::exporters::prometheus::PrometheusRecorder; -use dojo_world::contracts::world::WorldContractReader; -use sqlx::sqlite::{ - SqliteAutoVacuum, SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous, -}; -use sqlx::SqlitePool; -use starknet::providers::jsonrpc::HttpTransport; -use starknet::providers::JsonRpcClient; -use tempfile::{NamedTempFile, TempDir}; -use tokio::sync::broadcast; -use tokio::sync::broadcast::Sender; -use tokio_stream::StreamExt; -use torii_core::engine::{Engine, EngineConfig, IndexingFlags, Processors}; -use torii_core::executor::Executor; -use torii_core::processors::store_transaction::StoreTransactionProcessor; -use torii_core::processors::EventProcessorConfig; -use torii_core::simple_broker::SimpleBroker; -use torii_core::sql::cache::ModelCache; -use torii_core::sql::Sql; -use torii_core::types::{Contract, ContractType, Model}; -use torii_server::proxy::Proxy; -use tracing::{error, info}; -use tracing_subscriber::{fmt, EnvFilter}; -use url::form_urlencoded; - -pub(crate) const LOG_TARGET: &str = "torii::cli"; +use torii_runner::Runner; mod cli; #[tokio::main] async fn main() -> anyhow::Result<()> { - let mut args = Cli::parse().args.with_config_file()?; - - let world_address = if let Some(world_address) = args.world_address { - world_address - } else { - return Err(anyhow::anyhow!("Please specify a world address.")); - }; - - args.indexing.contracts.push(Contract { address: world_address, r#type: ContractType::WORLD }); - - let filter_layer = EnvFilter::try_from_default_env() - .unwrap_or_else(|_| EnvFilter::new("info,hyper_reverse_proxy=off")); - - let subscriber = fmt::Subscriber::builder().with_env_filter(filter_layer).finish(); - - // Set the global subscriber - tracing::subscriber::set_global_default(subscriber) - .expect("Failed to set the global tracing subscriber"); - - // Setup cancellation for graceful shutdown - let (shutdown_tx, _) = broadcast::channel(1); - - let shutdown_tx_clone = shutdown_tx.clone(); - ctrlc::set_handler(move || { - let _ = shutdown_tx_clone.send(()); - }) - .expect("Error setting Ctrl-C handler"); - - let tempfile = NamedTempFile::new()?; - let database_path = if let Some(db_dir) = args.db_dir { - // Create the directory if it doesn't exist - std::fs::create_dir_all(&db_dir)?; - // Set the database file path inside the directory - db_dir.join("torii.db") - } else { - tempfile.path().to_path_buf() - }; - - let mut options = SqliteConnectOptions::from_str(&database_path.to_string_lossy())? - .create_if_missing(true) - .with_regexp(); - - // Performance settings - options = options.auto_vacuum(SqliteAutoVacuum::None); - options = options.journal_mode(SqliteJournalMode::Wal); - options = options.synchronous(SqliteSynchronous::Normal); - - let pool = SqlitePoolOptions::new().min_connections(1).connect_with(options.clone()).await?; - - let readonly_options = options.read_only(true); - let readonly_pool = SqlitePoolOptions::new() - .min_connections(1) - .max_connections(100) - .connect_with(readonly_options) - .await?; - - // Set the number of threads based on CPU count - let cpu_count = std::thread::available_parallelism().unwrap().get(); - let thread_count = cmp::min(cpu_count, 8); - sqlx::query(&format!("PRAGMA threads = {};", thread_count)).execute(&pool).await?; - - sqlx::migrate!("../../crates/torii/migrations").run(&pool).await?; - - let provider: Arc<_> = JsonRpcClient::new(HttpTransport::new(args.rpc)).into(); - - // Get world address - let world = WorldContractReader::new(world_address, provider.clone()); - - let (mut executor, sender) = Executor::new( - pool.clone(), - shutdown_tx.clone(), - provider.clone(), - args.indexing.max_concurrent_tasks, - ) - .await?; - let executor_handle = tokio::spawn(async move { executor.run().await }); - - let model_cache = Arc::new(ModelCache::new(readonly_pool.clone())); - let db = Sql::new(pool.clone(), sender.clone(), &args.indexing.contracts, model_cache.clone()) - .await?; - - let processors = Processors { - transaction: vec![Box::new(StoreTransactionProcessor)], - ..Processors::default() - }; - - let (block_tx, block_rx) = tokio::sync::mpsc::channel(100); - - let mut flags = IndexingFlags::empty(); - if args.indexing.transactions { - flags.insert(IndexingFlags::TRANSACTIONS); - } - if args.events.raw { - flags.insert(IndexingFlags::RAW_EVENTS); - } - if args.indexing.pending { - flags.insert(IndexingFlags::PENDING_BLOCKS); - } - - let mut engine: Engine>> = Engine::new( - world, - db.clone(), - provider.clone(), - processors, - EngineConfig { - max_concurrent_tasks: args.indexing.max_concurrent_tasks, - blocks_chunk_size: args.indexing.blocks_chunk_size, - events_chunk_size: args.indexing.events_chunk_size, - polling_interval: Duration::from_millis(args.indexing.polling_interval), - flags, - event_processor_config: EventProcessorConfig { - historical_events: args.events.historical.into_iter().collect(), - namespaces: args.indexing.namespaces.into_iter().collect(), - }, - world_block: args.indexing.world_block, - }, - shutdown_tx.clone(), - Some(block_tx), - &args.indexing.contracts, - ); - - let shutdown_rx = shutdown_tx.subscribe(); - let (grpc_addr, grpc_server) = torii_grpc::server::new( - shutdown_rx, - &readonly_pool, - block_rx, - world_address, - Arc::clone(&provider), - model_cache, - ) - .await?; - - let temp_dir = TempDir::new()?; - let artifacts_path = - args.artifacts_path.unwrap_or_else(|| Utf8PathBuf::from(temp_dir.path().to_str().unwrap())); - - tokio::fs::create_dir_all(&artifacts_path).await?; - let absolute_path = artifacts_path.canonicalize_utf8()?; - - let (artifacts_addr, artifacts_server) = torii_server::artifacts::new( - shutdown_tx.subscribe(), - &absolute_path, - readonly_pool.clone(), - ) - .await?; - - let mut libp2p_relay_server = torii_relay::server::Relay::new( - db, - provider.clone(), - args.relay.port, - args.relay.webrtc_port, - args.relay.websocket_port, - args.relay.local_key_path, - args.relay.cert_path, - ) - .expect("Failed to start libp2p relay server"); - - let addr = SocketAddr::new(args.server.http_addr, args.server.http_port); - - let proxy_server = Arc::new(Proxy::new( - addr, - args.server.http_cors_origins.filter(|cors_origins| !cors_origins.is_empty()), - Some(grpc_addr), - None, - Some(artifacts_addr), - Arc::new(readonly_pool.clone()), - )); - - let graphql_server = spawn_rebuilding_graphql_server( - shutdown_tx.clone(), - readonly_pool.into(), - proxy_server.clone(), - ); - - let gql_endpoint = format!("{addr}/graphql"); - let encoded: String = - form_urlencoded::byte_serialize(gql_endpoint.replace("0.0.0.0", "localhost").as_bytes()) - .collect(); - let explorer_url = format!("https://worlds.dev/torii?url={}", encoded); - info!(target: LOG_TARGET, endpoint = %addr, "Starting torii endpoint."); - info!(target: LOG_TARGET, endpoint = %gql_endpoint, "Serving Graphql playground."); - info!(target: LOG_TARGET, url = %explorer_url, "Serving World Explorer."); - info!(target: LOG_TARGET, path = %artifacts_path, "Serving ERC artifacts at path"); - - if args.explorer { - if let Err(e) = webbrowser::open(&explorer_url) { - error!(target: LOG_TARGET, error = %e, "Opening World Explorer in the browser."); - } - } - - if args.metrics.metrics { - let addr = SocketAddr::new(args.metrics.metrics_addr, args.metrics.metrics_port); - info!(target: LOG_TARGET, %addr, "Starting metrics endpoint."); - let prometheus_handle = PrometheusRecorder::install("torii")?; - let server = dojo_metrics::Server::new(prometheus_handle).with_process_metrics(); - tokio::spawn(server.start(addr)); - } - - let engine_handle = tokio::spawn(async move { engine.start().await }); - let proxy_server_handle = - tokio::spawn(async move { proxy_server.start(shutdown_tx.subscribe()).await }); - let graphql_server_handle = tokio::spawn(graphql_server); - let grpc_server_handle = tokio::spawn(grpc_server); - let libp2p_relay_server_handle = tokio::spawn(async move { libp2p_relay_server.run().await }); - let artifacts_server_handle = tokio::spawn(artifacts_server); - - tokio::select! { - res = engine_handle => res??, - res = executor_handle => res??, - res = proxy_server_handle => res??, - res = graphql_server_handle => res?, - res = grpc_server_handle => res??, - res = libp2p_relay_server_handle => res?, - res = artifacts_server_handle => res?, - _ = dojo_utils::signal::wait_signals() => {}, - }; - + let args = Cli::parse().args.with_config_file()?; + let runner = Runner::new(args); + runner.run().await?; Ok(()) } - -async fn spawn_rebuilding_graphql_server( - shutdown_tx: Sender<()>, - pool: Arc, - proxy_server: Arc, -) { - let mut broker = SimpleBroker::::subscribe(); - - loop { - let shutdown_rx = shutdown_tx.subscribe(); - let (new_addr, new_server) = torii_graphql::server::new(shutdown_rx, &pool).await; - - tokio::spawn(new_server); - - proxy_server.set_graphql_addr(new_addr).await; - - // Break the loop if there are no more events - if broker.next().await.is_none() { - break; - } else { - tokio::time::sleep(Duration::from_secs(1)).await; - } - } -} diff --git a/crates/dojo/core/Scarb.lock b/crates/dojo/core/Scarb.lock index 71b9ee70f7..c039265efd 100644 --- a/crates/dojo/core/Scarb.lock +++ b/crates/dojo/core/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "1.0.9" +version = "1.0.10" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo/core/Scarb.toml b/crates/dojo/core/Scarb.toml index 9d87f099ab..97ee22dc3f 100644 --- a/crates/dojo/core/Scarb.toml +++ b/crates/dojo/core/Scarb.toml @@ -3,7 +3,7 @@ cairo-version = "=2.8.4" edition = "2024_07" description = "The Dojo Core library for autonomous worlds." name = "dojo" -version = "1.0.9" +version = "1.0.10" [dependencies] starknet = "=2.8.4" diff --git a/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo b/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo index 0d8897f5a5..41bd5651a4 100644 --- a/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo +++ b/crates/katana/contracts/messaging/cairo/src/appchain_messaging.cairo @@ -6,17 +6,6 @@ //! listen for those events. When an event with a message is gathered //! by katana, a L1 handler transaction is then created and added to the pool. //! -//! For the appchain to send a message to starknet, the process can be done in two -//! fashions: -//! -//! 1. The appchain register messages hashes exactly as starknet does. And then -//! a transaction on starknet must be issued to consume the message. -//! -//! 2. The sequencer (katana in that case) has also the capability of directly send -//! send a transaction to "execute" the content of the message. In the appchain -//! context this is a very effective manner to have a more dynamic and real-time -//! messaging than manual consuming of a message. -//! /// Trait for Appchain messaging. For now, the messaging only whitelist one /// appchain. @@ -46,17 +35,6 @@ trait IAppchainMessaging { fn consume_message_from_appchain( ref self: T, from_address: starknet::ContractAddress, payload: Span, ) -> felt252; - - /// Executes a message sent from the appchain. A message to execute - /// does not need to be registered as consumable. It is automatically - /// consumed while executed. - fn execute_message_from_appchain( - ref self: T, - from_address: starknet::ContractAddress, - to_address: starknet::ContractAddress, - selector: felt252, - payload: Span, - ); } #[starknet::interface] @@ -76,7 +54,7 @@ mod appchain_messaging { // Owner of this contract. owner: ContractAddress, // The account on Starknet (or the chain where this contract is deployed) - // used by the appchain sequencer to register messages hashes / execute messages. + // used by the appchain sequencer to register messages hashes. appchain_account: ContractAddress, // The nonce for messages sent from Starknet. sn_to_appc_nonce: felt252, @@ -93,7 +71,6 @@ mod appchain_messaging { MessageSentToAppchain: MessageSentToAppchain, MessagesRegisteredFromAppchain: MessagesRegisteredFromAppchain, MessageConsumed: MessageConsumed, - MessageExecuted: MessageExecuted, Upgraded: Upgraded, } @@ -126,17 +103,6 @@ mod appchain_messaging { payload: Span, } - #[derive(Drop, starknet::Event)] - struct MessageExecuted { - #[key] - from_address: ContractAddress, - #[key] - to_address: ContractAddress, - #[key] - selector: felt252, - payload: Span, - } - #[derive(Drop, starknet::Event)] struct Upgraded { class_hash: ClassHash, @@ -171,8 +137,19 @@ mod appchain_messaging { hash.try_into().expect('starknet keccak overflow') } - /// Computes message hash to consume messages from appchain. - /// starknet_keccak(from_address, to_address, payload_len, payload). + /// Computes the hash of a message that is sent from the Appchain to Starknet. + /// + /// + /// + /// # Arguments + /// + /// * `from_address` - Contract address of the message sender on the Appchain. + /// * `to_address` - Contract address to send the message to on the Appchain. + /// * `payload` - The message payload. + /// + /// # Returns + /// + /// The hash of the message from the Appchain to Starknet. fn compute_hash_appc_to_sn( from_address: ContractAddress, to_address: ContractAddress, payload: Span ) -> felt252 { @@ -192,12 +169,32 @@ mod appchain_messaging { starknet_keccak(hash_data.span()) } - /// Computes message hash to send messages to appchain. - /// starknet_keccak(nonce, to_address, selector, payload). + /// Computes the hash of a message that is sent from Starknet to the Appchain. + /// + /// + /// + /// # Arguments + /// + /// * `from_address` - Contract address of the message sender on the Appchain. + /// * `to_address` - Contract address to send the message to on the Appchain. + /// * `selector` - The `l1_handler` function selector of the contract on the Appchain + /// to execute. + /// * `payload` - The message payload. + /// * `nonce` - Nonce of the message. + /// + /// # Returns + /// + /// The hash of the message from Starknet to the Appchain. fn compute_hash_sn_to_appc( - nonce: felt252, to_address: ContractAddress, selector: felt252, payload: Span + from_address: ContractAddress, + to_address: ContractAddress, + selector: felt252, + payload: Span, + nonce: felt252 ) -> felt252 { - let mut hash_data = array![nonce, to_address.into(), selector,]; + let mut hash_data = array![ + from_address.into(), to_address.into(), nonce, selector, payload.len().into(), + ]; let mut i = 0_usize; loop { @@ -208,7 +205,7 @@ mod appchain_messaging { i += 1; }; - starknet_keccak(hash_data.span()) + core::poseidon::poseidon_hash_span(hash_data.span()) } #[abi(embed_v0)] @@ -308,26 +305,5 @@ mod appchain_messaging { msg_hash } - - fn execute_message_from_appchain( - ref self: ContractState, - from_address: ContractAddress, - to_address: ContractAddress, - selector: felt252, - payload: Span, - ) { - assert( - self.appchain_account.read() == starknet::get_caller_address(), - 'Unauthorized executor', - ); - - match starknet::call_contract_syscall(to_address, selector, payload) { - Result::Ok(_) => self - .emit(MessageExecuted { from_address, to_address, selector, payload, }), - Result::Err(e) => { - panic(e) - } - } - } } } diff --git a/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo b/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo index eacffbb26c..4cc6e681bb 100644 --- a/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo +++ b/crates/katana/contracts/messaging/cairo/src/contract_msg_starknet.cairo @@ -4,10 +4,9 @@ //! This contract can sends messages using the send message to l1 //! syscall as we normally do for messaging. //! -//! If the message contains a `to_address` that is not zero, the message -//! hash will be sent to starknet to be registered. -//! If the `to_address` is zero, then the message will then fire a transaction -//! on the starknet to directly execute the message content. +//! However, the `to_address` is set to the `MSG` magic value since +//! this field is restricted to a valid Ethereum address, too small to +//! be a valid Starknet address. use starknet::ContractAddress; #[starknet::interface] @@ -21,20 +20,6 @@ trait IContractAppchain { /// * `to_address` - Contract address on Starknet. /// * `value` - Value to be sent in the payload. fn send_message(ref self: T, to_address: ContractAddress, value: felt252); - - /// Executes a message on Starknet. When the Katana will see this message - /// with `to_address` set to 0, an invoke transaction will be fired. - /// So basically this function can invoke any contract on Starknet, the fees on starknet - /// being paid by the sequencer. We can here imagine several scenarios. :) - /// The invoke though is not directly done to the destination contract, but the - /// app messaging contract that will forward the execution. - /// - /// # Arguments - /// - /// * `to_address` - Contract address on Starknet. - /// * `selector` - Selector. - /// * `value` - Value to be sent as argument to the contract being executed on starknet. - fn execute_message(ref self: T, to_address: ContractAddress, selector: felt252, value: felt252); } #[starknet::contract] @@ -67,12 +52,5 @@ mod contract_msg_starknet { let buf: Array = array![to_address.into(), value]; starknet::send_message_to_l1_syscall('MSG', buf.span()).unwrap_syscall(); } - - fn execute_message( - ref self: ContractState, to_address: ContractAddress, selector: felt252, value: felt252, - ) { - let buf: Array = array![to_address.into(), selector, value]; - starknet::send_message_to_l1_syscall('EXE', buf.span()).unwrap_syscall(); - } } } diff --git a/crates/katana/core/Cargo.toml b/crates/katana/core/Cargo.toml index 896f619a48..66231271f1 100644 --- a/crates/katana/core/Cargo.toml +++ b/crates/katana/core/Cargo.toml @@ -28,6 +28,7 @@ reqwest.workspace = true serde.workspace = true serde_json.workspace = true starknet.workspace = true +starknet-crypto.workspace = true starknet-types-core.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/crates/katana/core/src/service/messaging/mod.rs b/crates/katana/core/src/service/messaging/mod.rs index 2626947ad5..55174777f5 100644 --- a/crates/katana/core/src/service/messaging/mod.rs +++ b/crates/katana/core/src/service/messaging/mod.rs @@ -52,7 +52,7 @@ use katana_executor::ExecutorFactory; use katana_primitives::chain::ChainId; use katana_primitives::receipt::MessageToL1; use serde::{Deserialize, Serialize}; -use tracing::{error, info}; +use tracing::{error, info, trace}; pub use self::service::{MessagingOutcome, MessagingService}; #[cfg(feature = "starknet-messaging")] @@ -229,11 +229,19 @@ impl Future for MessagingTask { while let Poll::Ready(Some(outcome)) = this.messaging.poll_next_unpin(cx) { match outcome { MessagingOutcome::Gather { msg_count, .. } => { - info!(target: LOG_TARGET, %msg_count, "Collected messages from settlement chain."); + if msg_count > 0 { + info!(target: LOG_TARGET, %msg_count, "Collected messages from settlement chain."); + } + + trace!(target: LOG_TARGET, %msg_count, "Collected messages from settlement chain."); } MessagingOutcome::Send { msg_count, .. } => { - info!(target: LOG_TARGET, %msg_count, "Sent messages to the settlement chain."); + if msg_count > 0 { + info!(target: LOG_TARGET, %msg_count, "Sent messages to the settlement chain."); + } + + trace!(target: LOG_TARGET, %msg_count, "Sent messages to the settlement chain."); } } } diff --git a/crates/katana/core/src/service/messaging/service.rs b/crates/katana/core/src/service/messaging/service.rs index 46d2379ce0..d290b611b5 100644 --- a/crates/katana/core/src/service/messaging/service.rs +++ b/crates/katana/core/src/service/messaging/service.rs @@ -265,33 +265,11 @@ fn interval_from_seconds(secs: u64) -> Interval { fn trace_msg_to_l1_sent(messages: &[MessageToL1], hashes: &[String]) { assert_eq!(messages.len(), hashes.len()); - #[cfg(feature = "starknet-messaging")] - let hash_exec_str = format!("{:#064x}", super::starknet::HASH_EXEC); - for (i, m) in messages.iter().enumerate() { let payload_str: Vec = m.payload.iter().map(|f| format!("{:#x}", *f)).collect(); let hash = &hashes[i]; - #[cfg(feature = "starknet-messaging")] - if hash == &hash_exec_str { - let to_address = &payload_str[0]; - let selector = &payload_str[1]; - let payload_str = &payload_str[2..]; - - #[rustfmt::skip] - info!( - target: LOG_TARGET, - from_address = %m.from_address, - to_address = %to_address, - selector = %selector, - payload = %payload_str.join(", "), - "Message executed on settlement layer.", - ); - - continue; - } - // We check for magic value 'MSG' used only when we are doing L3-L2 messaging. let (to_address, payload_str) = if format!("{}", m.to_address) == "0x4d5347" { (payload_str[0].clone(), &payload_str[1..]) diff --git a/crates/katana/core/src/service/messaging/starknet.rs b/crates/katana/core/src/service/messaging/starknet.rs index 7c3f2bb3db..5b379940f5 100644 --- a/crates/katana/core/src/service/messaging/starknet.rs +++ b/crates/katana/core/src/service/messaging/starknet.rs @@ -1,14 +1,13 @@ use std::sync::Arc; +use alloy_primitives::B256; use anyhow::Result; use async_trait::async_trait; use katana_primitives::chain::ChainId; use katana_primitives::receipt::MessageToL1; use katana_primitives::transaction::L1HandlerTx; -use katana_primitives::utils::transaction::compute_l2_to_l1_message_hash; use starknet::accounts::{Account, ExecutionEncoding, SingleOwnerAccount}; use starknet::core::types::{BlockId, BlockTag, Call, EmittedEvent, EventFilter, Felt}; -use starknet::core::utils::starknet_keccak; use starknet::macros::{felt, selector}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{AnyProvider, JsonRpcClient, Provider}; @@ -19,14 +18,14 @@ use url::Url; use super::{Error, MessagingConfig, Messenger, MessengerResult, LOG_TARGET}; /// As messaging in starknet is only possible with EthAddress in the `to_address` -/// field, we have to set magic value to understand what the user want to do. -/// In the case of execution -> the felt 'EXE' will be passed. -/// And for normal messages, the felt 'MSG' is used. -/// Those values are very not likely a valid account address on starknet. +/// field, in teh current design we set the `to_address` to the `MSG` magic value. +/// +/// Blockifier is the one responsible for this out of range error. +/// const MSG_MAGIC: Felt = felt!("0x4d5347"); -const EXE_MAGIC: Felt = felt!("0x455845"); -pub const HASH_EXEC: Felt = felt!("0xee"); +/// TODO: This may come from the configuration. +pub const MESSAGE_SENT_EVENT_KEY: Felt = selector!("MessageSent"); #[derive(Debug)] pub struct StarknetMessaging { @@ -73,11 +72,10 @@ impl StarknetMessaging { from_block: Some(from_block), to_block: Some(to_block), address: Some(self.messaging_contract_address), - // TODO: this might come from the configuration actually. - keys: None, + keys: Some(vec![vec![MESSAGE_SENT_EVENT_KEY]]), }; - // TODO: this chunk_size may also come from configuration? + // TODO: This chunk_size may also come from configuration? let chunk_size = 200; let mut continuation_token: Option = None; @@ -127,9 +125,7 @@ impl StarknetMessaging { } /// Sends messages hashes to settlement layer by sending a transaction. - async fn send_hashes(&self, mut hashes: Vec) -> MessengerResult { - hashes.retain(|&x| x != HASH_EXEC); - + async fn send_hashes(&self, hashes: Vec) -> MessengerResult { if hashes.is_empty() { return Ok(Felt::ZERO); } @@ -221,94 +217,54 @@ impl Messenger for StarknetMessaging { return Ok(vec![]); } - let (hashes, calls) = parse_messages(messages)?; - - if !calls.is_empty() { - match self.send_invoke_tx(calls).await { - Ok(tx_hash) => { - trace!(target: LOG_TARGET, tx_hash = %format!("{:#064x}", tx_hash), "Invoke transaction hash."); - } - Err(e) => { - error!(target: LOG_TARGET, error = %e, "Sending invoke tx on Starknet."); - return Err(Error::SendError); - } - }; - } - + let hashes = parse_messages(messages)?; self.send_hashes(hashes.clone()).await?; Ok(hashes) } } -/// Parses messages sent by cairo contracts to compute their hashes. -/// -/// Messages can also be labelled as EXE, which in this case generate a `Call` -/// additionally to the hash. -fn parse_messages(messages: &[MessageToL1]) -> MessengerResult<(Vec, Vec)> { +/// Parses messages sent by cairo contracts on the appchain to compute their hashes. +fn parse_messages(messages: &[MessageToL1]) -> MessengerResult> { let mut hashes: Vec = vec![]; - let mut calls: Vec = vec![]; for m in messages { // Field `to_address` is restricted to eth addresses space. So the - // `to_address` is set to 'EXE'/'MSG' to indicate that the message - // has to be executed or sent normally. + // `to_address` is set to 'MSG' to indicate that the message + // has to be sent to the L2 messaging contract. + // + // Blockifier is the one responsible for this out of range error. + // let magic = m.to_address; - if magic == EXE_MAGIC { - if m.payload.len() < 2 { - error!( - target: LOG_TARGET, - "Message execution is expecting a payload of at least length \ - 2. With [0] being the contract address, and [1] the selector.", - ); - } + if magic != MSG_MAGIC { + warn!(target: LOG_TARGET, magic = %magic, "Skipping message with non-MSG magic."); + continue; + } - let to = m.payload[0]; - let selector = m.payload[1]; + // In the case or regular message, we compute the message's hash + // which will then be sent in a transaction to be registered as being + // ready for consumption by the L2 messaging contract. - let mut calldata = vec![]; - // We must exclude the `to_address` and `selector` from the actual payload. - if m.payload.len() >= 3 { - calldata.extend(m.payload[2..].to_vec()); - } + // As to_address is used by the magic, the `to_address` we want + // is the first element of the payload. + let to_address = m.payload[0]; - calls.push(Call { to, selector, calldata }); - hashes.push(HASH_EXEC); - } else if magic == MSG_MAGIC { - // In the case or regular message, we compute the message's hash - // which will then be sent in a transaction to be registered. - - // As to_address is used by the magic, the `to_address` we want - // is the first element of the payload. - let to_address = m.payload[0]; - - // Then, the payload must be changed to only keep the rest of the - // data, without the first element that was the `to_address`. - let payload = &m.payload[1..]; - - let mut buf: Vec = vec![]; - buf.extend(m.from_address.to_bytes_be()); - buf.extend(to_address.to_bytes_be()); - buf.extend(Felt::from(payload.len()).to_bytes_be()); - for p in payload { - buf.extend(p.to_bytes_be()); - } + // Then, the payload must be changed to only keep the rest of the + // data, without the first element that was the `to_address`. + let payload = &m.payload[1..]; - hashes.push(starknet_keccak(&buf)); - } else { - // Skip the message if no valid magic number found. - warn!(target: LOG_TARGET, magic = ?magic, "Invalid message to_address magic value."); - continue; - } + let message_hash = + compute_appchain_to_starknet_message_hash(m.from_address.into(), to_address, payload); + hashes.push(message_hash); } - Ok((hashes, calls)) + Ok(hashes) } fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result { - if event.keys[0] != selector!("MessageSentToAppchain") { - debug!( + if event.keys[0] != MESSAGE_SENT_EVENT_KEY { + error!( target: LOG_TARGET, event_key = ?event.keys[0], "Event can't be converted into L1HandlerTx." @@ -331,8 +287,15 @@ fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result l2 hash computation instead. - let message_hash = compute_l2_to_l1_message_hash(from_address, to_address, &calldata); + let message_hash = compute_starknet_to_appchain_message_hash( + from_address, + to_address, + nonce, + entry_point_selector, + &calldata, + ); + + let message_hash = B256::from_slice(message_hash.to_bytes_be().as_slice()); Ok(L1HandlerTx { nonce, @@ -340,6 +303,7 @@ fn l1_handler_tx_from_event(event: &EmittedEvent, chain_id: ChainId) -> Result Result +fn compute_starknet_to_appchain_message_hash( + from_address: Felt, + to_address: Felt, + nonce: Felt, + entry_point_selector: Felt, + payload: &[Felt], +) -> Felt { + let mut buf: Vec = + vec![from_address, to_address, nonce, entry_point_selector, Felt::from(payload.len())]; + for p in payload { + buf.push(*p); + } + + starknet_crypto::poseidon_hash_many(&buf) +} + +/// Computes the hash of a L3 to L2 message. +/// +/// Piltover uses poseidon hash for all hashes computation. +/// +fn compute_appchain_to_starknet_message_hash( + from_address: Felt, + to_address: Felt, + payload: &[Felt], +) -> Felt { + let mut buf: Vec = vec![from_address, to_address, Felt::from(payload.len())]; + for p in payload { + buf.push(*p); + } + + starknet_crypto::poseidon_hash_many(&buf) +} + #[cfg(test)] mod tests { @@ -359,41 +360,25 @@ mod tests { fn parse_messages_msg() { let from_address = selector!("from_address"); let to_address = selector!("to_address"); - let selector = selector!("selector"); + let _selector = selector!("selector"); let payload_msg = vec![to_address, Felt::ONE, Felt::TWO]; - let payload_exe = vec![to_address, selector, Felt::ONE, Felt::TWO]; - - let messages = vec![ - MessageToL1 { - from_address: from_address.into(), - to_address: MSG_MAGIC, - payload: payload_msg, - }, - MessageToL1 { - from_address: from_address.into(), - to_address: EXE_MAGIC, - payload: payload_exe.clone(), - }, - ]; - - let (hashes, calls) = parse_messages(&messages).unwrap(); - - assert_eq!(hashes.len(), 2); + + let messages = vec![MessageToL1 { + from_address: from_address.into(), + to_address: MSG_MAGIC, + payload: payload_msg, + }]; + + let hashes = parse_messages(&messages).unwrap(); + + assert_eq!(hashes.len(), 1); assert_eq!( hashes, vec![ - Felt::from_hex( - "0x03a1d2e131360f15e26dd4f6ff10550685611cc25f75e7950b704adb04b36162" - ) - .unwrap(), - HASH_EXEC, + Felt::from_hex("0x5063bd24379be4da83d607725d1a9f7e5571cb1be30784b4a7a22996f59ff22") + .unwrap(), ] ); - - assert_eq!(calls.len(), 1); - assert_eq!(calls[0].to, to_address); - assert_eq!(calls[0].selector, selector); - assert_eq!(calls[0].calldata, payload_exe[2..].to_vec()); } #[test] @@ -411,21 +396,6 @@ mod tests { parse_messages(&messages).unwrap(); } - #[test] - #[should_panic] - fn parse_messages_exe_bad_payload() { - let from_address = selector!("from_address"); - let payload_exe = vec![Felt::ONE]; - - let messages = vec![MessageToL1 { - from_address: from_address.into(), - to_address: EXE_MAGIC, - payload: payload_exe, - }]; - - parse_messages(&messages).unwrap(); - } - #[test] fn l1_handler_tx_from_event_parse_ok() { let from_address = selector!("from_address"); @@ -448,25 +418,26 @@ mod tests { from_address: felt!( "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" ), - keys: vec![ - selector!("MessageSentToAppchain"), - selector!("random_hash"), - from_address, - to_address, - ], + keys: vec![MESSAGE_SENT_EVENT_KEY, selector!("random_hash"), from_address, to_address], data: vec![selector, nonce, Felt::from(calldata.len() as u128), Felt::THREE], block_hash: Some(selector!("block_hash")), block_number: Some(0), transaction_hash, }; - let message_hash = compute_l2_to_l1_message_hash(from_address, to_address, &calldata); + let message_hash = compute_starknet_to_appchain_message_hash( + from_address, + to_address, + nonce, + selector, + &calldata, + ); let expected = L1HandlerTx { nonce, calldata, chain_id, - message_hash, + message_hash: B256::from_slice(message_hash.to_bytes_be().as_slice()), paid_fee_on_l1: 30000_u128, version: Felt::ZERO, entry_point_selector: selector, diff --git a/crates/katana/rpc/rpc-types/src/trie.rs b/crates/katana/rpc/rpc-types/src/trie.rs index 5fab51bc57..00d310efb6 100644 --- a/crates/katana/rpc/rpc-types/src/trie.rs +++ b/crates/katana/rpc/rpc-types/src/trie.rs @@ -77,6 +77,7 @@ pub struct GetStorageProofResponse { } #[derive(Debug, Default, Serialize, Deserialize)] +#[serde(transparent)] pub struct ClassesProof { pub nodes: Nodes, } @@ -101,9 +102,11 @@ pub struct ContractLeafData { } #[derive(Debug, Default, Serialize, Deserialize)] +#[serde(transparent)] pub struct ContractStorageProofs { pub nodes: Vec, } + #[derive(Debug, Serialize, Deserialize)] pub struct NodeWithHash { pub node_hash: Felt, diff --git a/crates/katana/rpc/rpc/tests/proofs.rs b/crates/katana/rpc/rpc/tests/proofs.rs index 66268a42ea..c07a8a21ba 100644 --- a/crates/katana/rpc/rpc/tests/proofs.rs +++ b/crates/katana/rpc/rpc/tests/proofs.rs @@ -122,7 +122,7 @@ async fn genesis_states() { let classes_proof = MultiProof::from(proofs.classes_proof.nodes); let classes_tree_root = proofs.global_roots.classes_tree_root; - let classes_verification_result = katana_trie::verify_proof::( + let classes_verification_result = katana_trie::verify_proof::( &classes_proof, classes_tree_root, genesis_classes, diff --git a/crates/katana/storage/db/src/trie/mod.rs b/crates/katana/storage/db/src/trie/mod.rs index fce7968a24..7301b66dc4 100644 --- a/crates/katana/storage/db/src/trie/mod.rs +++ b/crates/katana/storage/db/src/trie/mod.rs @@ -388,7 +388,7 @@ fn to_db_key(key: &DatabaseKey<'_>) -> models::trie::TrieDatabaseKey { #[cfg(test)] mod tests { - use katana_primitives::hash::{Pedersen, StarkHash}; + use katana_primitives::hash::{Poseidon, StarkHash}; use katana_primitives::{felt, hash}; use katana_trie::{verify_proof, ClassesTrie, CommitId}; use starknet::macros::short_string; @@ -447,7 +447,7 @@ mod tests { let proofs0 = snapshot0.multiproof(vec![felt!("0x9999")]); let verify_result0 = - verify_proof::(&proofs0, snapshot_root0, vec![felt!("0x9999")]); + verify_proof::(&proofs0, snapshot_root0, vec![felt!("0x9999")]); let value = hash::Poseidon::hash(&short_string!("CONTRACT_CLASS_LEAF_V0"), &felt!("0xdead")); @@ -464,7 +464,7 @@ mod tests { let proofs1 = snapshot1.multiproof(vec![felt!("0x6969")]); let verify_result1 = - verify_proof::(&proofs1, snapshot_root1, vec![felt!("0x6969")]); + verify_proof::(&proofs1, snapshot_root1, vec![felt!("0x6969")]); let value = hash::Poseidon::hash(&short_string!("CONTRACT_CLASS_LEAF_V0"), &felt!("0x80085")); @@ -475,7 +475,7 @@ mod tests { let root = trie.root(); let proofs = trie.multiproof(vec![felt!("0x6969"), felt!("0x9999")]); let result = - verify_proof::(&proofs, root, vec![felt!("0x6969"), felt!("0x9999")]); + verify_proof::(&proofs, root, vec![felt!("0x6969"), felt!("0x9999")]); let value0 = hash::Poseidon::hash(&short_string!("CONTRACT_CLASS_LEAF_V0"), &felt!("0x80085")); diff --git a/crates/katana/trie/src/classes.rs b/crates/katana/trie/src/classes.rs index e2481c7dcd..ff71e3b3b8 100644 --- a/crates/katana/trie/src/classes.rs +++ b/crates/katana/trie/src/classes.rs @@ -1,10 +1,9 @@ use bonsai_trie::{BonsaiDatabase, BonsaiPersistentDatabase, MultiProof}; use katana_primitives::block::BlockNumber; use katana_primitives::class::{ClassHash, CompiledClassHash}; -use katana_primitives::hash::Pedersen; +use katana_primitives::hash::{Poseidon, StarkHash}; use katana_primitives::Felt; use starknet::macros::short_string; -use starknet_types_core::hash::{Poseidon, StarkHash}; use crate::id::CommitId; @@ -15,7 +14,7 @@ impl ClassesMultiProof { // TODO: maybe perform results check in this method as well. make it accept the compiled class // hashes pub fn verify(&self, root: Felt, class_hashes: Vec) -> Vec { - crate::verify_proof::(&self.0, root, class_hashes) + crate::verify_proof::(&self.0, root, class_hashes) } } @@ -27,7 +26,7 @@ impl From for ClassesMultiProof { #[derive(Debug)] pub struct ClassesTrie { - trie: crate::BonsaiTrie, + trie: crate::BonsaiTrie, } ////////////////////////////////////////////////////////////// diff --git a/crates/sozo/walnut/Cargo.toml b/crates/sozo/walnut/Cargo.toml index 0a9cf690eb..04ad812f0a 100644 --- a/crates/sozo/walnut/Cargo.toml +++ b/crates/sozo/walnut/Cargo.toml @@ -8,18 +8,18 @@ version.workspace = true [dependencies] anyhow.workspace = true console.workspace = true -dojo-world.workspace = true reqwest.workspace = true scarb.workspace = true scarb-ui.workspace = true serde.workspace = true serde_json.workspace = true -sozo-scarbext.workspace = true starknet.workspace = true thiserror.workspace = true url.workspace = true urlencoding = "2.1.3" walkdir.workspace = true +dojo-utils.workspace = true +clap.workspace = true [dev-dependencies] starknet.workspace = true diff --git a/crates/sozo/walnut/src/debugger.rs b/crates/sozo/walnut/src/debugger.rs index 0edd38b80c..97d3ba546e 100644 --- a/crates/sozo/walnut/src/debugger.rs +++ b/crates/sozo/walnut/src/debugger.rs @@ -1,12 +1,11 @@ -use dojo_world::diff::WorldDiff; +use dojo_utils::TransactionResult; use scarb::core::Workspace; use scarb_ui::Ui; -use starknet::core::types::Felt; use url::Url; use crate::transaction::walnut_debug_transaction; -use crate::verification::walnut_verify_migration_strategy; -use crate::{utils, Error}; +use crate::verification::walnut_verify; +use crate::Error; /// A debugger for Starknet transactions embedding the walnut configuration. #[derive(Debug)] @@ -26,7 +25,18 @@ impl WalnutDebugger { } /// Debugs a transaction with Walnut by printing a link to the Walnut debugger page. - pub fn debug_transaction(&self, ui: &Ui, transaction_hash: &Felt) -> Result<(), Error> { + pub fn debug_transaction( + &self, + ui: &Ui, + transaction_result: &TransactionResult, + ) -> Result<(), Error> { + let transaction_hash = match transaction_result { + TransactionResult::Hash(transaction_hash) => transaction_hash, + TransactionResult::Noop => { + return Ok(()); + } + TransactionResult::HashReceipt(transaction_hash, _) => transaction_hash, + }; let url = walnut_debug_transaction(&self.rpc_url, transaction_hash)?; ui.print(format!("Debug transaction with Walnut: {url}")); Ok(()) @@ -34,17 +44,7 @@ impl WalnutDebugger { /// Verifies a migration strategy with Walnut by uploading the source code of the contracts and /// models in the strategy. - pub async fn verify_migration_strategy( - &self, - ws: &Workspace<'_>, - world_diff: &WorldDiff, - ) -> anyhow::Result<()> { - walnut_verify_migration_strategy(ws, self.rpc_url.to_string(), world_diff).await - } - - /// Checks if the Walnut API key is set. - pub fn check_api_key() -> Result<(), Error> { - let _ = utils::walnut_get_api_key()?; - Ok(()) + pub async fn verify(ws: &Workspace<'_>) -> anyhow::Result<()> { + walnut_verify(ws).await } } diff --git a/crates/sozo/walnut/src/lib.rs b/crates/sozo/walnut/src/lib.rs index 96feb2be92..08ec8d767a 100644 --- a/crates/sozo/walnut/src/lib.rs +++ b/crates/sozo/walnut/src/lib.rs @@ -25,6 +25,7 @@ mod debugger; mod transaction; mod utils; mod verification; +pub mod walnut; pub use debugger::WalnutDebugger; diff --git a/crates/sozo/walnut/src/utils.rs b/crates/sozo/walnut/src/utils.rs index ca861249c1..89da582579 100644 --- a/crates/sozo/walnut/src/utils.rs +++ b/crates/sozo/walnut/src/utils.rs @@ -1,10 +1,6 @@ use std::env; -use crate::{Error, WALNUT_API_KEY_ENV_VAR, WALNUT_API_URL, WALNUT_API_URL_ENV_VAR}; - -pub fn walnut_get_api_key() -> Result { - env::var(WALNUT_API_KEY_ENV_VAR).map_err(|_| Error::MissingApiKey) -} +use crate::{WALNUT_API_URL, WALNUT_API_URL_ENV_VAR}; pub fn walnut_get_api_url() -> String { env::var(WALNUT_API_URL_ENV_VAR).unwrap_or_else(|_| WALNUT_API_URL.to_string()) diff --git a/crates/sozo/walnut/src/verification.rs b/crates/sozo/walnut/src/verification.rs index ca11be74ad..ab885ef950 100644 --- a/crates/sozo/walnut/src/verification.rs +++ b/crates/sozo/walnut/src/verification.rs @@ -3,67 +3,31 @@ use std::io; use std::path::Path; use console::{pad_str, Alignment, Style, StyledObject}; -use dojo_world::diff::{ResourceDiff, WorldDiff}; -use dojo_world::local::ResourceLocal; -use dojo_world::remote::ResourceRemote; -use dojo_world::ResourceType; use reqwest::StatusCode; use scarb::core::Workspace; use serde::Serialize; use serde_json::Value; -use sozo_scarbext::WorkspaceExt; use walkdir::WalkDir; -use crate::utils::{walnut_get_api_key, walnut_get_api_url}; +use crate::utils::walnut_get_api_url; use crate::Error; -/// Verifies all classes declared during migration. -/// Only supported on hosted networks (non-localhost). -/// -/// This function verifies all contracts and models in the strategy. For every contract and model, -/// it sends a request to the Walnut backend with the class name, class hash, RPC URL, and source -/// code. Walnut will then build the project with Sozo, compare the Sierra bytecode with the -/// bytecode on the network, and if they are equal, it will store the source code and associate it -/// with the class hash. -pub async fn walnut_verify_migration_strategy( - ws: &Workspace<'_>, - rpc_url: String, - world_diff: &WorldDiff, -) -> anyhow::Result<()> { - let ui = ws.config().ui(); - // Check if rpc_url is localhost - if rpc_url.contains("localhost") || rpc_url.contains("127.0.0.1") { - ui.print(" "); - ui.warn("Verifying classes with Walnut is only supported on hosted networks."); - ui.print(" "); - return Ok(()); - } - - // Check if there are any contracts or models in the strategy - if world_diff.is_synced() { - ui.print(" "); - ui.print("🌰 No contracts or models to verify."); - ui.print(" "); - return Ok(()); - } +#[derive(Debug, Serialize)] +struct VerificationPayload { + /// JSON that contains a map where the key is the path to the file and the value is the content + /// of the file. It should contain all files required to build the Dojo project with Sozo. + pub source_code: Value, - let _profile_config = ws.load_profile_config()?; + pub cairo_version: String, +} - for (_selector, resource) in world_diff.resources.iter() { - if resource.resource_type() == ResourceType::Contract { - match resource { - ResourceDiff::Created(ResourceLocal::Contract(_contract)) => { - // Need to verify created. - } - ResourceDiff::Updated(_, ResourceRemote::Contract(_contract)) => { - // Need to verify updated. - } - _ => { - // Synced, we don't need to verify. - } - } - } - } +/// Verifies all classes in the workspace. +/// +/// This function verifies all contracts and models in the workspace. It sends a single request to +/// the Walnut backend with the source code. Walnut will then build the project and store +/// the source code associated with the class hashes. +pub async fn walnut_verify(ws: &Workspace<'_>) -> anyhow::Result<()> { + let ui = ws.config().ui(); // Notify start of verification ui.print(" "); @@ -71,46 +35,29 @@ pub async fn walnut_verify_migration_strategy( ui.print(" "); // Retrieve the API key and URL from environment variables - let _api_key = walnut_get_api_key()?; - let _api_url = walnut_get_api_url(); + let api_url = walnut_get_api_url(); - // Collect source code - // TODO: now it's the same output as scarb, need to update the dojo fork to output the source - // code, or does scarb supports it already? + // its path to a file so `parent` should never return `None` + let root_dir: &Path = ws.manifest_path().parent().unwrap().as_std_path(); - Ok(()) -} + let source_code = collect_source_code(root_dir)?; + let cairo_version = scarb::version::get().version; -fn _get_class_name_from_artifact_path(path: &Path, namespace: &str) -> Result { - let file_name = path.file_stem().and_then(OsStr::to_str).ok_or(Error::InvalidFileName)?; - let class_name = file_name.strip_prefix(namespace).ok_or(Error::NamespacePrefixNotFound)?; - Ok(class_name.to_string()) -} + let verification_payload = + VerificationPayload { source_code, cairo_version: cairo_version.to_string() }; -#[derive(Debug, Serialize)] -struct _VerificationPayload { - /// The names of the classes we want to verify together with the selector. - pub class_names: Vec, - /// The hashes of the Sierra classes. - pub class_hashes: Vec, - /// The RPC URL of the network where these classes are declared (can only be a hosted network). - pub rpc_url: String, - /// JSON that contains a map where the key is the path to the file and the value is the content - /// of the file. It should contain all files required to build the Dojo project with Sozo. - pub source_code: Value, + // Send verification request + match verify_classes(verification_payload, &api_url).await { + Ok(message) => ui.print(_subtitle(message)), + Err(e) => ui.print(_subtitle(e.to_string())), + } + + Ok(()) } -async fn _verify_classes( - payload: _VerificationPayload, - api_url: &str, - api_key: &str, -) -> Result { - let res = reqwest::Client::new() - .post(format!("{api_url}/v1/verify")) - .header("x-api-key", api_key) - .json(&payload) - .send() - .await?; +async fn verify_classes(payload: VerificationPayload, api_url: &str) -> Result { + let res = + reqwest::Client::new().post(format!("{api_url}/v1/verify")).json(&payload).send().await?; if res.status() == StatusCode::OK { Ok(res.text().await?) @@ -119,7 +66,7 @@ async fn _verify_classes( } } -fn _collect_source_code(root_dir: &Path) -> Result { +fn collect_source_code(root_dir: &Path) -> Result { fn collect_files( root_dir: &Path, search_dir: &Path, diff --git a/crates/sozo/walnut/src/walnut.rs b/crates/sozo/walnut/src/walnut.rs new file mode 100644 index 0000000000..ced939d281 --- /dev/null +++ b/crates/sozo/walnut/src/walnut.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use clap::{Args, Subcommand}; +use scarb::core::Config; + +use crate::WalnutDebugger; + +#[derive(Debug, Args)] +pub struct WalnutArgs { + #[command(subcommand)] + pub command: WalnutVerifyCommand, +} + +#[derive(Debug, Subcommand)] +pub enum WalnutVerifyCommand { + #[command( + about = "Verify contracts in walnut.dev - essential for debugging source code in Walnut" + )] + Verify(WalnutVerifyOptions), +} + +#[derive(Debug, Args)] +pub struct WalnutVerifyOptions {} + +impl WalnutArgs { + pub fn run(self, config: &Config) -> Result<()> { + let ws = scarb::ops::read_workspace(config.manifest_path(), config)?; + config.tokio_handle().block_on(async { + match self.command { + WalnutVerifyCommand::Verify(_options) => { + WalnutDebugger::verify(&ws).await?; + } + } + Ok(()) + }) + } +} diff --git a/crates/torii/cli/Cargo.toml b/crates/torii/cli/Cargo.toml index e4ed7ab09e..e02f44c640 100644 --- a/crates/torii/cli/Cargo.toml +++ b/crates/torii/cli/Cargo.toml @@ -13,7 +13,7 @@ dojo-utils.workspace = true serde.workspace = true starknet.workspace = true toml.workspace = true -torii-core.workspace = true +torii-sqlite.workspace = true url.workspace = true [dev-dependencies] diff --git a/crates/torii/cli/src/args.rs b/crates/torii/cli/src/args.rs index 36a72ad20a..1d451cc0a4 100644 --- a/crates/torii/cli/src/args.rs +++ b/crates/torii/cli/src/args.rs @@ -183,7 +183,7 @@ mod test { use std::net::{IpAddr, Ipv4Addr}; use std::str::FromStr; - use torii_core::types::{Contract, ContractType}; + use torii_sqlite::types::{Contract, ContractType}; use super::*; diff --git a/crates/torii/cli/src/options.rs b/crates/torii/cli/src/options.rs index 9a824beb09..c89d3a2475 100644 --- a/crates/torii/cli/src/options.rs +++ b/crates/torii/cli/src/options.rs @@ -5,7 +5,7 @@ use anyhow::Context; use serde::ser::SerializeSeq; use serde::{Deserialize, Serialize}; use starknet::core::types::Felt; -use torii_core::types::{Contract, ContractType}; +use torii_sqlite::types::{Contract, ContractType}; pub const DEFAULT_HTTP_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST); pub const DEFAULT_HTTP_PORT: u16 = 8080; diff --git a/crates/torii/client/Cargo.toml b/crates/torii/client/Cargo.toml index d534123d07..fa5d6debf8 100644 --- a/crates/torii/client/Cargo.toml +++ b/crates/torii/client/Cargo.toml @@ -21,7 +21,7 @@ starknet-crypto.workspace = true thiserror.workspace = true tokio = { version = "1.32.0", features = [ "sync" ], default-features = false } torii-grpc = { workspace = true, features = [ "client" ] } -torii-relay = { workspace = true } +torii-relay = { workspace = true, features = [ "client" ] } url.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/torii/client/src/client/error.rs b/crates/torii/client/src/client/error.rs index 5a036e19cd..734c068707 100644 --- a/crates/torii/client/src/client/error.rs +++ b/crates/torii/client/src/client/error.rs @@ -16,7 +16,7 @@ pub enum Error { #[error(transparent)] GrpcClient(#[from] torii_grpc::client::Error), #[error(transparent)] - RelayClient(#[from] torii_relay::errors::Error), + RelayClient(#[from] torii_relay::error::Error), #[error(transparent)] Model(#[from] ModelError), #[error("Unsupported query")] diff --git a/crates/torii/core/src/lib.rs b/crates/torii/core/src/lib.rs deleted file mode 100644 index fbf9a1e14b..0000000000 --- a/crates/torii/core/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![warn(unused_crate_dependencies)] - -pub mod constants; -pub mod engine; -pub mod error; -pub mod executor; -pub mod model; -pub mod processors; -pub mod simple_broker; -pub mod sql; -pub mod types; -pub mod utils; diff --git a/crates/torii/core/src/utils.rs b/crates/torii/core/src/utils.rs deleted file mode 100644 index a0f0fac94c..0000000000 --- a/crates/torii/core/src/utils.rs +++ /dev/null @@ -1,82 +0,0 @@ -use std::time::Duration; - -use anyhow::Result; -use chrono::{DateTime, Utc}; -use futures_util::TryStreamExt; -use ipfs_api_backend_hyper::{IpfsApi, IpfsClient, TryFromUri}; -use tokio_util::bytes::Bytes; -use tracing::info; - -use crate::constants::{ - IPFS_CLIENT_MAX_RETRY, IPFS_CLIENT_PASSWORD, IPFS_CLIENT_URL, IPFS_CLIENT_USERNAME, -}; - -pub fn must_utc_datetime_from_timestamp(timestamp: u64) -> DateTime { - let naive_dt = DateTime::from_timestamp(timestamp as i64, 0) - .expect("Failed to convert timestamp to NaiveDateTime"); - naive_dt.to_utc() -} - -pub fn utc_dt_string_from_timestamp(timestamp: u64) -> String { - must_utc_datetime_from_timestamp(timestamp).to_rfc3339() -} -pub async fn fetch_content_from_ipfs(cid: &str, mut retries: u8) -> Result { - let client = IpfsClient::from_str(IPFS_CLIENT_URL)? - .with_credentials(IPFS_CLIENT_USERNAME, IPFS_CLIENT_PASSWORD); - while retries > 0 { - let response = client.cat(cid).map_ok(|chunk| chunk.to_vec()).try_concat().await; - match response { - Ok(stream) => return Ok(Bytes::from(stream)), - Err(e) => { - retries -= 1; - if retries > 0 { - info!( - error = %e, - "Fetch uri." - ); - tokio::time::sleep(Duration::from_secs(3)).await; - } - } - } - } - - Err(anyhow::anyhow!(format!( - "Failed to pull data from IPFS after {} attempts, cid: {}", - IPFS_CLIENT_MAX_RETRY, cid - ))) -} - -// tests -#[cfg(test)] -mod tests { - use chrono::{DateTime, NaiveDate, NaiveTime, Utc}; - - use super::*; - - #[test] - fn test_must_utc_datetime_from_timestamp() { - let timestamp = 1633027200; - let expected_date = NaiveDate::from_ymd_opt(2021, 9, 30).unwrap(); - let expected_time = NaiveTime::from_hms_opt(18, 40, 0).unwrap(); - let expected = - DateTime::::from_naive_utc_and_offset(expected_date.and_time(expected_time), Utc); - let out = must_utc_datetime_from_timestamp(timestamp); - assert_eq!(out, expected, "Failed to convert timestamp to DateTime"); - } - - #[test] - #[should_panic(expected = "Failed to convert timestamp to NaiveDateTime")] - fn test_must_utc_datetime_from_timestamp_incorrect_timestamp() { - let timestamp = i64::MAX as u64 + 1; - let _result = must_utc_datetime_from_timestamp(timestamp); - } - - #[test] - fn test_utc_dt_string_from_timestamp() { - let timestamp = 1633027200; - let expected = "2021-09-30T18:40:00+00:00"; - let out = utc_dt_string_from_timestamp(timestamp); - println!("{}", out); - assert_eq!(out, expected, "Failed to convert timestamp to String"); - } -} diff --git a/crates/torii/graphql/Cargo.toml b/crates/torii/graphql/Cargo.toml index 3d0d497f32..28bf116330 100644 --- a/crates/torii/graphql/Cargo.toml +++ b/crates/torii/graphql/Cargo.toml @@ -28,7 +28,8 @@ strum_macros.workspace = true thiserror.workspace = true tokio.workspace = true tokio-stream = "0.1.11" -torii-core.workspace = true +torii-sqlite.workspace = true +torii-indexer.workspace = true tracing.workspace = true url.workspace = true warp.workspace = true diff --git a/crates/torii/graphql/src/object/entity.rs b/crates/torii/graphql/src/object/entity.rs index b99935f29c..1860fec4b2 100644 --- a/crates/torii/graphql/src/object/entity.rs +++ b/crates/torii/graphql/src/object/entity.rs @@ -7,8 +7,8 @@ use dojo_types::naming::get_tag; use dojo_types::schema::Ty; use sqlx::{Pool, Sqlite}; use tokio_stream::StreamExt; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::Entity; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::Entity; use super::inputs::keys_input::keys_argument; use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; diff --git a/crates/torii/graphql/src/object/erc/token_balance.rs b/crates/torii/graphql/src/object/erc/token_balance.rs index 7a4481ff8e..09ef725ed2 100644 --- a/crates/torii/graphql/src/object/erc/token_balance.rs +++ b/crates/torii/graphql/src/object/erc/token_balance.rs @@ -5,8 +5,8 @@ use serde::Deserialize; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Pool, Row, Sqlite, SqliteConnection}; use starknet_crypto::Felt; -use torii_core::constants::TOKEN_BALANCE_TABLE; -use torii_core::sql::utils::felt_to_sql_string; +use torii_sqlite::constants::TOKEN_BALANCE_TABLE; +use torii_sqlite::utils::felt_to_sql_string; use tracing::warn; use super::erc_token::{Erc20Token, ErcTokenType}; diff --git a/crates/torii/graphql/src/object/erc/token_transfer.rs b/crates/torii/graphql/src/object/erc/token_transfer.rs index 31bf30776d..fe5a256571 100644 --- a/crates/torii/graphql/src/object/erc/token_transfer.rs +++ b/crates/torii/graphql/src/object/erc/token_transfer.rs @@ -5,9 +5,9 @@ use serde::Deserialize; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Pool, Row, Sqlite, SqliteConnection}; use starknet_crypto::Felt; -use torii_core::constants::TOKEN_TRANSFER_TABLE; -use torii_core::engine::get_transaction_hash_from_event_id; -use torii_core::sql::utils::felt_to_sql_string; +use torii_indexer::engine::get_transaction_hash_from_event_id; +use torii_sqlite::constants::TOKEN_TRANSFER_TABLE; +use torii_sqlite::utils::felt_to_sql_string; use tracing::warn; use super::erc_token::{Erc20Token, ErcTokenType}; diff --git a/crates/torii/graphql/src/object/event.rs b/crates/torii/graphql/src/object/event.rs index e553f4be32..6c9cdd2032 100644 --- a/crates/torii/graphql/src/object/event.rs +++ b/crates/torii/graphql/src/object/event.rs @@ -3,9 +3,9 @@ use async_graphql::dynamic::{ }; use async_graphql::{Name, Result, Value}; use tokio_stream::{Stream, StreamExt}; -use torii_core::constants::SQL_FELT_DELIMITER; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::Event; +use torii_sqlite::constants::SQL_FELT_DELIMITER; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::Event; use super::inputs::keys_input::{keys_argument, parse_keys_argument}; use super::{resolve_many, BasicObject, ResolvableObject, TypeMapping}; diff --git a/crates/torii/graphql/src/object/event_message.rs b/crates/torii/graphql/src/object/event_message.rs index 155db80f95..d0597b643a 100644 --- a/crates/torii/graphql/src/object/event_message.rs +++ b/crates/torii/graphql/src/object/event_message.rs @@ -7,8 +7,8 @@ use dojo_types::naming::get_tag; use dojo_types::schema::Ty; use sqlx::{Pool, Sqlite}; use tokio_stream::StreamExt; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::EventMessage; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::EventMessage; use super::inputs::keys_input::keys_argument; use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; diff --git a/crates/torii/graphql/src/object/model.rs b/crates/torii/graphql/src/object/model.rs index c12cf070d4..281187bca4 100644 --- a/crates/torii/graphql/src/object/model.rs +++ b/crates/torii/graphql/src/object/model.rs @@ -4,8 +4,8 @@ use async_graphql::dynamic::{ }; use async_graphql::{Name, Value}; use tokio_stream::StreamExt; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::Model; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::Model; use super::{resolve_many, BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ diff --git a/crates/torii/graphql/src/query/data.rs b/crates/torii/graphql/src/query/data.rs index c01895730b..8a165e6cee 100644 --- a/crates/torii/graphql/src/query/data.rs +++ b/crates/torii/graphql/src/query/data.rs @@ -1,7 +1,7 @@ use async_graphql::connection::PageInfo; use sqlx::sqlite::SqliteRow; use sqlx::{Result, Row, SqliteConnection}; -use torii_core::constants::WORLD_CONTRACT_TYPE; +use torii_sqlite::constants::WORLD_CONTRACT_TYPE; use super::filter::{Filter, FilterValue}; use super::order::{CursorDirection, Direction, Order}; diff --git a/crates/torii/graphql/src/query/mod.rs b/crates/torii/graphql/src/query/mod.rs index 69c8d4113b..cca7ee8816 100644 --- a/crates/torii/graphql/src/query/mod.rs +++ b/crates/torii/graphql/src/query/mod.rs @@ -10,7 +10,7 @@ use dojo_types::schema::Ty; use regex::Regex; use sqlx::sqlite::SqliteRow; use sqlx::Row; -use torii_core::constants::SQL_FELT_DELIMITER; +use torii_sqlite::constants::SQL_FELT_DELIMITER; use crate::constants::{ BOOLEAN_TRUE, ENTITY_ID_COLUMN, EVENT_MESSAGE_ID_COLUMN, INTERNAL_ENTITY_ID_KEY, @@ -172,6 +172,11 @@ pub fn value_mapping_from_row( TypeData::List(_) => { let value = fetch_value(row, &column_name, "String", is_internal, snake_case)?; if let Value::String(json_str) = value { + if json_str.is_empty() { + value_mapping.insert(Name::new(field_name), Value::List(vec![])); + continue; + } + let mut array_value: Value = serde_json::from_str(&json_str).map_err(|e| { sqlx::Error::Protocol(format!("JSON parse error: {}", e)) diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 8e1aba65c3..adee496350 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -2,7 +2,7 @@ use anyhow::Result; use async_graphql::dynamic::{Object, Scalar, Schema, Subscription, Union}; use dojo_types::schema::Ty; use sqlx::SqlitePool; -use torii_core::types::Model; +use torii_sqlite::types::Model; use super::object::connection::page_info::PageInfoObject; use super::object::entity::EntityObject; diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index b9cfab8a93..552e10c277 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -8,10 +8,10 @@ mod tests { use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::JsonRpcClient; use tokio::sync::broadcast; - use torii_core::executor::Executor; - use torii_core::sql::cache::ModelCache; - use torii_core::sql::Sql; - use torii_core::types::{Contract, ContractType}; + use torii_sqlite::cache::ModelCache; + use torii_sqlite::executor::Executor; + use torii_sqlite::types::{Contract, ContractType}; + use torii_sqlite::Sql; use url::Url; use crate::schema::build_schema; diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index 06d7afa747..92e23ad3e8 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -27,11 +27,11 @@ use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider}; use tokio::sync::broadcast; use tokio_stream::StreamExt; -use torii_core::engine::{Engine, EngineConfig, Processors}; -use torii_core::executor::Executor; -use torii_core::sql::cache::ModelCache; -use torii_core::sql::Sql; -use torii_core::types::{Contract, ContractType}; +use torii_indexer::engine::{Engine, EngineConfig, Processors}; +use torii_sqlite::cache::ModelCache; +use torii_sqlite::executor::Executor; +use torii_sqlite::types::{Contract, ContractType}; +use torii_sqlite::Sql; mod entities_test; mod events_test; diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index ca332a4cf6..50cfb23bb4 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -17,11 +17,11 @@ mod tests { use starknet::providers::JsonRpcClient; use starknet_crypto::{poseidon_hash_many, Felt}; use tokio::sync::{broadcast, mpsc}; - use torii_core::executor::Executor; - use torii_core::sql::cache::ModelCache; - use torii_core::sql::utils::felts_to_sql_string; - use torii_core::sql::Sql; - use torii_core::types::{Contract, ContractType}; + use torii_sqlite::cache::ModelCache; + use torii_sqlite::executor::Executor; + use torii_sqlite::types::{Contract, ContractType}; + use torii_sqlite::utils::felts_to_sql_string; + use torii_sqlite::Sql; use url::Url; use crate::tests::{model_fixtures, run_graphql_subscription}; diff --git a/crates/torii/grpc/Cargo.toml b/crates/torii/grpc/Cargo.toml index e585af72f2..fc88ccd8ee 100644 --- a/crates/torii/grpc/Cargo.toml +++ b/crates/torii/grpc/Cargo.toml @@ -14,7 +14,7 @@ rayon.workspace = true starknet.workspace = true starknet-crypto.workspace = true thiserror.workspace = true -torii-core = { path = "../core", optional = true } +torii-sqlite = { path = "../sqlite", optional = true } crypto-bigint.workspace = true serde.workspace = true @@ -38,6 +38,7 @@ katana-runner.workspace = true scarb.workspace = true tempfile.workspace = true sozo-scarbext.workspace = true +torii-indexer.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] tonic-web-wasm-client.workspace = true @@ -61,4 +62,4 @@ wasm-tonic-build.workspace = true [features] client = [ ] -server = [ "dep:torii-core" ] # this feature can't be build on wasm32 +server = [ "dep:torii-sqlite" ] # this feature can't be build on wasm32 diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 297c9c9656..c64768ff62 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -42,10 +42,10 @@ use tonic::codec::CompressionEncoding; use tonic::transport::Server; use tonic::{Request, Response, Status}; use tonic_web::GrpcWebLayer; -use torii_core::error::{Error, ParseError, QueryError}; -use torii_core::model::{fetch_entities, map_row_to_ty}; -use torii_core::sql::cache::ModelCache; -use torii_core::types::{Token, TokenBalance}; +use torii_sqlite::cache::ModelCache; +use torii_sqlite::error::{Error, ParseError, QueryError}; +use torii_sqlite::model::{fetch_entities, map_row_to_ty}; +use torii_sqlite::types::{Token, TokenBalance}; use tower_http::cors::{AllowOrigin, CorsLayer}; use self::subscriptions::entity::EntityManager; diff --git a/crates/torii/grpc/src/server/subscriptions/entity.rs b/crates/torii/grpc/src/server/subscriptions/entity.rs index ceed5ac823..d014f3b4dd 100644 --- a/crates/torii/grpc/src/server/subscriptions/entity.rs +++ b/crates/torii/grpc/src/server/subscriptions/entity.rs @@ -13,10 +13,10 @@ use tokio::sync::mpsc::{ channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender, }; use tokio::sync::RwLock; -use torii_core::constants::SQL_FELT_DELIMITER; -use torii_core::error::{Error, ParseError}; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::OptimisticEntity; +use torii_sqlite::constants::SQL_FELT_DELIMITER; +use torii_sqlite::error::{Error, ParseError}; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::OptimisticEntity; use tracing::{error, trace}; use super::match_entity_keys; diff --git a/crates/torii/grpc/src/server/subscriptions/error.rs b/crates/torii/grpc/src/server/subscriptions/error.rs index c901113f98..70c9c3fe38 100644 --- a/crates/torii/grpc/src/server/subscriptions/error.rs +++ b/crates/torii/grpc/src/server/subscriptions/error.rs @@ -1,5 +1,5 @@ use starknet::providers::ProviderError; -use torii_core::error::ParseError; +use torii_sqlite::error::ParseError; #[derive(Debug, thiserror::Error)] pub enum SubscriptionError { diff --git a/crates/torii/grpc/src/server/subscriptions/event.rs b/crates/torii/grpc/src/server/subscriptions/event.rs index 65a7cade52..c615b1d732 100644 --- a/crates/torii/grpc/src/server/subscriptions/event.rs +++ b/crates/torii/grpc/src/server/subscriptions/event.rs @@ -13,10 +13,10 @@ use tokio::sync::mpsc::{ channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender, }; use tokio::sync::RwLock; -use torii_core::constants::SQL_FELT_DELIMITER; -use torii_core::error::{Error, ParseError}; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::Event; +use torii_sqlite::constants::SQL_FELT_DELIMITER; +use torii_sqlite::error::{Error, ParseError}; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::Event; use tracing::{error, trace}; use super::match_keys; diff --git a/crates/torii/grpc/src/server/subscriptions/event_message.rs b/crates/torii/grpc/src/server/subscriptions/event_message.rs index db1141cee8..fd5419d095 100644 --- a/crates/torii/grpc/src/server/subscriptions/event_message.rs +++ b/crates/torii/grpc/src/server/subscriptions/event_message.rs @@ -13,10 +13,10 @@ use tokio::sync::mpsc::{ channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender, }; use tokio::sync::RwLock; -use torii_core::constants::SQL_FELT_DELIMITER; -use torii_core::error::{Error, ParseError}; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::OptimisticEventMessage; +use torii_sqlite::constants::SQL_FELT_DELIMITER; +use torii_sqlite::error::{Error, ParseError}; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::OptimisticEventMessage; use tracing::{error, trace}; use super::match_entity_keys; diff --git a/crates/torii/grpc/src/server/subscriptions/indexer.rs b/crates/torii/grpc/src/server/subscriptions/indexer.rs index 11271ce970..b342ffbc57 100644 --- a/crates/torii/grpc/src/server/subscriptions/indexer.rs +++ b/crates/torii/grpc/src/server/subscriptions/indexer.rs @@ -13,9 +13,9 @@ use tokio::sync::mpsc::{ channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender, }; use tokio::sync::RwLock; -use torii_core::error::{Error, ParseError}; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::ContractCursor as ContractUpdated; +use torii_sqlite::error::{Error, ParseError}; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::ContractCursor as ContractUpdated; use tracing::{error, trace}; use crate::proto; diff --git a/crates/torii/grpc/src/server/subscriptions/model_diff.rs b/crates/torii/grpc/src/server/subscriptions/model_diff.rs index ba9ef6b49c..7ad3eaab3f 100644 --- a/crates/torii/grpc/src/server/subscriptions/model_diff.rs +++ b/crates/torii/grpc/src/server/subscriptions/model_diff.rs @@ -16,7 +16,7 @@ use starknet::providers::Provider; use starknet_crypto::poseidon_hash_many; use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::RwLock; -use torii_core::error::Error; +use torii_sqlite::error::Error; use tracing::{debug, error, trace}; use super::error::SubscriptionError; diff --git a/crates/torii/grpc/src/server/subscriptions/token_balance.rs b/crates/torii/grpc/src/server/subscriptions/token_balance.rs index cf932894d4..e883d6fb0b 100644 --- a/crates/torii/grpc/src/server/subscriptions/token_balance.rs +++ b/crates/torii/grpc/src/server/subscriptions/token_balance.rs @@ -12,9 +12,9 @@ use tokio::sync::mpsc::{ channel, unbounded_channel, Receiver, Sender, UnboundedReceiver, UnboundedSender, }; use tokio::sync::RwLock; -use torii_core::error::{Error, ParseError}; -use torii_core::simple_broker::SimpleBroker; -use torii_core::types::TokenBalance; +use torii_sqlite::error::{Error, ParseError}; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::TokenBalance; use tracing::{error, trace}; use crate::proto; diff --git a/crates/torii/grpc/src/server/tests/entities_test.rs b/crates/torii/grpc/src/server/tests/entities_test.rs index 9791c09455..37f7674cec 100644 --- a/crates/torii/grpc/src/server/tests/entities_test.rs +++ b/crates/torii/grpc/src/server/tests/entities_test.rs @@ -22,11 +22,11 @@ use starknet::providers::{JsonRpcClient, Provider}; use starknet_crypto::poseidon_hash_many; use tempfile::NamedTempFile; use tokio::sync::broadcast; -use torii_core::engine::{Engine, EngineConfig, Processors}; -use torii_core::executor::Executor; -use torii_core::sql::cache::ModelCache; -use torii_core::sql::Sql; -use torii_core::types::{Contract, ContractType}; +use torii_indexer::engine::{Engine, EngineConfig, Processors}; +use torii_sqlite::cache::ModelCache; +use torii_sqlite::executor::Executor; +use torii_sqlite::types::{Contract, ContractType}; +use torii_sqlite::Sql; use crate::proto::types::KeysClause; use crate::server::DojoWorld; diff --git a/crates/torii/core/Cargo.toml b/crates/torii/indexer/Cargo.toml similarity index 92% rename from crates/torii/core/Cargo.toml rename to crates/torii/indexer/Cargo.toml index b9c90fa17e..2a2e1ad22c 100644 --- a/crates/torii/core/Cargo.toml +++ b/crates/torii/indexer/Cargo.toml @@ -1,8 +1,8 @@ [package] -description = "Torii core implementation." +description = "Torii indexer implementation." edition.workspace = true license-file.workspace = true -name = "torii-core" +name = "torii-indexer" repository.workspace = true version.workspace = true @@ -29,7 +29,6 @@ reqwest.workspace = true serde.workspace = true serde_json.workspace = true slab = "0.4.2" -sqlx.workspace = true starknet-crypto.workspace = true starknet.workspace = true thiserror.workspace = true @@ -38,6 +37,7 @@ tokio = { version = "1.32.0", features = [ "macros", "sync" ], default-features ipfs-api-backend-hyper.workspace = true tokio-util.workspace = true tracing.workspace = true +torii-sqlite.workspace = true [dev-dependencies] dojo-test-utils.workspace = true @@ -46,3 +46,4 @@ katana-runner.workspace = true scarb.workspace = true sozo-scarbext.workspace = true tempfile.workspace = true +sqlx.workspace = true diff --git a/crates/torii/indexer/src/constants.rs b/crates/torii/indexer/src/constants.rs new file mode 100644 index 0000000000..9bdbfc9a40 --- /dev/null +++ b/crates/torii/indexer/src/constants.rs @@ -0,0 +1 @@ +pub(crate) const LOG_TARGET: &str = "torii::engine"; diff --git a/crates/torii/core/src/engine.rs b/crates/torii/indexer/src/engine.rs similarity index 99% rename from crates/torii/core/src/engine.rs rename to crates/torii/indexer/src/engine.rs index 46d84cc57f..1210c5ec9d 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/indexer/src/engine.rs @@ -23,6 +23,8 @@ use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::Semaphore; use tokio::task::JoinSet; use tokio::time::{sleep, Instant}; +use torii_sqlite::types::{Contract, ContractType}; +use torii_sqlite::{Cursors, Sql}; use tracing::{debug, error, info, trace, warn}; use crate::constants::LOG_TARGET; @@ -44,8 +46,6 @@ use crate::processors::upgrade_model::UpgradeModelProcessor; use crate::processors::{ BlockProcessor, EventProcessor, EventProcessorConfig, TransactionProcessor, }; -use crate::sql::{Cursors, Sql}; -use crate::types::{Contract, ContractType}; type EventProcessorMap

= HashMap>>>; diff --git a/crates/torii/indexer/src/lib.rs b/crates/torii/indexer/src/lib.rs new file mode 100644 index 0000000000..7191c5480f --- /dev/null +++ b/crates/torii/indexer/src/lib.rs @@ -0,0 +1,10 @@ +mod constants; + +#[cfg(test)] +#[path = "test.rs"] +mod test; + +pub mod engine; +pub mod processors; + +pub use engine::Engine; diff --git a/crates/torii/core/src/processors/erc20_legacy_transfer.rs b/crates/torii/indexer/src/processors/erc20_legacy_transfer.rs similarity index 94% rename from crates/torii/core/src/processors/erc20_legacy_transfer.rs rename to crates/torii/indexer/src/processors/erc20_legacy_transfer.rs index 903836c65d..3b207d466b 100644 --- a/crates/torii/core/src/processors/erc20_legacy_transfer.rs +++ b/crates/torii/indexer/src/processors/erc20_legacy_transfer.rs @@ -4,12 +4,12 @@ use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome}; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{Event, U256}; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_legacy_transfer"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::erc20_legacy_transfer"; #[derive(Default, Debug)] pub struct Erc20LegacyTransferProcessor; diff --git a/crates/torii/core/src/processors/erc20_transfer.rs b/crates/torii/indexer/src/processors/erc20_transfer.rs similarity index 94% rename from crates/torii/core/src/processors/erc20_transfer.rs rename to crates/torii/indexer/src/processors/erc20_transfer.rs index 014e131a3b..a0643abd41 100644 --- a/crates/torii/core/src/processors/erc20_transfer.rs +++ b/crates/torii/indexer/src/processors/erc20_transfer.rs @@ -4,12 +4,12 @@ use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome}; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{Event, U256}; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc20_transfer"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::erc20_transfer"; #[derive(Default, Debug)] pub struct Erc20TransferProcessor; diff --git a/crates/torii/core/src/processors/erc721_legacy_transfer.rs b/crates/torii/indexer/src/processors/erc721_legacy_transfer.rs similarity index 94% rename from crates/torii/core/src/processors/erc721_legacy_transfer.rs rename to crates/torii/indexer/src/processors/erc721_legacy_transfer.rs index 34106c5eb5..df6b2a88de 100644 --- a/crates/torii/core/src/processors/erc721_legacy_transfer.rs +++ b/crates/torii/indexer/src/processors/erc721_legacy_transfer.rs @@ -4,12 +4,12 @@ use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome}; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{Event, U256}; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc721_legacy_transfer"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::erc721_legacy_transfer"; #[derive(Default, Debug)] pub struct Erc721LegacyTransferProcessor; diff --git a/crates/torii/core/src/processors/erc721_transfer.rs b/crates/torii/indexer/src/processors/erc721_transfer.rs similarity index 94% rename from crates/torii/core/src/processors/erc721_transfer.rs rename to crates/torii/indexer/src/processors/erc721_transfer.rs index 47205d2ed9..faf124360b 100644 --- a/crates/torii/core/src/processors/erc721_transfer.rs +++ b/crates/torii/indexer/src/processors/erc721_transfer.rs @@ -4,12 +4,12 @@ use cainome::cairo_serde::{CairoSerde, U256 as U256Cainome}; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{Event, U256}; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::erc721_transfer"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::erc721_transfer"; #[derive(Default, Debug)] pub struct Erc721TransferProcessor; diff --git a/crates/torii/core/src/processors/event_message.rs b/crates/torii/indexer/src/processors/event_message.rs similarity index 95% rename from crates/torii/core/src/processors/event_message.rs rename to crates/torii/indexer/src/processors/event_message.rs index 9d36368d3b..4495665bed 100644 --- a/crates/torii/core/src/processors/event_message.rs +++ b/crates/torii/indexer/src/processors/event_message.rs @@ -5,12 +5,12 @@ use dojo_world::contracts::naming::get_tag; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{Event, Felt}; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::info; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::event_message"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::event_message"; #[derive(Default, Debug)] pub struct EventMessageProcessor; diff --git a/crates/torii/core/src/processors/metadata_update.rs b/crates/torii/indexer/src/processors/metadata_update.rs similarity index 94% rename from crates/torii/core/src/processors/metadata_update.rs rename to crates/torii/indexer/src/processors/metadata_update.rs index f9cff569f8..ec2c4b493d 100644 --- a/crates/torii/core/src/processors/metadata_update.rs +++ b/crates/torii/indexer/src/processors/metadata_update.rs @@ -9,14 +9,14 @@ use dojo_world::contracts::world::WorldContractReader; use dojo_world::uri::Uri; use starknet::core::types::{Event, Felt}; use starknet::providers::Provider; +use torii_sqlite::constants::IPFS_CLIENT_MAX_RETRY; +use torii_sqlite::utils::fetch_content_from_ipfs; +use torii_sqlite::Sql; use tracing::{error, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::constants::IPFS_CLIENT_MAX_RETRY; -use crate::sql::Sql; -use crate::utils::fetch_content_from_ipfs; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::metadata_update"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::metadata_update"; #[derive(Default, Debug)] pub struct MetadataUpdateProcessor; diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/indexer/src/processors/mod.rs similarity index 96% rename from crates/torii/core/src/processors/mod.rs rename to crates/torii/indexer/src/processors/mod.rs index 4d4c5c637d..abe358b6e7 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/indexer/src/processors/mod.rs @@ -5,8 +5,7 @@ use async_trait::async_trait; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{Event, Felt, Transaction}; use starknet::providers::Provider; - -use crate::sql::Sql; +use torii_sqlite::Sql; pub mod erc20_legacy_transfer; pub mod erc20_transfer; @@ -25,9 +24,6 @@ pub mod store_update_record; pub mod upgrade_event; pub mod upgrade_model; -const MODEL_INDEX: usize = 0; -const ENTITY_ID_INDEX: usize = 1; - #[derive(Clone, Debug, Default)] pub struct EventProcessorConfig { pub historical_events: HashSet, diff --git a/crates/torii/core/src/processors/raw_event.rs b/crates/torii/indexer/src/processors/raw_event.rs similarity index 97% rename from crates/torii/core/src/processors/raw_event.rs rename to crates/torii/indexer/src/processors/raw_event.rs index ff496ef74b..c30a918fa2 100644 --- a/crates/torii/core/src/processors/raw_event.rs +++ b/crates/torii/indexer/src/processors/raw_event.rs @@ -3,9 +3,9 @@ use async_trait::async_trait; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::Sql; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; #[derive(Default, Debug)] pub struct RawEventProcessor; diff --git a/crates/torii/core/src/processors/register_event.rs b/crates/torii/indexer/src/processors/register_event.rs similarity index 96% rename from crates/torii/core/src/processors/register_event.rs rename to crates/torii/indexer/src/processors/register_event.rs index be9919200c..e9c94f296a 100644 --- a/crates/torii/core/src/processors/register_event.rs +++ b/crates/torii/indexer/src/processors/register_event.rs @@ -5,12 +5,12 @@ use dojo_world::contracts::model::ModelReader; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::register_event"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::register_event"; #[derive(Default, Debug)] pub struct RegisterEventProcessor; diff --git a/crates/torii/core/src/processors/register_model.rs b/crates/torii/indexer/src/processors/register_model.rs similarity index 96% rename from crates/torii/core/src/processors/register_model.rs rename to crates/torii/indexer/src/processors/register_model.rs index ed0f710f2e..d630feff88 100644 --- a/crates/torii/core/src/processors/register_model.rs +++ b/crates/torii/indexer/src/processors/register_model.rs @@ -5,12 +5,12 @@ use dojo_world::contracts::model::ModelReader; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::register_model"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::register_model"; #[derive(Default, Debug)] pub struct RegisterModelProcessor; diff --git a/crates/torii/core/src/processors/store_del_record.rs b/crates/torii/indexer/src/processors/store_del_record.rs similarity index 95% rename from crates/torii/core/src/processors/store_del_record.rs rename to crates/torii/indexer/src/processors/store_del_record.rs index f6b34db24a..e109f069d7 100644 --- a/crates/torii/core/src/processors/store_del_record.rs +++ b/crates/torii/indexer/src/processors/store_del_record.rs @@ -4,12 +4,12 @@ use dojo_world::contracts::abigen::world::Event as WorldEvent; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_del_record"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::store_del_record"; #[derive(Default, Debug)] pub struct StoreDelRecordProcessor; diff --git a/crates/torii/core/src/processors/store_set_record.rs b/crates/torii/indexer/src/processors/store_set_record.rs similarity index 94% rename from crates/torii/core/src/processors/store_set_record.rs rename to crates/torii/indexer/src/processors/store_set_record.rs index cc602fd868..c564cb3968 100644 --- a/crates/torii/core/src/processors/store_set_record.rs +++ b/crates/torii/indexer/src/processors/store_set_record.rs @@ -4,13 +4,13 @@ use dojo_world::contracts::abigen::world::Event as WorldEvent; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::utils::felts_to_sql_string; +use torii_sqlite::Sql; use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::utils::felts_to_sql_string; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_set_record"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::store_set_record"; #[derive(Default, Debug)] pub struct StoreSetRecordProcessor; diff --git a/crates/torii/core/src/processors/store_transaction.rs b/crates/torii/indexer/src/processors/store_transaction.rs similarity index 97% rename from crates/torii/core/src/processors/store_transaction.rs rename to crates/torii/indexer/src/processors/store_transaction.rs index 101fb88093..3eb52725b8 100644 --- a/crates/torii/core/src/processors/store_transaction.rs +++ b/crates/torii/indexer/src/processors/store_transaction.rs @@ -2,9 +2,9 @@ use anyhow::{Error, Ok, Result}; use async_trait::async_trait; use starknet::core::types::{Felt, Transaction}; use starknet::providers::Provider; +use torii_sqlite::Sql; use super::TransactionProcessor; -use crate::sql::Sql; #[derive(Default, Debug)] pub struct StoreTransactionProcessor; diff --git a/crates/torii/core/src/processors/store_update_member.rs b/crates/torii/indexer/src/processors/store_update_member.rs similarity index 56% rename from crates/torii/core/src/processors/store_update_member.rs rename to crates/torii/indexer/src/processors/store_update_member.rs index 2dd28735ff..776d61d9cb 100644 --- a/crates/torii/core/src/processors/store_update_member.rs +++ b/crates/torii/indexer/src/processors/store_update_member.rs @@ -1,21 +1,17 @@ use anyhow::{Context, Error, Result}; use async_trait::async_trait; use dojo_types::schema::{Struct, Ty}; -use dojo_world::contracts::naming; +use dojo_world::contracts::abigen::world::Event as WorldEvent; use dojo_world::contracts::world::WorldContractReader; -use num_traits::ToPrimitive; use starknet::core::types::Event; use starknet::core::utils::get_selector_from_name; use starknet::providers::Provider; -use tracing::{info, warn}; +use torii_sqlite::Sql; +use tracing::info; use super::{EventProcessor, EventProcessorConfig}; -use crate::processors::{ENTITY_ID_INDEX, MODEL_INDEX}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_update_member"; - -const MEMBER_INDEX: usize = 2; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::store_update_member"; #[derive(Default, Debug)] pub struct StoreUpdateMemberProcessor; @@ -29,16 +25,7 @@ where "StoreUpdateMember".to_string() } - fn validate(&self, event: &Event) -> bool { - if event.keys.len() > 1 { - info!( - target: LOG_TARGET, - event_key = %>::event_key(self), - invalid_keys = %>::event_keys_as_string(self, event), - "Invalid event keys." - ); - return false; - } + fn validate(&self, _event: &Event) -> bool { true } @@ -52,13 +39,27 @@ where event: &Event, _config: &EventProcessorConfig, ) -> Result<(), Error> { - let model_id = event.data[MODEL_INDEX]; - let entity_id = event.data[ENTITY_ID_INDEX]; - let member_selector = event.data[MEMBER_INDEX]; + // Torii version is coupled to the world version, so we can expect the event to be well + // formed. + let event = match WorldEvent::try_from(event).unwrap_or_else(|_| { + panic!( + "Expected {} event to be well formed.", + >::event_key(self) + ) + }) { + WorldEvent::StoreUpdateMember(e) => e, + _ => { + unreachable!() + } + }; + + let model_selector = event.selector; + let entity_id = event.entity_id; + let member_selector = event.member_selector; // If the model does not exist, silently ignore it. // This can happen if only specific namespaces are indexed. - let model = match db.model(model_id).await { + let model = match db.model(model_selector).await { Ok(m) => m, Err(e) => { if e.to_string().contains("no rows") { @@ -90,30 +91,12 @@ where "Store update member.", ); - let values_start = MEMBER_INDEX + 1; - let values_end: usize = - values_start + event.data[values_start].to_usize().context("invalid usize")?; - - // Skip the length to only get the values as they will be deserialized. - let mut values = event.data[values_start + 1..=values_end].to_vec(); - - let tag = naming::get_tag(&model.namespace, &model.name); - - if !db.does_entity_exist(tag.clone(), entity_id).await? { - warn!( - target: LOG_TARGET, - tag, - entity_id = format!("{:#x}", entity_id), - "Entity not found, must be set before updating a member.", - ); - - return Ok(()); - } - + let mut values = event.values.to_vec(); member.ty.deserialize(&mut values)?; - let wrapped_ty = Ty::Struct(Struct { name: schema.name(), children: vec![member] }); - db.set_entity(wrapped_ty, event_id, block_timestamp, entity_id, model_id, None).await?; + let wrapped_ty = Ty::Struct(Struct { name: schema.name(), children: vec![member] }); + db.set_entity(wrapped_ty, event_id, block_timestamp, entity_id, model_selector, None) + .await?; Ok(()) } } diff --git a/crates/torii/core/src/processors/store_update_record.rs b/crates/torii/indexer/src/processors/store_update_record.rs similarity index 96% rename from crates/torii/core/src/processors/store_update_record.rs rename to crates/torii/indexer/src/processors/store_update_record.rs index f94920e411..b92344849d 100644 --- a/crates/torii/core/src/processors/store_update_record.rs +++ b/crates/torii/indexer/src/processors/store_update_record.rs @@ -5,12 +5,12 @@ use dojo_world::contracts::abigen::world::Event as WorldEvent; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_update_record"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::store_update_record"; #[derive(Default, Debug)] pub struct StoreUpdateRecordProcessor; diff --git a/crates/torii/core/src/processors/upgrade_event.rs b/crates/torii/indexer/src/processors/upgrade_event.rs similarity index 97% rename from crates/torii/core/src/processors/upgrade_event.rs rename to crates/torii/indexer/src/processors/upgrade_event.rs index da329501af..ba7966d63b 100644 --- a/crates/torii/core/src/processors/upgrade_event.rs +++ b/crates/torii/indexer/src/processors/upgrade_event.rs @@ -5,12 +5,12 @@ use dojo_world::contracts::model::ModelReader; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::upgrade_event"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::upgrade_event"; #[derive(Default, Debug)] pub struct UpgradeEventProcessor; diff --git a/crates/torii/core/src/processors/upgrade_model.rs b/crates/torii/indexer/src/processors/upgrade_model.rs similarity index 97% rename from crates/torii/core/src/processors/upgrade_model.rs rename to crates/torii/indexer/src/processors/upgrade_model.rs index 0b7e36820d..40717df30d 100644 --- a/crates/torii/core/src/processors/upgrade_model.rs +++ b/crates/torii/indexer/src/processors/upgrade_model.rs @@ -5,12 +5,12 @@ use dojo_world::contracts::model::ModelReader; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::Event; use starknet::providers::Provider; +use torii_sqlite::Sql; use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -use crate::sql::Sql; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::upgrade_model"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::upgrade_model"; #[derive(Default, Debug)] pub struct UpgradeModelProcessor; diff --git a/crates/torii/core/src/sql/test.rs b/crates/torii/indexer/src/test.rs similarity index 77% rename from crates/torii/core/src/sql/test.rs rename to crates/torii/indexer/src/test.rs index 7d79f3ba4a..ccfc77608b 100644 --- a/crates/torii/core/src/sql/test.rs +++ b/crates/torii/indexer/src/test.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; -use cainome::cairo_serde::ContractAddress; +use cainome::cairo_serde::{ByteArray, CairoSerde, ContractAddress}; use dojo_test_utils::compiler::CompilerTestSetup; use dojo_test_utils::migration::copy_spawn_and_move_db; use dojo_utils::{TransactionExt, TransactionWaiter, TxnConfig}; @@ -20,12 +20,12 @@ use starknet::providers::{JsonRpcClient, Provider}; use starknet_crypto::poseidon_hash_many; use tempfile::NamedTempFile; use tokio::sync::broadcast; +use torii_sqlite::cache::ModelCache; +use torii_sqlite::executor::Executor; +use torii_sqlite::types::{Contract, ContractType}; +use torii_sqlite::Sql; use crate::engine::{Engine, EngineConfig, Processors}; -use crate::executor::Executor; -use crate::sql::cache::ModelCache; -use crate::sql::Sql; -use crate::types::{Contract, ContractType}; pub async fn bootstrap_engine

( world: WorldContractReader

, @@ -390,6 +390,115 @@ async fn test_update_with_set_record(sequencer: &RunnerCtx) { let _ = bootstrap_engine(world_reader, db.clone(), Arc::clone(&provider)).await.unwrap(); } +#[ignore = "This test is being flaky and need to find why. Sometimes it fails, sometimes it passes."] +#[tokio::test(flavor = "multi_thread")] +#[katana_runner::test(accounts = 10, db_dir = copy_spawn_and_move_db().as_str())] +async fn test_load_from_remote_update(sequencer: &RunnerCtx) { + let setup = CompilerTestSetup::from_examples("../../dojo/core", "../../../examples/"); + let config = setup.build_test_config("spawn-and-move", Profile::DEV); + + let ws = scarb::ops::read_workspace(config.manifest_path(), &config).unwrap(); + + let account = sequencer.account(0); + let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(sequencer.url()))); + + let world_local = ws.load_world_local().unwrap(); + let world_address = world_local.deterministic_world_address().unwrap(); + let actions_address = world_local + .get_contract_address_local(compute_selector_from_names("ns", "actions")) + .unwrap(); + + let world = WorldContract::new(world_address, &account); + + let res = world + .grant_writer(&compute_bytearray_hash("ns"), &ContractAddress(actions_address)) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + TransactionWaiter::new(res.transaction_hash, &provider).await.unwrap(); + + // spawn + let res = account + .execute_v1(vec![Call { + to: actions_address, + selector: get_selector_from_name("spawn").unwrap(), + calldata: vec![], + }]) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + TransactionWaiter::new(res.transaction_hash, &provider).await.unwrap(); + + // Set player config. + let res = account + .execute_v1(vec![Call { + to: actions_address, + selector: get_selector_from_name("set_player_config").unwrap(), + // Empty ByteArray. + calldata: vec![Felt::ZERO, Felt::ZERO, Felt::ZERO], + }]) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + TransactionWaiter::new(res.transaction_hash, &provider).await.unwrap(); + + let name = ByteArray::from_string("mimi").unwrap(); + let res = account + .execute_v1(vec![Call { + to: actions_address, + selector: get_selector_from_name("update_player_config_name").unwrap(), + calldata: ByteArray::cairo_serialize(&name), + }]) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + TransactionWaiter::new(res.transaction_hash, &provider).await.unwrap(); + + let world_reader = WorldContractReader::new(world_address, Arc::clone(&provider)); + + let tempfile = NamedTempFile::new().unwrap(); + let path = tempfile.path().to_string_lossy(); + let options = SqliteConnectOptions::from_str(&path).unwrap().create_if_missing(true); + let pool = SqlitePoolOptions::new().connect_with(options).await.unwrap(); + sqlx::migrate!("../migrations").run(&pool).await.unwrap(); + + let (shutdown_tx, _) = broadcast::channel(1); + let (mut executor, sender) = + Executor::new(pool.clone(), shutdown_tx.clone(), Arc::clone(&provider), 100).await.unwrap(); + tokio::spawn(async move { + executor.run().await.unwrap(); + }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); + let db = Sql::new( + pool.clone(), + sender.clone(), + &[Contract { address: world_reader.address, r#type: ContractType::WORLD }], + model_cache.clone(), + ) + .await + .unwrap(); + + let _ = bootstrap_engine(world_reader, db.clone(), Arc::clone(&provider)).await.unwrap(); + + let name: String = sqlx::query_scalar( + format!( + "SELECT name FROM [ns-PlayerConfig] WHERE internal_id = '{:#x}'", + poseidon_hash_many(&[account.address()]) + ) + .as_str(), + ) + .fetch_one(&pool) + .await + .unwrap(); + + assert_eq!(name, "mimi"); +} + /// Count the number of rows in a table. /// /// # Arguments diff --git a/crates/torii/libp2p/Cargo.toml b/crates/torii/libp2p/Cargo.toml index 05cf945115..83f75e1f83 100644 --- a/crates/torii/libp2p/Cargo.toml +++ b/crates/torii/libp2p/Cargo.toml @@ -5,43 +5,49 @@ name = "torii-relay" repository.workspace = true version.workspace = true +[features] +client = [ ] +server = [ "dep:sqlx", "dep:torii-sqlite", "dep:dojo-types", "dep:dojo-world", "dep:starknet-crypto", "dep:chrono", "dep:libp2p-webrtc", "dep:rand" ] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] futures.workspace = true -rand.workspace = true serde.workspace = true # preserve order anyhow.workspace = true -cainome.workspace = true -chrono.workspace = true -crypto-bigint.workspace = true -dojo-types.workspace = true -dojo-world.workspace = true -indexmap.workspace = true serde_json.workspace = true -starknet-crypto.workspace = true starknet.workspace = true thiserror.workspace = true +torii-typed-data.workspace = true tracing.workspace = true +sqlx = { workspace = true, optional = true } +torii-sqlite = { workspace = true, optional = true } +dojo-types = { workspace = true, optional = true } +dojo-world = { workspace = true, optional = true } +rand = { workspace = true, optional = true } +starknet-crypto = { workspace = true, optional = true } +chrono = { workspace = true, optional = true } +libp2p-webrtc = { git = "https://github.com/libp2p/rust-libp2p", features = [ "pem", "tokio" ], rev = "cdc9638", optional = true } [dev-dependencies] +indexmap.workspace = true katana-runner.workspace = true tempfile.workspace = true tokio.workspace = true tracing-subscriber.workspace = true +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +tracing-wasm = "0.2.1" +wasm-bindgen-futures = "0.4.40" +wasm-bindgen-test = "0.3.40" +wasm-timer = "0.2.5" + + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] libp2p = { git = "https://github.com/libp2p/rust-libp2p", features = [ "dns", "ed25519", "gossipsub", "identify", "macros", "noise", "ping", "quic", "relay", "tcp", "tokio", "websocket", "yamux" ], rev = "cdc9638" } -libp2p-webrtc = { git = "https://github.com/libp2p/rust-libp2p", features = [ "pem", "tokio" ], rev = "cdc9638" } -sqlx.workspace = true -torii-core.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] libp2p = { git = "https://github.com/libp2p/rust-libp2p", features = [ "ed25519", "gossipsub", "identify", "macros", "noise", "ping", "tcp", "wasm-bindgen", "yamux" ], rev = "cdc9638" } libp2p-webrtc-websys = { git = "https://github.com/libp2p/rust-libp2p", rev = "cdc9638" } libp2p-websocket-websys = { git = "https://github.com/libp2p/rust-libp2p", rev = "cdc9638" } -tracing-wasm = "0.2.1" -wasm-bindgen-futures = "0.4.40" -wasm-bindgen-test = "0.3.40" -wasm-timer = "0.2.5" diff --git a/crates/torii/libp2p/src/client/mod.rs b/crates/torii/libp2p/src/client/mod.rs index f438d23072..eb5e66bb18 100644 --- a/crates/torii/libp2p/src/client/mod.rs +++ b/crates/torii/libp2p/src/client/mod.rs @@ -17,7 +17,7 @@ use tracing::info; pub mod events; use crate::client::events::ClientEvent; use crate::constants; -use crate::errors::Error; +use crate::error::Error; use crate::types::Message; pub(crate) const LOG_TARGET: &str = "torii::relay::client"; diff --git a/crates/torii/libp2p/src/errors.rs b/crates/torii/libp2p/src/error.rs similarity index 91% rename from crates/torii/libp2p/src/errors.rs rename to crates/torii/libp2p/src/error.rs index ec615b88d7..a8234c3cce 100644 --- a/crates/torii/libp2p/src/errors.rs +++ b/crates/torii/libp2p/src/error.rs @@ -6,6 +6,7 @@ use libp2p::gossipsub::{PublishError, SubscriptionError}; use libp2p::noise; use starknet::providers::ProviderError; use thiserror::Error; +use torii_typed_data::error::Error as TypedDataError; #[derive(Error, Debug)] pub enum Error { @@ -41,12 +42,15 @@ pub enum Error { #[error("Failed to read certificate: {0}")] ReadCertificateError(anyhow::Error), - #[error("Invalid message provided: {0}")] - InvalidMessageError(String), - #[error("Invalid type provided: {0}")] InvalidTypeError(String), #[error(transparent)] ProviderError(#[from] ProviderError), + + #[error(transparent)] + TypedDataError(#[from] TypedDataError), + + #[error("Invalid message provided: {0}")] + InvalidMessageError(String), } diff --git a/crates/torii/libp2p/src/lib.rs b/crates/torii/libp2p/src/lib.rs index 9d08de68c6..ec95bd8724 100644 --- a/crates/torii/libp2p/src/lib.rs +++ b/crates/torii/libp2p/src/lib.rs @@ -1,10 +1,14 @@ #![warn(unused_crate_dependencies)] -pub mod client; mod constants; -pub mod errors; -#[cfg(not(target_arch = "wasm32"))] +pub mod error; + +#[cfg(feature = "client")] +pub mod client; +#[cfg(feature = "server")] pub mod server; -mod tests; -pub mod typed_data; + +#[cfg(test)] +mod test; + pub mod types; diff --git a/crates/torii/libp2p/src/server/mod.rs b/crates/torii/libp2p/src/server/mod.rs index 7bb0508cc8..13bb63382f 100644 --- a/crates/torii/libp2p/src/server/mod.rs +++ b/crates/torii/libp2p/src/server/mod.rs @@ -25,19 +25,20 @@ use starknet::core::types::{BlockId, BlockTag, Felt, FunctionCall}; use starknet::core::utils::get_selector_from_name; use starknet::providers::Provider; use starknet_crypto::poseidon_hash_many; -use torii_core::executor::QueryMessage; -use torii_core::sql::utils::felts_to_sql_string; -use torii_core::sql::Sql; +use torii_sqlite::executor::QueryMessage; +use torii_sqlite::utils::felts_to_sql_string; +use torii_sqlite::Sql; use tracing::{info, warn}; use webrtc::tokio::Certificate; use crate::constants; -use crate::errors::Error; +use crate::error::Error; mod events; +use torii_typed_data::typed_data::{parse_value_to_ty, PrimitiveType, TypedData}; + use crate::server::events::ServerEvent; -use crate::typed_data::{parse_value_to_ty, PrimitiveType, TypedData}; use crate::types::Message; pub(crate) const LOG_TARGET: &str = "torii::relay::server"; @@ -128,7 +129,7 @@ impl Relay

{ relay: relay::Behaviour::new(key.public().to_peer_id(), Default::default()), ping: ping::Behaviour::new(ping::Config::new()), identify: identify::Behaviour::new(identify::Config::new( - format!("/torii-relay/{}", env!("CARGO_PKG_VERSION")), + format!("/{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")), key.public(), )), gossipsub: gossipsub::Behaviour::new( diff --git a/crates/torii/libp2p/src/test.rs b/crates/torii/libp2p/src/test.rs new file mode 100644 index 0000000000..35c6a85965 --- /dev/null +++ b/crates/torii/libp2p/src/test.rs @@ -0,0 +1,241 @@ +use std::error::Error; + +use crate::client::RelayClient; + +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +use dojo_types::primitive::Primitive; +use katana_runner::KatanaRunner; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; + +// This tests subscribing to a topic and receiving a message +#[cfg(not(target_arch = "wasm32"))] +#[tokio::test] +async fn test_client_messaging() -> Result<(), Box> { + use std::sync::Arc; + use std::time::Duration; + + use dojo_types::schema::{Member, Struct, Ty}; + use dojo_world::contracts::abigen::model::Layout; + use indexmap::IndexMap; + use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; + use starknet::providers::jsonrpc::HttpTransport; + use starknet::providers::JsonRpcClient; + use starknet::signers::SigningKey; + use starknet_crypto::Felt; + use tempfile::NamedTempFile; + use tokio::select; + use tokio::sync::broadcast; + use tokio::time::sleep; + use torii_sqlite::cache::ModelCache; + use torii_sqlite::executor::Executor; + use torii_sqlite::types::{Contract, ContractType}; + use torii_sqlite::Sql; + use torii_typed_data::typed_data::{Domain, Field, SimpleField, TypedData}; + + use crate::server::Relay; + use crate::types::Message; + + let _ = tracing_subscriber::fmt() + .with_env_filter("torii::relay::client=debug,torii::relay::server=debug") + .try_init(); + + // Database + let tempfile = NamedTempFile::new().unwrap(); + let path = tempfile.path().to_string_lossy(); + let options = ::from_str(&path) + .unwrap() + .create_if_missing(true); + let pool = SqlitePoolOptions::new() + .min_connections(1) + .idle_timeout(None) + .max_lifetime(None) + .connect_with(options) + .await + .unwrap(); + sqlx::migrate!("../migrations").run(&pool).await.unwrap(); + + let sequencer = KatanaRunner::new().expect("Failed to create Katana sequencer"); + + let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(sequencer.url()))); + + let account = sequencer.account_data(0); + + let (shutdown_tx, _) = broadcast::channel(1); + let (mut executor, sender) = + Executor::new(pool.clone(), shutdown_tx.clone(), Arc::clone(&provider), 100).await.unwrap(); + tokio::spawn(async move { + executor.run().await.unwrap(); + }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); + let mut db = Sql::new( + pool.clone(), + sender, + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, + ) + .await + .unwrap(); + + // Register the model of our Message + db.register_model( + "types_test", + &Ty::Struct(Struct { + name: "Message".to_string(), + children: vec![ + Member { + name: "identity".to_string(), + ty: Ty::Primitive(Primitive::ContractAddress(None)), + key: true, + }, + Member { + name: "message".to_string(), + ty: Ty::ByteArray("".to_string()), + key: false, + }, + ], + }), + Layout::Fixed(vec![]), + Felt::ZERO, + Felt::ZERO, + 0, + 0, + 0, + None, + ) + .await + .unwrap(); + db.execute().await.unwrap(); + + // Initialize the relay server + let mut relay_server = Relay::new(db, provider, 9900, 9901, 9902, None, None)?; + tokio::spawn(async move { + relay_server.run().await; + }); + + // Initialize the first client (listener) + let client = RelayClient::new("/ip4/127.0.0.1/tcp/9900".to_string())?; + tokio::spawn(async move { + client.event_loop.lock().await.run().await; + }); + + let mut typed_data = TypedData::new( + IndexMap::from_iter(vec![ + ( + "types_test-Message".to_string(), + vec![ + Field::SimpleType(SimpleField { + name: "identity".to_string(), + r#type: "ContractAddress".to_string(), + }), + Field::SimpleType(SimpleField { + name: "message".to_string(), + r#type: "string".to_string(), + }), + ], + ), + ( + "StarknetDomain".to_string(), + vec![ + Field::SimpleType(SimpleField { + name: "name".to_string(), + r#type: "shortstring".to_string(), + }), + Field::SimpleType(SimpleField { + name: "version".to_string(), + r#type: "shortstring".to_string(), + }), + Field::SimpleType(SimpleField { + name: "chainId".to_string(), + r#type: "shortstring".to_string(), + }), + Field::SimpleType(SimpleField { + name: "revision".to_string(), + r#type: "shortstring".to_string(), + }), + ], + ), + ]), + "types_test-Message", + Domain::new("types_test-Message", "1", "0x0", Some("1")), + IndexMap::new(), + ); + typed_data.message.insert( + "identity".to_string(), + torii_typed_data::typed_data::PrimitiveType::String(account.address.to_string()), + ); + + typed_data.message.insert( + "message".to_string(), + torii_typed_data::typed_data::PrimitiveType::String("mimi".to_string()), + ); + + let message_hash = typed_data.encode(account.address).unwrap(); + let signature = + SigningKey::from_secret_scalar(account.private_key.clone().unwrap().secret_scalar()) + .sign(&message_hash) + .unwrap(); + + client + .command_sender + .publish(Message { message: typed_data, signature: vec![signature.r, signature.s] }) + .await?; + + sleep(std::time::Duration::from_secs(2)).await; + + loop { + select! { + entity = sqlx::query("SELECT * FROM entities").fetch_one(&pool) => if entity.is_ok() { + println!("Test OK: Received message within 5 seconds."); + return Ok(()); + }, + _ = sleep(Duration::from_secs(5)) => { + println!("Test Failed: Did not receive message within 5 seconds."); + return Err("Timeout reached without receiving a message".into()); + } + } + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +async fn test_client_connection_wasm() -> Result<(), Box> { + use futures::future::{select, Either}; + use wasm_bindgen_futures::spawn_local; + + tracing_wasm::set_as_global_default(); + + let _ = tracing_subscriber::fmt().with_env_filter("torii_libp2p=debug").try_init(); + // Initialize the first client (listener) + // Make sure the cert hash is correct - corresponding to the cert in the relay server + let mut client = RelayClient::new( + "/ip4/127.0.0.1/udp/9091/webrtc-direct/certhash/\ + uEiCAoeHQh49fCHDolECesXO0CPR7fpz0sv0PWVaIahzT4g" + .to_string(), + )?; + + spawn_local(async move { + client.event_loop.lock().await.run().await; + }); + + client.command_sender.subscribe("mawmaw".to_string()).await?; + client.command_sender.wait_for_relay().await?; + client.command_sender.publish("mawmaw".to_string(), "mimi".as_bytes().to_vec()).await?; + + let timeout = wasm_timer::Delay::new(std::time::Duration::from_secs(2)); + let mut message_future = client.message_receiver.lock().await; + let message_future = message_future.next(); + + match select(message_future, timeout).await { + Either::Left((Some(_message), _)) => { + println!("Test OK: Received message within 5 seconds."); + Ok(()) + } + _ => { + println!("Test Failed: Did not receive message within 5 seconds."); + Err("Timeout reached without receiving a message".into()) + } + } +} diff --git a/crates/torii/libp2p/src/tests.rs b/crates/torii/libp2p/src/tests.rs deleted file mode 100644 index 9016f053ba..0000000000 --- a/crates/torii/libp2p/src/tests.rs +++ /dev/null @@ -1,755 +0,0 @@ -#[cfg(test)] -mod test { - use std::error::Error; - - use crate::client::RelayClient; - use crate::typed_data::{ - map_ty_to_primitive, parse_value_to_ty, Domain, PrimitiveType, TypedData, - }; - - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - use crypto_bigint::U256; - use dojo_types::primitive::Primitive; - use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; - use katana_runner::KatanaRunner; - use serde_json::Number; - use starknet::core::types::Felt; - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::*; - - #[test] - fn test_parse_primitive_to_ty() { - // primitives - let mut ty = Ty::Primitive(Primitive::U8(None)); - let value = PrimitiveType::Number(Number::from(1u64)); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::U8(Some(1)))); - - let mut ty = Ty::Primitive(Primitive::U16(None)); - let value = PrimitiveType::Number(Number::from(1u64)); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::U16(Some(1)))); - - let mut ty = Ty::Primitive(Primitive::U32(None)); - let value = PrimitiveType::Number(Number::from(1u64)); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::U32(Some(1)))); - - let mut ty = Ty::Primitive(Primitive::USize(None)); - let value = PrimitiveType::Number(Number::from(1u64)); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::USize(Some(1)))); - - let mut ty = Ty::Primitive(Primitive::U64(None)); - let value = PrimitiveType::Number(Number::from(1u64)); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::U64(Some(1)))); - - let mut ty = Ty::Primitive(Primitive::U128(None)); - let value = PrimitiveType::String("1".to_string()); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::U128(Some(1)))); - - // test u256 with low high - let mut ty = Ty::Primitive(Primitive::U256(None)); - let value = PrimitiveType::Object( - vec![ - ("low".to_string(), PrimitiveType::String("1".to_string())), - ("high".to_string(), PrimitiveType::String("0".to_string())), - ] - .into_iter() - .collect(), - ); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::U256(Some(U256::ONE)))); - - let mut ty = Ty::Primitive(Primitive::Felt252(None)); - let value = PrimitiveType::String("1".to_string()); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::Felt252(Some(Felt::ONE)))); - - let mut ty = Ty::Primitive(Primitive::ClassHash(None)); - let value = PrimitiveType::String("1".to_string()); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::ClassHash(Some(Felt::ONE)))); - - let mut ty = Ty::Primitive(Primitive::ContractAddress(None)); - let value = PrimitiveType::String("1".to_string()); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE)))); - - let mut ty = Ty::Primitive(Primitive::Bool(None)); - let value = PrimitiveType::Bool(true); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::Primitive(Primitive::Bool(Some(true)))); - - // bytearray - let mut ty = Ty::ByteArray("".to_string()); - let value = PrimitiveType::String("mimi".to_string()); - parse_value_to_ty(&value, &mut ty).unwrap(); - assert_eq!(ty, Ty::ByteArray("mimi".to_string())); - } - - #[test] - fn test_map_ty_to_primitive() { - let ty = Ty::Primitive(Primitive::U8(Some(1))); - let value = PrimitiveType::Number(Number::from(1u64)); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::U16(Some(1))); - let value = PrimitiveType::Number(Number::from(1u64)); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::U32(Some(1))); - let value = PrimitiveType::Number(Number::from(1u64)); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::USize(Some(1))); - let value = PrimitiveType::Number(Number::from(1u64)); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::U64(Some(1))); - let value = PrimitiveType::String("1".to_string()); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::U128(Some(1))); - let value = PrimitiveType::String("1".to_string()); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::U256(Some(U256::ONE))); - let value = PrimitiveType::Object( - vec![ - ("low".to_string(), PrimitiveType::String("1".to_string())), - ("high".to_string(), PrimitiveType::String("0".to_string())), - ] - .into_iter() - .collect(), - ); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::Felt252(Some(Felt::ONE))); - let value = PrimitiveType::String("1".to_string()); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::ClassHash(Some(Felt::ONE))); - let value = PrimitiveType::String("1".to_string()); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))); - let value = PrimitiveType::String("1".to_string()); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::Primitive(Primitive::Bool(Some(true))); - let value = PrimitiveType::Bool(true); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - - let ty = Ty::ByteArray("mimi".to_string()); - let value = PrimitiveType::String("mimi".to_string()); - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - } - - #[test] - fn test_parse_complex_to_ty() { - let mut ty = Ty::Struct(Struct { - name: "PlayerConfig".to_string(), - children: vec![ - Member { - name: "player".to_string(), - ty: Ty::Primitive(Primitive::ContractAddress(None)), - key: true, - }, - Member { name: "name".to_string(), ty: Ty::ByteArray("".to_string()), key: false }, - Member { - name: "items".to_string(), - // array of PlayerItem struct - ty: Ty::Array(vec![Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(None)), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(None)), - key: false, - }, - ], - })]), - key: false, - }, - // a favorite_item field with enum type Option - Member { - name: "favorite_item".to_string(), - ty: Ty::Enum(Enum { - name: "Option".to_string(), - option: None, - options: vec![ - EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { - name: "Some".to_string(), - ty: Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(None)), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(None)), - key: false, - }, - ], - }), - }, - ], - }), - key: false, - }, - ], - }); - - let value = PrimitiveType::Object( - vec![ - ("player".to_string(), PrimitiveType::String("1".to_string())), - ("name".to_string(), PrimitiveType::String("mimi".to_string())), - ( - "items".to_string(), - PrimitiveType::Array(vec![PrimitiveType::Object( - vec![ - ("item_id".to_string(), PrimitiveType::String("1".to_string())), - ("quantity".to_string(), PrimitiveType::Number(Number::from(1u64))), - ] - .into_iter() - .collect(), - )]), - ), - ( - "favorite_item".to_string(), - PrimitiveType::Object( - vec![( - "Some".to_string(), - PrimitiveType::Object( - vec![ - ("item_id".to_string(), PrimitiveType::String("1".to_string())), - ( - "quantity".to_string(), - PrimitiveType::Number(Number::from(1u64)), - ), - ] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ); - - parse_value_to_ty(&value, &mut ty).unwrap(); - - assert_eq!( - ty, - Ty::Struct(Struct { - name: "PlayerConfig".to_string(), - children: vec![ - Member { - name: "player".to_string(), - ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))), - key: true, - }, - Member { - name: "name".to_string(), - ty: Ty::ByteArray("mimi".to_string()), - key: false, - }, - Member { - name: "items".to_string(), - ty: Ty::Array(vec![Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - ], - })]), - key: false, - }, - Member { - name: "favorite_item".to_string(), - ty: Ty::Enum(Enum { - name: "Option".to_string(), - option: Some(1_u8), - options: vec![ - EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { - name: "Some".to_string(), - ty: Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - ], - }), - }, - ] - }), - key: false, - }, - ], - }) - ); - } - - #[test] - fn test_map_ty_to_complex() { - let ty = Ty::Struct(Struct { - name: "PlayerConfig".to_string(), - children: vec![ - Member { - name: "player".to_string(), - ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))), - key: true, - }, - Member { - name: "name".to_string(), - ty: Ty::ByteArray("mimi".to_string()), - key: false, - }, - Member { - name: "items".to_string(), - ty: Ty::Array(vec![Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - ], - })]), - key: false, - }, - Member { - name: "favorite_item".to_string(), - ty: Ty::Enum(Enum { - name: "Option".to_string(), - option: Some(1_u8), - options: vec![ - EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { - name: "Some".to_string(), - ty: Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - ], - }), - }, - ], - }), - key: false, - }, - ], - }); - - let value = PrimitiveType::Object( - vec![ - ("player".to_string(), PrimitiveType::String("1".to_string())), - ("name".to_string(), PrimitiveType::String("mimi".to_string())), - ( - "items".to_string(), - PrimitiveType::Array(vec![PrimitiveType::Object( - vec![ - ("item_id".to_string(), PrimitiveType::Number(Number::from(1u64))), - ("quantity".to_string(), PrimitiveType::Number(Number::from(1u64))), - ] - .into_iter() - .collect(), - )]), - ), - ( - "favorite_item".to_string(), - PrimitiveType::Object( - vec![( - "Some".to_string(), - PrimitiveType::Object( - vec![ - ( - "item_id".to_string(), - PrimitiveType::Number(Number::from(1u64)), - ), - ( - "quantity".to_string(), - PrimitiveType::Number(Number::from(1u64)), - ), - ] - .into_iter() - .collect(), - ), - )] - .into_iter() - .collect(), - ), - ), - ] - .into_iter() - .collect(), - ); - - assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); - } - - #[test] - fn test_model_to_typed_data() { - let ty = Ty::Struct(Struct { - name: "PlayerConfig".to_string(), - children: vec![ - Member { - name: "player".to_string(), - ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))), - key: true, - }, - Member { - name: "name".to_string(), - ty: Ty::ByteArray("mimi".to_string()), - key: false, - }, - Member { - name: "items".to_string(), - // array of PlayerItem struct - ty: Ty::Array(vec![Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(1))), - key: false, - }, - ], - })]), - key: false, - }, - // a favorite_item field with enum type Option - Member { - name: "favorite_item".to_string(), - ty: Ty::Enum(Enum { - name: "Option".to_string(), - option: Some(1), - options: vec![ - EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, - EnumOption { - name: "Some".to_string(), - ty: Ty::Struct(Struct { - name: "PlayerItem".to_string(), - children: vec![ - Member { - name: "item_id".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(69))), - key: false, - }, - Member { - name: "quantity".to_string(), - ty: Ty::Primitive(Primitive::U32(Some(42))), - key: false, - }, - ], - }), - }, - ], - }), - key: false, - }, - ], - }); - - let typed_data = - TypedData::from_model(ty, Domain::new("Test", "1", "Test", Some("1"))).unwrap(); - - let path = "mocks/model_PlayerConfig.json"; - let file = std::fs::File::open(path).unwrap(); - let reader = std::io::BufReader::new(file); - - let file_typed_data: TypedData = serde_json::from_reader(reader).unwrap(); - - assert_eq!( - typed_data.encode(Felt::ZERO).unwrap(), - file_typed_data.encode(Felt::ZERO).unwrap() - ); - } - - // This tests subscribing to a topic and receiving a message - #[cfg(not(target_arch = "wasm32"))] - #[tokio::test] - async fn test_client_messaging() -> Result<(), Box> { - use std::sync::Arc; - use std::time::Duration; - - use dojo_types::schema::{Member, Struct, Ty}; - use dojo_world::contracts::abigen::model::Layout; - use indexmap::IndexMap; - use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; - use starknet::providers::jsonrpc::HttpTransport; - use starknet::providers::JsonRpcClient; - use starknet::signers::SigningKey; - use starknet_crypto::Felt; - use tempfile::NamedTempFile; - use tokio::select; - use tokio::sync::broadcast; - use tokio::time::sleep; - use torii_core::executor::Executor; - use torii_core::sql::cache::ModelCache; - use torii_core::sql::Sql; - use torii_core::types::{Contract, ContractType}; - - use crate::server::Relay; - use crate::typed_data::{Domain, Field, SimpleField, TypedData}; - use crate::types::Message; - - let _ = tracing_subscriber::fmt() - .with_env_filter("torii::relay::client=debug,torii::relay::server=debug") - .try_init(); - - // Database - let tempfile = NamedTempFile::new().unwrap(); - let path = tempfile.path().to_string_lossy(); - let options = ::from_str(&path) - .unwrap() - .create_if_missing(true); - let pool = SqlitePoolOptions::new() - .min_connections(1) - .idle_timeout(None) - .max_lifetime(None) - .connect_with(options) - .await - .unwrap(); - sqlx::migrate!("../migrations").run(&pool).await.unwrap(); - - let sequencer = KatanaRunner::new().expect("Failed to create Katana sequencer"); - - let provider = Arc::new(JsonRpcClient::new(HttpTransport::new(sequencer.url()))); - - let account = sequencer.account_data(0); - - let (shutdown_tx, _) = broadcast::channel(1); - let (mut executor, sender) = - Executor::new(pool.clone(), shutdown_tx.clone(), Arc::clone(&provider), 100) - .await - .unwrap(); - tokio::spawn(async move { - executor.run().await.unwrap(); - }); - - let model_cache = Arc::new(ModelCache::new(pool.clone())); - let mut db = Sql::new( - pool.clone(), - sender, - &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], - model_cache, - ) - .await - .unwrap(); - - // Register the model of our Message - db.register_model( - "types_test", - &Ty::Struct(Struct { - name: "Message".to_string(), - children: vec![ - Member { - name: "identity".to_string(), - ty: Ty::Primitive(Primitive::ContractAddress(None)), - key: true, - }, - Member { - name: "message".to_string(), - ty: Ty::ByteArray("".to_string()), - key: false, - }, - ], - }), - Layout::Fixed(vec![]), - Felt::ZERO, - Felt::ZERO, - 0, - 0, - 0, - None, - ) - .await - .unwrap(); - db.execute().await.unwrap(); - - // Initialize the relay server - let mut relay_server = Relay::new(db, provider, 9900, 9901, 9902, None, None)?; - tokio::spawn(async move { - relay_server.run().await; - }); - - // Initialize the first client (listener) - let client = RelayClient::new("/ip4/127.0.0.1/tcp/9900".to_string())?; - tokio::spawn(async move { - client.event_loop.lock().await.run().await; - }); - - let mut typed_data = TypedData::new( - IndexMap::from_iter(vec![ - ( - "types_test-Message".to_string(), - vec![ - Field::SimpleType(SimpleField { - name: "identity".to_string(), - r#type: "ContractAddress".to_string(), - }), - Field::SimpleType(SimpleField { - name: "message".to_string(), - r#type: "string".to_string(), - }), - ], - ), - ( - "StarknetDomain".to_string(), - vec![ - Field::SimpleType(SimpleField { - name: "name".to_string(), - r#type: "shortstring".to_string(), - }), - Field::SimpleType(SimpleField { - name: "version".to_string(), - r#type: "shortstring".to_string(), - }), - Field::SimpleType(SimpleField { - name: "chainId".to_string(), - r#type: "shortstring".to_string(), - }), - Field::SimpleType(SimpleField { - name: "revision".to_string(), - r#type: "shortstring".to_string(), - }), - ], - ), - ]), - "types_test-Message", - Domain::new("types_test-Message", "1", "0x0", Some("1")), - IndexMap::new(), - ); - typed_data.message.insert( - "identity".to_string(), - crate::typed_data::PrimitiveType::String(account.address.to_string()), - ); - - typed_data.message.insert( - "message".to_string(), - crate::typed_data::PrimitiveType::String("mimi".to_string()), - ); - - let message_hash = typed_data.encode(account.address).unwrap(); - let signature = - SigningKey::from_secret_scalar(account.private_key.clone().unwrap().secret_scalar()) - .sign(&message_hash) - .unwrap(); - - client - .command_sender - .publish(Message { message: typed_data, signature: vec![signature.r, signature.s] }) - .await?; - - sleep(std::time::Duration::from_secs(2)).await; - - loop { - select! { - entity = sqlx::query("SELECT * FROM entities").fetch_one(&pool) => if entity.is_ok() { - println!("Test OK: Received message within 5 seconds."); - return Ok(()); - }, - _ = sleep(Duration::from_secs(5)) => { - println!("Test Failed: Did not receive message within 5 seconds."); - return Err("Timeout reached without receiving a message".into()); - } - } - } - } - - #[cfg(target_arch = "wasm32")] - #[wasm_bindgen_test] - async fn test_client_connection_wasm() -> Result<(), Box> { - use futures::future::{select, Either}; - use wasm_bindgen_futures::spawn_local; - - tracing_wasm::set_as_global_default(); - - let _ = tracing_subscriber::fmt().with_env_filter("torii_libp2p=debug").try_init(); - // Initialize the first client (listener) - // Make sure the cert hash is correct - corresponding to the cert in the relay server - let mut client = RelayClient::new( - "/ip4/127.0.0.1/udp/9091/webrtc-direct/certhash/\ - uEiCAoeHQh49fCHDolECesXO0CPR7fpz0sv0PWVaIahzT4g" - .to_string(), - )?; - - spawn_local(async move { - client.event_loop.lock().await.run().await; - }); - - client.command_sender.subscribe("mawmaw".to_string()).await?; - client.command_sender.wait_for_relay().await?; - client.command_sender.publish("mawmaw".to_string(), "mimi".as_bytes().to_vec()).await?; - - let timeout = wasm_timer::Delay::new(std::time::Duration::from_secs(2)); - let mut message_future = client.message_receiver.lock().await; - let message_future = message_future.next(); - - match select(message_future, timeout).await { - Either::Left((Some(_message), _)) => { - println!("Test OK: Received message within 5 seconds."); - Ok(()) - } - _ => { - println!("Test Failed: Did not receive message within 5 seconds."); - Err("Timeout reached without receiving a message".into()) - } - } - } -} diff --git a/crates/torii/libp2p/src/types.rs b/crates/torii/libp2p/src/types.rs index 1122f046fe..e8d52c2402 100644 --- a/crates/torii/libp2p/src/types.rs +++ b/crates/torii/libp2p/src/types.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; use starknet::core::types::Felt; - -use crate::typed_data::TypedData; +use torii_typed_data::TypedData; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { diff --git a/crates/torii/runner/Cargo.toml b/crates/torii/runner/Cargo.toml new file mode 100644 index 0000000000..4eefa45d4e --- /dev/null +++ b/crates/torii/runner/Cargo.toml @@ -0,0 +1,63 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "torii-runner" +repository.workspace = true +version.workspace = true + + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +base64.workspace = true +camino.workspace = true +chrono.workspace = true +ctrlc = { version = "3.4", features = [ "termination" ] } +dojo-metrics.workspace = true +dojo-types.workspace = true +dojo-utils.workspace = true +dojo-world.workspace = true +either = "1.9.0" +futures.workspace = true +http-body = "0.4.5" +http.workspace = true +hyper-reverse-proxy = { git = "https://github.com/tarrencev/hyper-reverse-proxy" } +hyper.workspace = true +indexmap.workspace = true +lazy_static.workspace = true +serde.workspace = true +serde_json.workspace = true +sqlx.workspace = true +starknet-crypto.workspace = true +starknet.workspace = true +tokio-stream = "0.1.11" +tokio-util = "0.7.7" +tokio.workspace = true +toml.workspace = true +torii-cli.workspace = true +torii-indexer.workspace = true +torii-sqlite.workspace = true +torii-graphql.workspace = true +torii-grpc = { workspace = true, features = [ "server" ] } +torii-relay = { workspace = true, features = [ "server" ] } +torii-server.workspace = true +tower.workspace = true + +tempfile.workspace = true +tower-http.workspace = true +tracing-subscriber.workspace = true +tracing.workspace = true +url.workspace = true +webbrowser = "0.8" + +[dev-dependencies] +assert_matches.workspace = true +camino.workspace = true + +[features] +default = [ "jemalloc", "sqlite" ] +jemalloc = [ "dojo-metrics/jemalloc" ] +sqlite = [ "sqlx/sqlite" ] diff --git a/crates/torii/runner/src/lib.rs b/crates/torii/runner/src/lib.rs new file mode 100644 index 0000000000..bfc763e4f6 --- /dev/null +++ b/crates/torii/runner/src/lib.rs @@ -0,0 +1,314 @@ +//! Torii binary executable. +//! +//! ## Feature Flags +//! +//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. +//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. +//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling +//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) +//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. + +use std::cmp; +use std::net::SocketAddr; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; + +use camino::Utf8PathBuf; +use dojo_metrics::exporters::prometheus::PrometheusRecorder; +use dojo_world::contracts::world::WorldContractReader; +use sqlx::sqlite::{ + SqliteAutoVacuum, SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous, +}; +use sqlx::SqlitePool; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use tempfile::{NamedTempFile, TempDir}; +use tokio::sync::broadcast; +use tokio::sync::broadcast::Sender; +use tokio_stream::StreamExt; +use torii_cli::ToriiArgs; +use torii_indexer::engine::{Engine, EngineConfig, IndexingFlags, Processors}; +use torii_indexer::processors::store_transaction::StoreTransactionProcessor; +use torii_indexer::processors::EventProcessorConfig; +use torii_server::proxy::Proxy; +use torii_sqlite::cache::ModelCache; +use torii_sqlite::executor::Executor; +use torii_sqlite::simple_broker::SimpleBroker; +use torii_sqlite::types::{Contract, ContractType, Model}; +use torii_sqlite::Sql; +use tracing::{error, info}; +use tracing_subscriber::{fmt, EnvFilter}; +use url::form_urlencoded; + +pub(crate) const LOG_TARGET: &str = "torii_runner"; + +#[derive(Debug, Clone)] +pub struct Runner { + args: ToriiArgs, +} + +impl Runner { + pub fn new(args: ToriiArgs) -> Self { + Self { args } + } + + pub async fn run(mut self) -> anyhow::Result<()> { + let world_address = if let Some(world_address) = self.args.world_address { + world_address + } else { + return Err(anyhow::anyhow!("Please specify a world address.")); + }; + + self.args + .indexing + .contracts + .push(Contract { address: world_address, r#type: ContractType::WORLD }); + + let filter_layer = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new("info,hyper_reverse_proxy=off")); + + let subscriber = fmt::Subscriber::builder().with_env_filter(filter_layer).finish(); + + // Set the global subscriber + tracing::subscriber::set_global_default(subscriber) + .expect("Failed to set the global tracing subscriber"); + + // Setup cancellation for graceful shutdown + let (shutdown_tx, _) = broadcast::channel(1); + + let shutdown_tx_clone = shutdown_tx.clone(); + ctrlc::set_handler(move || { + let _ = shutdown_tx_clone.send(()); + }) + .expect("Error setting Ctrl-C handler"); + + let tempfile = NamedTempFile::new()?; + let database_path = if let Some(db_dir) = self.args.db_dir { + // Create the directory if it doesn't exist + std::fs::create_dir_all(&db_dir)?; + // Set the database file path inside the directory + db_dir.join("torii.db") + } else { + tempfile.path().to_path_buf() + }; + + let mut options = SqliteConnectOptions::from_str(&database_path.to_string_lossy())? + .create_if_missing(true) + .with_regexp(); + + // Performance settings + options = options.auto_vacuum(SqliteAutoVacuum::None); + options = options.journal_mode(SqliteJournalMode::Wal); + options = options.synchronous(SqliteSynchronous::Normal); + + let pool = + SqlitePoolOptions::new().min_connections(1).connect_with(options.clone()).await?; + + let readonly_options = options.read_only(true); + let readonly_pool = SqlitePoolOptions::new() + .min_connections(1) + .max_connections(100) + .connect_with(readonly_options) + .await?; + + // Set the number of threads based on CPU count + let cpu_count = std::thread::available_parallelism().unwrap().get(); + let thread_count = cmp::min(cpu_count, 8); + sqlx::query(&format!("PRAGMA threads = {};", thread_count)).execute(&pool).await?; + + sqlx::migrate!("../migrations").run(&pool).await?; + + let provider: Arc<_> = JsonRpcClient::new(HttpTransport::new(self.args.rpc)).into(); + + // Get world address + let world = WorldContractReader::new(world_address, provider.clone()); + + let (mut executor, sender) = Executor::new( + pool.clone(), + shutdown_tx.clone(), + provider.clone(), + self.args.indexing.max_concurrent_tasks, + ) + .await?; + let executor_handle = tokio::spawn(async move { executor.run().await }); + + let model_cache = Arc::new(ModelCache::new(readonly_pool.clone())); + let db = Sql::new( + pool.clone(), + sender.clone(), + &self.args.indexing.contracts, + model_cache.clone(), + ) + .await?; + + let processors = Processors { + transaction: vec![Box::new(StoreTransactionProcessor)], + ..Processors::default() + }; + + let (block_tx, block_rx) = tokio::sync::mpsc::channel(100); + + let mut flags = IndexingFlags::empty(); + if self.args.indexing.transactions { + flags.insert(IndexingFlags::TRANSACTIONS); + } + if self.args.events.raw { + flags.insert(IndexingFlags::RAW_EVENTS); + } + if self.args.indexing.pending { + flags.insert(IndexingFlags::PENDING_BLOCKS); + } + + let mut engine: Engine>> = Engine::new( + world, + db.clone(), + provider.clone(), + processors, + EngineConfig { + max_concurrent_tasks: self.args.indexing.max_concurrent_tasks, + blocks_chunk_size: self.args.indexing.blocks_chunk_size, + events_chunk_size: self.args.indexing.events_chunk_size, + polling_interval: Duration::from_millis(self.args.indexing.polling_interval), + flags, + event_processor_config: EventProcessorConfig { + historical_events: self.args.events.historical.into_iter().collect(), + namespaces: self.args.indexing.namespaces.into_iter().collect(), + }, + world_block: self.args.indexing.world_block, + }, + shutdown_tx.clone(), + Some(block_tx), + &self.args.indexing.contracts, + ); + + let shutdown_rx = shutdown_tx.subscribe(); + let (grpc_addr, grpc_server) = torii_grpc::server::new( + shutdown_rx, + &readonly_pool, + block_rx, + world_address, + Arc::clone(&provider), + model_cache, + ) + .await?; + + let temp_dir = TempDir::new()?; + let artifacts_path = self + .args + .artifacts_path + .unwrap_or_else(|| Utf8PathBuf::from(temp_dir.path().to_str().unwrap())); + + tokio::fs::create_dir_all(&artifacts_path).await?; + let absolute_path = artifacts_path.canonicalize_utf8()?; + + let (artifacts_addr, artifacts_server) = torii_server::artifacts::new( + shutdown_tx.subscribe(), + &absolute_path, + readonly_pool.clone(), + ) + .await?; + + let mut libp2p_relay_server = torii_relay::server::Relay::new( + db, + provider.clone(), + self.args.relay.port, + self.args.relay.webrtc_port, + self.args.relay.websocket_port, + self.args.relay.local_key_path, + self.args.relay.cert_path, + ) + .expect("Failed to start libp2p relay server"); + + let addr = SocketAddr::new(self.args.server.http_addr, self.args.server.http_port); + + let proxy_server = Arc::new(Proxy::new( + addr, + self.args.server.http_cors_origins.filter(|cors_origins| !cors_origins.is_empty()), + Some(grpc_addr), + None, + Some(artifacts_addr), + Arc::new(readonly_pool.clone()), + )); + + let graphql_server = spawn_rebuilding_graphql_server( + shutdown_tx.clone(), + readonly_pool.into(), + proxy_server.clone(), + ); + + let gql_endpoint = format!("{addr}/graphql"); + let encoded: String = form_urlencoded::byte_serialize( + gql_endpoint.replace("0.0.0.0", "localhost").as_bytes(), + ) + .collect(); + let explorer_url = format!("https://worlds.dev/torii?url={}", encoded); + info!(target: LOG_TARGET, endpoint = %addr, "Starting torii endpoint."); + info!(target: LOG_TARGET, endpoint = %gql_endpoint, "Serving Graphql playground."); + info!(target: LOG_TARGET, url = %explorer_url, "Serving World Explorer."); + info!(target: LOG_TARGET, path = %artifacts_path, "Serving ERC artifacts at path"); + + if self.args.explorer { + if let Err(e) = webbrowser::open(&explorer_url) { + error!(target: LOG_TARGET, error = %e, "Opening World Explorer in the browser."); + } + } + + if self.args.metrics.metrics { + let addr = + SocketAddr::new(self.args.metrics.metrics_addr, self.args.metrics.metrics_port); + info!(target: LOG_TARGET, %addr, "Starting metrics endpoint."); + let prometheus_handle = PrometheusRecorder::install("torii")?; + let server = dojo_metrics::Server::new(prometheus_handle).with_process_metrics(); + tokio::spawn(server.start(addr)); + } + + let engine_handle = tokio::spawn(async move { engine.start().await }); + let proxy_server_handle = + tokio::spawn(async move { proxy_server.start(shutdown_tx.subscribe()).await }); + let graphql_server_handle = tokio::spawn(graphql_server); + let grpc_server_handle = tokio::spawn(grpc_server); + let libp2p_relay_server_handle = + tokio::spawn(async move { libp2p_relay_server.run().await }); + let artifacts_server_handle = tokio::spawn(artifacts_server); + + tokio::select! { + res = engine_handle => res??, + res = executor_handle => res??, + res = proxy_server_handle => res??, + res = graphql_server_handle => res?, + res = grpc_server_handle => res??, + res = libp2p_relay_server_handle => res?, + res = artifacts_server_handle => res?, + _ = dojo_utils::signal::wait_signals() => {}, + }; + + Ok(()) + } +} + +async fn spawn_rebuilding_graphql_server( + shutdown_tx: Sender<()>, + pool: Arc, + proxy_server: Arc, +) { + let mut broker = SimpleBroker::::subscribe(); + + loop { + let shutdown_rx = shutdown_tx.subscribe(); + let (new_addr, new_server) = torii_graphql::server::new(shutdown_rx, &pool).await; + + tokio::spawn(new_server); + + proxy_server.set_graphql_addr(new_addr).await; + + // Break the loop if there are no more events + if broker.next().await.is_none() { + break; + } else { + tokio::time::sleep(Duration::from_secs(1)).await; + } + } +} diff --git a/crates/torii/server/Cargo.toml b/crates/torii/server/Cargo.toml index 465b446003..9d01849f4f 100644 --- a/crates/torii/server/Cargo.toml +++ b/crates/torii/server/Cargo.toml @@ -24,7 +24,7 @@ serde_json.workspace = true sqlx.workspace = true tokio-util = "0.7.7" tokio.workspace = true -torii-core.workspace = true +torii-sqlite.workspace = true tower-http.workspace = true tower.workspace = true tracing.workspace = true diff --git a/crates/torii/server/src/artifacts.rs b/crates/torii/server/src/artifacts.rs index c0d17687a5..30e3a55135 100644 --- a/crates/torii/server/src/artifacts.rs +++ b/crates/torii/server/src/artifacts.rs @@ -15,8 +15,8 @@ use tokio::fs; use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::sync::broadcast::Receiver; -use torii_core::constants::{IPFS_CLIENT_MAX_RETRY, TOKENS_TABLE}; -use torii_core::utils::fetch_content_from_ipfs; +use torii_sqlite::constants::{IPFS_CLIENT_MAX_RETRY, TOKENS_TABLE}; +use torii_sqlite::utils::fetch_content_from_ipfs; use tracing::{debug, error, trace}; use warp::http::Response; use warp::path::Tail; diff --git a/crates/torii/sqlite/Cargo.toml b/crates/torii/sqlite/Cargo.toml new file mode 100644 index 0000000000..8ae31daa35 --- /dev/null +++ b/crates/torii/sqlite/Cargo.toml @@ -0,0 +1,48 @@ +[package] +description = "Torii SQLite implementation." +edition.workspace = true +license-file.workspace = true +name = "torii-sqlite" +repository.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +base64.workspace = true +bitflags = "2.6.0" +cainome.workspace = true +chrono.workspace = true +crypto-bigint.workspace = true +data-url.workspace = true +dojo-types.workspace = true +dojo-utils.workspace = true +dojo-world.workspace = true +futures-channel = "0.3.0" +futures-util.workspace = true +hashlink.workspace = true +once_cell.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +slab = "0.4.2" +sqlx.workspace = true +starknet-crypto.workspace = true +starknet.workspace = true +thiserror.workspace = true +tokio = { version = "1.32.0", features = [ "macros", "sync" ], default-features = true } +# tokio-stream = "0.1.11" +ipfs-api-backend-hyper.workspace = true +tokio-util.workspace = true +tracing.workspace = true + +[dev-dependencies] +dojo-test-utils.workspace = true +dojo-utils.workspace = true +katana-runner.workspace = true +scarb.workspace = true +sozo-scarbext.workspace = true +tempfile.workspace = true +torii-indexer.workspace = true diff --git a/crates/torii/core/src/sql/cache.rs b/crates/torii/sqlite/src/cache.rs similarity index 99% rename from crates/torii/core/src/sql/cache.rs rename to crates/torii/sqlite/src/cache.rs index db7fcd5d55..bbfad566db 100644 --- a/crates/torii/core/src/sql/cache.rs +++ b/crates/torii/sqlite/src/cache.rs @@ -8,8 +8,8 @@ use tokio::sync::RwLock; use crate::constants::TOKEN_BALANCE_TABLE; use crate::error::{Error, ParseError}; -use crate::sql::utils::I256; use crate::types::ContractType; +use crate::utils::I256; #[derive(Debug, Clone)] pub struct Model { diff --git a/crates/torii/core/src/constants.rs b/crates/torii/sqlite/src/constants.rs similarity index 90% rename from crates/torii/core/src/constants.rs rename to crates/torii/sqlite/src/constants.rs index ce46d77d31..be9e7140b2 100644 --- a/crates/torii/core/src/constants.rs +++ b/crates/torii/sqlite/src/constants.rs @@ -1,9 +1,9 @@ +pub const QUERY_QUEUE_BATCH_SIZE: usize = 1000; pub const TOKEN_BALANCE_TABLE: &str = "token_balances"; pub const TOKEN_TRANSFER_TABLE: &str = "token_transfers"; pub const TOKENS_TABLE: &str = "tokens"; - -pub(crate) const LOG_TARGET: &str = "torii_core::engine"; -pub const QUERY_QUEUE_BATCH_SIZE: usize = 1000; +pub const WORLD_CONTRACT_TYPE: &str = "WORLD"; +pub const SQL_FELT_DELIMITER: &str = "/"; pub const IPFS_URL: &str = "https://ipfs.io/ipfs/"; pub const IPFS_CLIENT_MAX_RETRY: u8 = 3; @@ -11,6 +11,3 @@ pub const IPFS_CLIENT_MAX_RETRY: u8 = 3; pub const IPFS_CLIENT_URL: &str = "https://ipfs.infura.io:5001"; pub const IPFS_CLIENT_USERNAME: &str = "2EBrzr7ZASQZKH32sl2xWauXPSA"; pub const IPFS_CLIENT_PASSWORD: &str = "12290b883db9138a8ae3363b6739d220"; - -pub const WORLD_CONTRACT_TYPE: &str = "WORLD"; -pub const SQL_FELT_DELIMITER: &str = "/"; diff --git a/crates/torii/core/src/sql/erc.rs b/crates/torii/sqlite/src/erc.rs similarity index 98% rename from crates/torii/core/src/sql/erc.rs rename to crates/torii/sqlite/src/erc.rs index 740c564b1a..f11f7988c1 100644 --- a/crates/torii/core/src/sql/erc.rs +++ b/crates/torii/sqlite/src/erc.rs @@ -14,9 +14,11 @@ use crate::executor::{ ApplyBalanceDiffQuery, Argument, QueryMessage, QueryType, RegisterErc20TokenQuery, RegisterErc721TokenQuery, }; -use crate::sql::utils::{felt_and_u256_to_sql_string, felt_to_sql_string, felts_to_sql_string}; use crate::types::ContractType; -use crate::utils::utc_dt_string_from_timestamp; +use crate::utils::{ + felt_and_u256_to_sql_string, felt_to_sql_string, felts_to_sql_string, + utc_dt_string_from_timestamp, +}; impl Sql { #[allow(clippy::too_many_arguments)] diff --git a/crates/torii/core/src/error.rs b/crates/torii/sqlite/src/error.rs similarity index 100% rename from crates/torii/core/src/error.rs rename to crates/torii/sqlite/src/error.rs diff --git a/crates/torii/core/src/executor/erc.rs b/crates/torii/sqlite/src/executor/erc.rs similarity index 99% rename from crates/torii/core/src/executor/erc.rs rename to crates/torii/sqlite/src/executor/erc.rs index 3e115e834e..c91cdce8a6 100644 --- a/crates/torii/core/src/executor/erc.rs +++ b/crates/torii/sqlite/src/executor/erc.rs @@ -16,9 +16,10 @@ use super::{ApplyBalanceDiffQuery, Executor}; use crate::constants::{IPFS_CLIENT_MAX_RETRY, SQL_FELT_DELIMITER, TOKEN_BALANCE_TABLE}; use crate::executor::LOG_TARGET; use crate::simple_broker::SimpleBroker; -use crate::sql::utils::{felt_to_sql_string, sql_string_to_u256, u256_to_sql_string, I256}; use crate::types::{ContractType, TokenBalance}; -use crate::utils::fetch_content_from_ipfs; +use crate::utils::{ + felt_to_sql_string, fetch_content_from_ipfs, sql_string_to_u256, u256_to_sql_string, I256, +}; #[derive(Debug, Clone)] pub struct RegisterErc721TokenQuery { diff --git a/crates/torii/core/src/executor/mod.rs b/crates/torii/sqlite/src/executor/mod.rs similarity index 99% rename from crates/torii/core/src/executor/mod.rs rename to crates/torii/sqlite/src/executor/mod.rs index acac0469f0..0b0c19b6ff 100644 --- a/crates/torii/core/src/executor/mod.rs +++ b/crates/torii/sqlite/src/executor/mod.rs @@ -20,17 +20,17 @@ use tracing::{debug, error}; use crate::constants::TOKENS_TABLE; use crate::simple_broker::SimpleBroker; -use crate::sql::utils::{felt_to_sql_string, I256}; use crate::types::{ ContractCursor, ContractType, Entity as EntityUpdated, Event as EventEmitted, EventMessage as EventMessageUpdated, Model as ModelRegistered, OptimisticEntity, OptimisticEventMessage, }; +use crate::utils::{felt_to_sql_string, I256}; pub mod erc; pub use erc::{RegisterErc20TokenQuery, RegisterErc721TokenMetadata, RegisterErc721TokenQuery}; -pub(crate) const LOG_TARGET: &str = "torii_core::executor"; +pub(crate) const LOG_TARGET: &str = "torii_sqlite::executor"; #[derive(Debug, Clone)] pub enum Argument { diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/sqlite/src/lib.rs similarity index 98% rename from crates/torii/core/src/sql/mod.rs rename to crates/torii/sqlite/src/lib.rs index 783fd246b4..7666108620 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/sqlite/src/lib.rs @@ -28,10 +28,13 @@ type IsEventMessage = bool; type IsStoreUpdate = bool; pub mod cache; +pub mod constants; pub mod erc; -#[cfg(test)] -#[path = "test.rs"] -mod test; +pub mod error; +pub mod executor; +pub mod model; +pub mod simple_broker; +pub mod types; pub mod utils; use cache::{LocalCache, Model, ModelCache}; @@ -169,7 +172,7 @@ impl Sql { Ok(()) } - pub(crate) async fn cursors(&self) -> Result { + pub async fn cursors(&self) -> Result { let mut conn: PoolConnection = self.pool.acquire().await?; let cursors = sqlx::query_as::<_, (String, String)>( "SELECT contract_address, last_pending_block_contract_tx FROM contracts WHERE \ @@ -511,15 +514,6 @@ impl Sql { self.model_cache.model(&selector).await.map_err(|e| e.into()) } - pub async fn does_entity_exist(&self, model: String, key: Felt) -> Result { - let sql = format!("SELECT COUNT(*) FROM [{model}] WHERE id = ?"); - - let count: i64 = - sqlx::query_scalar(&sql).bind(format!("{:#x}", key)).fetch_one(&self.pool).await?; - - Ok(count > 0) - } - pub async fn entities(&self, model: String) -> Result>> { let query = sqlx::query_as::<_, (i32, String, String)>("SELECT * FROM ?").bind(model); let mut conn: PoolConnection = self.pool.acquire().await?; diff --git a/crates/torii/core/src/model.rs b/crates/torii/sqlite/src/model.rs similarity index 100% rename from crates/torii/core/src/model.rs rename to crates/torii/sqlite/src/model.rs diff --git a/crates/torii/core/src/simple_broker.rs b/crates/torii/sqlite/src/simple_broker.rs similarity index 100% rename from crates/torii/core/src/simple_broker.rs rename to crates/torii/sqlite/src/simple_broker.rs diff --git a/crates/torii/core/src/types.rs b/crates/torii/sqlite/src/types.rs similarity index 100% rename from crates/torii/core/src/types.rs rename to crates/torii/sqlite/src/types.rs diff --git a/crates/torii/core/src/sql/utils.rs b/crates/torii/sqlite/src/utils.rs similarity index 79% rename from crates/torii/core/src/sql/utils.rs rename to crates/torii/sqlite/src/utils.rs index d5a6c5cd75..1d16e1a27e 100644 --- a/crates/torii/core/src/sql/utils.rs +++ b/crates/torii/sqlite/src/utils.rs @@ -1,11 +1,31 @@ use std::cmp::Ordering; use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::str::FromStr; +use std::time::Duration; +use anyhow::Result; +use chrono::{DateTime, Utc}; +use futures_util::TryStreamExt; +use ipfs_api_backend_hyper::{IpfsApi, IpfsClient, TryFromUri}; use starknet::core::types::U256; use starknet_crypto::Felt; +use tokio_util::bytes::Bytes; +use tracing::info; + +use crate::constants::{ + IPFS_CLIENT_MAX_RETRY, IPFS_CLIENT_PASSWORD, IPFS_CLIENT_URL, IPFS_CLIENT_USERNAME, + SQL_FELT_DELIMITER, +}; + +pub fn must_utc_datetime_from_timestamp(timestamp: u64) -> DateTime { + let naive_dt = DateTime::from_timestamp(timestamp as i64, 0) + .expect("Failed to convert timestamp to NaiveDateTime"); + naive_dt.to_utc() +} -use crate::constants::SQL_FELT_DELIMITER; +pub fn utc_dt_string_from_timestamp(timestamp: u64) -> String { + must_utc_datetime_from_timestamp(timestamp).to_rfc3339() +} pub fn felts_to_sql_string(felts: &[Felt]) -> String { felts.iter().map(|k| format!("{:#x}", k)).collect::>().join(SQL_FELT_DELIMITER) @@ -33,6 +53,32 @@ pub fn sql_string_to_felts(sql_string: &str) -> Vec { sql_string.split(SQL_FELT_DELIMITER).map(|felt| Felt::from_str(felt).unwrap()).collect() } +pub async fn fetch_content_from_ipfs(cid: &str, mut retries: u8) -> Result { + let client = IpfsClient::from_str(IPFS_CLIENT_URL)? + .with_credentials(IPFS_CLIENT_USERNAME, IPFS_CLIENT_PASSWORD); + while retries > 0 { + let response = client.cat(cid).map_ok(|chunk| chunk.to_vec()).try_concat().await; + match response { + Ok(stream) => return Ok(Bytes::from(stream)), + Err(e) => { + retries -= 1; + if retries > 0 { + info!( + error = %e, + "Fetch uri." + ); + tokio::time::sleep(Duration::from_secs(3)).await; + } + } + } + } + + Err(anyhow::anyhow!(format!( + "Failed to pull data from IPFS after {} attempts, cid: {}", + IPFS_CLIENT_MAX_RETRY, cid + ))) +} + // type used to do calculation on inmemory balances #[derive(Debug, Clone, Copy)] pub struct I256 { @@ -114,8 +160,37 @@ impl SubAssign for I256 { #[cfg(test)] mod tests { + use chrono::{DateTime, NaiveDate, NaiveTime, Utc}; + use super::*; + #[test] + fn test_must_utc_datetime_from_timestamp() { + let timestamp = 1633027200; + let expected_date = NaiveDate::from_ymd_opt(2021, 9, 30).unwrap(); + let expected_time = NaiveTime::from_hms_opt(18, 40, 0).unwrap(); + let expected = + DateTime::::from_naive_utc_and_offset(expected_date.and_time(expected_time), Utc); + let out = must_utc_datetime_from_timestamp(timestamp); + assert_eq!(out, expected, "Failed to convert timestamp to DateTime"); + } + + #[test] + #[should_panic(expected = "Failed to convert timestamp to NaiveDateTime")] + fn test_must_utc_datetime_from_timestamp_incorrect_timestamp() { + let timestamp = i64::MAX as u64 + 1; + let _result = must_utc_datetime_from_timestamp(timestamp); + } + + #[test] + fn test_utc_dt_string_from_timestamp() { + let timestamp = 1633027200; + let expected = "2021-09-30T18:40:00+00:00"; + let out = utc_dt_string_from_timestamp(timestamp); + println!("{}", out); + assert_eq!(out, expected, "Failed to convert timestamp to String"); + } + #[test] fn test_add_zero_false_and_zero_false() { // 0,false + 0,false == 0,false diff --git a/crates/torii/typed-data/Cargo.toml b/crates/torii/typed-data/Cargo.toml new file mode 100644 index 0000000000..5694354d1a --- /dev/null +++ b/crates/torii/typed-data/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition.workspace = true +license-file.workspace = true +name = "torii-typed-data" +repository.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde.workspace = true +# preserve order +cainome.workspace = true +crypto-bigint.workspace = true +dojo-types.workspace = true +indexmap.workspace = true +serde_json.workspace = true +starknet-crypto.workspace = true +starknet.workspace = true +thiserror.workspace = true diff --git a/crates/torii/libp2p/mocks/example_baseTypes.json b/crates/torii/typed-data/mocks/example_baseTypes.json similarity index 100% rename from crates/torii/libp2p/mocks/example_baseTypes.json rename to crates/torii/typed-data/mocks/example_baseTypes.json diff --git a/crates/torii/libp2p/mocks/example_enum.json b/crates/torii/typed-data/mocks/example_enum.json similarity index 100% rename from crates/torii/libp2p/mocks/example_enum.json rename to crates/torii/typed-data/mocks/example_enum.json diff --git a/crates/torii/libp2p/mocks/example_presetTypes.json b/crates/torii/typed-data/mocks/example_presetTypes.json similarity index 100% rename from crates/torii/libp2p/mocks/example_presetTypes.json rename to crates/torii/typed-data/mocks/example_presetTypes.json diff --git a/crates/torii/libp2p/mocks/mail_StructArray.json b/crates/torii/typed-data/mocks/mail_StructArray.json similarity index 100% rename from crates/torii/libp2p/mocks/mail_StructArray.json rename to crates/torii/typed-data/mocks/mail_StructArray.json diff --git a/crates/torii/libp2p/mocks/model_PlayerConfig.json b/crates/torii/typed-data/mocks/model_PlayerConfig.json similarity index 100% rename from crates/torii/libp2p/mocks/model_PlayerConfig.json rename to crates/torii/typed-data/mocks/model_PlayerConfig.json diff --git a/crates/torii/typed-data/src/error.rs b/crates/torii/typed-data/src/error.rs new file mode 100644 index 0000000000..28a09c2986 --- /dev/null +++ b/crates/torii/typed-data/src/error.rs @@ -0,0 +1,37 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Invalid type: {0}")] + InvalidType(String), + + #[error("Type not found: {0}")] + TypeNotFound(String), + + #[error("Invalid enum: {0}")] + InvalidEnum(String), + + #[error("Invalid field: {0}")] + InvalidField(String), + + #[error("Invalid value: {0}")] + InvalidValue(String), + + #[error("Invalid domain: {0}")] + InvalidDomain(String), + + #[error("Invalid message: {0}")] + InvalidMessage(String), + + #[error("Serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + + #[error("Crypto error: {0}")] + CryptoError(String), + + #[error("Parse error: {0}")] + ParseError(String), + + #[error("Field not found: {0}")] + FieldNotFound(String), +} diff --git a/crates/torii/typed-data/src/lib.rs b/crates/torii/typed-data/src/lib.rs new file mode 100644 index 0000000000..bf289fda18 --- /dev/null +++ b/crates/torii/typed-data/src/lib.rs @@ -0,0 +1,9 @@ +#![warn(unused_crate_dependencies)] + +#[cfg(test)] +mod test; + +pub mod error; +pub mod typed_data; + +pub use typed_data::TypedData; diff --git a/crates/torii/typed-data/src/test.rs b/crates/torii/typed-data/src/test.rs new file mode 100644 index 0000000000..1dc71203c3 --- /dev/null +++ b/crates/torii/typed-data/src/test.rs @@ -0,0 +1,489 @@ +use crypto_bigint::U256; +use dojo_types::primitive::Primitive; +use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; +use serde_json::Number; +use starknet_crypto::Felt; + +use crate::typed_data::{map_ty_to_primitive, parse_value_to_ty, Domain, PrimitiveType, TypedData}; + +#[test] +fn test_parse_primitive_to_ty() { + // primitives + let mut ty = Ty::Primitive(Primitive::U8(None)); + let value = PrimitiveType::Number(Number::from(1u64)); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::U8(Some(1)))); + + let mut ty = Ty::Primitive(Primitive::U16(None)); + let value = PrimitiveType::Number(Number::from(1u64)); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::U16(Some(1)))); + + let mut ty = Ty::Primitive(Primitive::U32(None)); + let value = PrimitiveType::Number(Number::from(1u64)); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::U32(Some(1)))); + + let mut ty = Ty::Primitive(Primitive::USize(None)); + let value = PrimitiveType::Number(Number::from(1u64)); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::USize(Some(1)))); + + let mut ty = Ty::Primitive(Primitive::U64(None)); + let value = PrimitiveType::Number(Number::from(1u64)); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::U64(Some(1)))); + + let mut ty = Ty::Primitive(Primitive::U128(None)); + let value = PrimitiveType::String("1".to_string()); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::U128(Some(1)))); + + // test u256 with low high + let mut ty = Ty::Primitive(Primitive::U256(None)); + let value = PrimitiveType::Object( + vec![ + ("low".to_string(), PrimitiveType::String("1".to_string())), + ("high".to_string(), PrimitiveType::String("0".to_string())), + ] + .into_iter() + .collect(), + ); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::U256(Some(U256::ONE)))); + + let mut ty = Ty::Primitive(Primitive::Felt252(None)); + let value = PrimitiveType::String("1".to_string()); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::Felt252(Some(Felt::ONE)))); + + let mut ty = Ty::Primitive(Primitive::ClassHash(None)); + let value = PrimitiveType::String("1".to_string()); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::ClassHash(Some(Felt::ONE)))); + + let mut ty = Ty::Primitive(Primitive::ContractAddress(None)); + let value = PrimitiveType::String("1".to_string()); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE)))); + + let mut ty = Ty::Primitive(Primitive::Bool(None)); + let value = PrimitiveType::Bool(true); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::Primitive(Primitive::Bool(Some(true)))); + + // bytearray + let mut ty = Ty::ByteArray("".to_string()); + let value = PrimitiveType::String("mimi".to_string()); + parse_value_to_ty(&value, &mut ty).unwrap(); + assert_eq!(ty, Ty::ByteArray("mimi".to_string())); +} + +#[test] +fn test_map_ty_to_primitive() { + let ty = Ty::Primitive(Primitive::U8(Some(1))); + let value = PrimitiveType::Number(Number::from(1u64)); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::U16(Some(1))); + let value = PrimitiveType::Number(Number::from(1u64)); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::U32(Some(1))); + let value = PrimitiveType::Number(Number::from(1u64)); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::USize(Some(1))); + let value = PrimitiveType::Number(Number::from(1u64)); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::U64(Some(1))); + let value = PrimitiveType::String("1".to_string()); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::U128(Some(1))); + let value = PrimitiveType::String("1".to_string()); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::U256(Some(U256::ONE))); + let value = PrimitiveType::Object( + vec![ + ("low".to_string(), PrimitiveType::String("1".to_string())), + ("high".to_string(), PrimitiveType::String("0".to_string())), + ] + .into_iter() + .collect(), + ); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::Felt252(Some(Felt::ONE))); + let value = PrimitiveType::String("1".to_string()); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::ClassHash(Some(Felt::ONE))); + let value = PrimitiveType::String("1".to_string()); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))); + let value = PrimitiveType::String("1".to_string()); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::Primitive(Primitive::Bool(Some(true))); + let value = PrimitiveType::Bool(true); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); + + let ty = Ty::ByteArray("mimi".to_string()); + let value = PrimitiveType::String("mimi".to_string()); + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); +} + +#[test] +fn test_parse_complex_to_ty() { + let mut ty = Ty::Struct(Struct { + name: "PlayerConfig".to_string(), + children: vec![ + Member { + name: "player".to_string(), + ty: Ty::Primitive(Primitive::ContractAddress(None)), + key: true, + }, + Member { name: "name".to_string(), ty: Ty::ByteArray("".to_string()), key: false }, + Member { + name: "items".to_string(), + // array of PlayerItem struct + ty: Ty::Array(vec![Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }, + ], + })]), + key: false, + }, + // a favorite_item field with enum type Option + Member { + name: "favorite_item".to_string(), + ty: Ty::Enum(Enum { + name: "Option".to_string(), + option: None, + options: vec![ + EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { + name: "Some".to_string(), + ty: Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }, + ], + }), + }, + ], + }), + key: false, + }, + ], + }); + + let value = PrimitiveType::Object( + vec![ + ("player".to_string(), PrimitiveType::String("1".to_string())), + ("name".to_string(), PrimitiveType::String("mimi".to_string())), + ( + "items".to_string(), + PrimitiveType::Array(vec![PrimitiveType::Object( + vec![ + ("item_id".to_string(), PrimitiveType::String("1".to_string())), + ("quantity".to_string(), PrimitiveType::Number(Number::from(1u64))), + ] + .into_iter() + .collect(), + )]), + ), + ( + "favorite_item".to_string(), + PrimitiveType::Object( + vec![( + "Some".to_string(), + PrimitiveType::Object( + vec![ + ("item_id".to_string(), PrimitiveType::String("1".to_string())), + ("quantity".to_string(), PrimitiveType::Number(Number::from(1u64))), + ] + .into_iter() + .collect(), + ), + )] + .into_iter() + .collect(), + ), + ), + ] + .into_iter() + .collect(), + ); + + parse_value_to_ty(&value, &mut ty).unwrap(); + + assert_eq!( + ty, + Ty::Struct(Struct { + name: "PlayerConfig".to_string(), + children: vec![ + Member { + name: "player".to_string(), + ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))), + key: true, + }, + Member { + name: "name".to_string(), + ty: Ty::ByteArray("mimi".to_string()), + key: false, + }, + Member { + name: "items".to_string(), + ty: Ty::Array(vec![Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + ], + })]), + key: false, + }, + Member { + name: "favorite_item".to_string(), + ty: Ty::Enum(Enum { + name: "Option".to_string(), + option: Some(1_u8), + options: vec![ + EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { + name: "Some".to_string(), + ty: Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + ], + }), + }, + ] + }), + key: false, + }, + ], + }) + ); +} + +#[test] +fn test_map_ty_to_complex() { + let ty = Ty::Struct(Struct { + name: "PlayerConfig".to_string(), + children: vec![ + Member { + name: "player".to_string(), + ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))), + key: true, + }, + Member { name: "name".to_string(), ty: Ty::ByteArray("mimi".to_string()), key: false }, + Member { + name: "items".to_string(), + ty: Ty::Array(vec![Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + ], + })]), + key: false, + }, + Member { + name: "favorite_item".to_string(), + ty: Ty::Enum(Enum { + name: "Option".to_string(), + option: Some(1_u8), + options: vec![ + EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { + name: "Some".to_string(), + ty: Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + ], + }), + }, + ], + }), + key: false, + }, + ], + }); + + let value = PrimitiveType::Object( + vec![ + ("player".to_string(), PrimitiveType::String("1".to_string())), + ("name".to_string(), PrimitiveType::String("mimi".to_string())), + ( + "items".to_string(), + PrimitiveType::Array(vec![PrimitiveType::Object( + vec![ + ("item_id".to_string(), PrimitiveType::Number(Number::from(1u64))), + ("quantity".to_string(), PrimitiveType::Number(Number::from(1u64))), + ] + .into_iter() + .collect(), + )]), + ), + ( + "favorite_item".to_string(), + PrimitiveType::Object( + vec![( + "Some".to_string(), + PrimitiveType::Object( + vec![ + ("item_id".to_string(), PrimitiveType::Number(Number::from(1u64))), + ("quantity".to_string(), PrimitiveType::Number(Number::from(1u64))), + ] + .into_iter() + .collect(), + ), + )] + .into_iter() + .collect(), + ), + ), + ] + .into_iter() + .collect(), + ); + + assert_eq!(value, map_ty_to_primitive(&ty).unwrap()); +} + +#[test] +fn test_model_to_typed_data() { + let ty = Ty::Struct(Struct { + name: "PlayerConfig".to_string(), + children: vec![ + Member { + name: "player".to_string(), + ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))), + key: true, + }, + Member { name: "name".to_string(), ty: Ty::ByteArray("mimi".to_string()), key: false }, + Member { + name: "items".to_string(), + // array of PlayerItem struct + ty: Ty::Array(vec![Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(1))), + key: false, + }, + ], + })]), + key: false, + }, + // a favorite_item field with enum type Option + Member { + name: "favorite_item".to_string(), + ty: Ty::Enum(Enum { + name: "Option".to_string(), + option: Some(1), + options: vec![ + EnumOption { name: "None".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { + name: "Some".to_string(), + ty: Ty::Struct(Struct { + name: "PlayerItem".to_string(), + children: vec![ + Member { + name: "item_id".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(69))), + key: false, + }, + Member { + name: "quantity".to_string(), + ty: Ty::Primitive(Primitive::U32(Some(42))), + key: false, + }, + ], + }), + }, + ], + }), + key: false, + }, + ], + }); + + let typed_data = + TypedData::from_model(ty, Domain::new("Test", "1", "Test", Some("1"))).unwrap(); + + let path = "mocks/model_PlayerConfig.json"; + let file = std::fs::File::open(path).unwrap(); + let reader = std::io::BufReader::new(file); + + let file_typed_data: TypedData = serde_json::from_reader(reader).unwrap(); + + assert_eq!(typed_data.encode(Felt::ZERO).unwrap(), file_typed_data.encode(Felt::ZERO).unwrap()); +} diff --git a/crates/torii/libp2p/src/typed_data.rs b/crates/torii/typed-data/src/typed_data.rs similarity index 89% rename from crates/torii/libp2p/src/typed_data.rs rename to crates/torii/typed-data/src/typed_data.rs index b15ee88188..ab8feb97ec 100644 --- a/crates/torii/libp2p/src/typed_data.rs +++ b/crates/torii/typed-data/src/typed_data.rs @@ -11,7 +11,7 @@ use starknet::core::types::Felt; use starknet::core::utils::{cairo_short_string_to_felt, get_selector_from_name}; use starknet_crypto::poseidon_hash_many; -use crate::errors::Error; +use crate::error::Error; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SimpleField { @@ -93,11 +93,7 @@ fn get_preset_types() -> IndexMap> { // Looks up both the types hashmap as well as the preset types // Returns the fields and the hashmap of types fn get_fields(name: &str, types: &IndexMap>) -> Result, Error> { - if let Some(fields) = types.get(name) { - return Ok(fields.clone()); - } - - Err(Error::InvalidMessageError(format!("Type {} not found", name))) + types.get(name).cloned().ok_or_else(|| Error::TypeNotFound(name.to_string())) } fn get_dependencies( @@ -234,17 +230,14 @@ pub(crate) fn get_value_type( } } - Err(Error::InvalidMessageError(format!("Field {} not found in types", name))) + Err(Error::FieldNotFound(name.to_string())) } fn get_hex(value: &str) -> Result { - if let Ok(felt) = Felt::from_str(value) { - Ok(felt) - } else { - // assume its a short string and encode + Felt::from_str(value).or_else(|_| { cairo_short_string_to_felt(value) - .map_err(|e| Error::InvalidMessageError(format!("Invalid shortstring for felt: {}", e))) - } + .map_err(|e| Error::ParseError(format!("Invalid shortstring for felt: {}", e))) + }) } impl PrimitiveType { @@ -263,8 +256,9 @@ impl PrimitiveType { if ctx.base_type == "enum" { let (variant_name, value) = obj.first().ok_or_else(|| { - Error::InvalidMessageError("Enum value must be populated".to_string()) + Error::InvalidEnum("Enum value must be populated".to_string()) })?; + let variant_type = get_value_type(variant_name, types)?; // variant index @@ -282,9 +276,7 @@ impl PrimitiveType { .split(',') .nth(idx) .ok_or_else(|| { - Error::InvalidMessageError( - "Invalid enum variant type".to_string(), - ) + Error::InvalidEnum("Invalid enum variant type".to_string()) })?; let field_hash = @@ -306,10 +298,7 @@ impl PrimitiveType { let type_hash = encode_type(r#type, if ctx.is_preset { preset_types } else { types })?; hashes.push(get_selector_from_name(&type_hash).map_err(|e| { - Error::InvalidMessageError(format!( - "Invalid type {} for selector: {}", - r#type, e - )) + Error::ParseError(format!("Invalid type {} for selector: {}", r#type, e)) })?); for (field_name, value) in obj { @@ -339,9 +328,7 @@ impl PrimitiveType { .collect::>(); if inner_types.len() != array.len() { - return Err(Error::InvalidMessageError( - "Tuple length mismatch".to_string(), - )); + return Err(Error::InvalidValue("Tuple length mismatch".to_string())); } let mut hashes = Vec::new(); @@ -371,7 +358,7 @@ impl PrimitiveType { "string" => { // split the string into short strings and encode let byte_array = ByteArray::from_string(string).map_err(|e| { - Error::InvalidMessageError(format!("Invalid string for bytearray: {}", e)) + Error::ParseError(format!("Invalid string for bytearray: {}", e)) })?; let mut hashes = vec![Felt::from(byte_array.data.len())]; @@ -386,19 +373,18 @@ impl PrimitiveType { Ok(poseidon_hash_many(hashes.as_slice())) } "selector" => get_selector_from_name(string) - .map_err(|e| Error::InvalidMessageError(format!("Invalid selector: {}", e))), + .map_err(|e| Error::ParseError(format!("Invalid selector: {}", e))), "felt" => get_hex(string), "ContractAddress" => get_hex(string), "ClassHash" => get_hex(string), "timestamp" => get_hex(string), "u128" => get_hex(string), "i128" => get_hex(string), - _ => Err(Error::InvalidMessageError(format!("Invalid type {} for string", r#type))), + _ => Err(Error::InvalidType(format!("Invalid type {} for string", r#type))), }, PrimitiveType::Number(number) => { - let felt = Felt::from_str(&number.to_string()).map_err(|_| { - Error::InvalidMessageError(format!("Invalid number {}", number)) - })?; + let felt = Felt::from_str(&number.to_string()) + .map_err(|_| Error::ParseError(format!("Invalid number {}", number)))?; Ok(felt) } } @@ -425,16 +411,19 @@ impl Domain { } pub fn encode(&self, types: &IndexMap>) -> Result { - let mut object = IndexMap::new(); + if self.revision.as_deref().unwrap_or("1") != "1" { + return Err(Error::InvalidDomain("Legacy revision 0 is not supported".to_string())); + } + let mut object = IndexMap::new(); object.insert("name".to_string(), PrimitiveType::String(self.name.clone())); object.insert("version".to_string(), PrimitiveType::String(self.version.clone())); object.insert("chainId".to_string(), PrimitiveType::String(self.chain_id.clone())); + if let Some(revision) = &self.revision { object.insert("revision".to_string(), PrimitiveType::String(revision.clone())); } - // we dont need to pass our preset types here. domain should never use a preset type PrimitiveType::Object(object).encode( "StarknetDomain", types, @@ -444,6 +433,17 @@ impl Domain { } } +macro_rules! from_str { + ($string:expr, $type:ty) => { + if $string.starts_with("0x") || $string.starts_with("0X") { + <$type>::from_str_radix(&$string[2..], 16) + } else { + <$type>::from_str($string) + } + .map_err(|e| Error::ParseError(format!("Failed to parse number: {}", e))) + }; +} + pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error> { match value { PrimitiveType::Object(object) => match ty { @@ -451,7 +451,7 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error for (key, value) in object { let member = struct_.children.iter_mut().find(|member| member.name == *key).ok_or_else( - || Error::InvalidMessageError(format!("Member {} not found", key)), + || Error::FieldNotFound(format!("Member {} not found", key)), )?; parse_value_to_ty(value, &mut member.ty)?; @@ -480,9 +480,9 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error // where the K is the variant name // and the value is the variant value Ty::Enum(enum_) => { - let (option_name, value) = object.first().ok_or_else(|| { - Error::InvalidMessageError("Enum variant not found".to_string()) - })?; + let (option_name, value) = object + .first() + .ok_or_else(|| Error::InvalidEnum("Enum variant not found".to_string()))?; enum_.options.iter_mut().for_each(|option| { if option.name == *option_name { @@ -490,15 +490,12 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error } }); - enum_.set_option(option_name).map_err(|e| { - Error::InvalidMessageError(format!("Failed to set enum option: {}", e)) - })?; + enum_ + .set_option(option_name) + .map_err(|e| Error::InvalidEnum(format!("Failed to set enum option: {}", e)))?; } _ => { - return Err(Error::InvalidMessageError(format!( - "Invalid object type for {}", - ty.name() - ))); + return Err(Error::InvalidType(format!("Invalid object type for {}", ty.name()))); } }, PrimitiveType::Array(values) => match ty { @@ -518,7 +515,7 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error Ty::Tuple(tuple) => { // our array values need to match the length of the tuple if tuple.len() != values.len() { - return Err(Error::InvalidMessageError("Tuple length mismatch".to_string())); + return Err(Error::InvalidValue("Tuple length mismatch".to_string())); } for (i, value) in tuple.iter_mut().enumerate() { @@ -526,10 +523,7 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error } } _ => { - return Err(Error::InvalidMessageError(format!( - "Invalid array type for {}", - ty.name() - ))); + return Err(Error::InvalidType(format!("Invalid array type for {}", ty.name()))); } }, PrimitiveType::Number(number) => match ty { @@ -562,17 +556,14 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error *usize = Some(number.as_u64().unwrap() as u32); } _ => { - return Err(Error::InvalidMessageError(format!( + return Err(Error::InvalidType(format!( "Invalid number type for {}", ty.name() ))); } }, _ => { - return Err(Error::InvalidMessageError(format!( - "Invalid number type for {}", - ty.name() - ))); + return Err(Error::InvalidType(format!("Invalid number type for {}", ty.name()))); } }, PrimitiveType::Bool(boolean) => { @@ -581,37 +572,37 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error PrimitiveType::String(string) => match ty { Ty::Primitive(primitive) => match primitive { Primitive::I8(v) => { - *v = Some(i8::from_str(string).unwrap()); + *v = Some(from_str!(string, i8)?); } Primitive::I16(v) => { - *v = Some(i16::from_str(string).unwrap()); + *v = Some(from_str!(string, i16)?); } Primitive::I32(v) => { - *v = Some(i32::from_str(string).unwrap()); + *v = Some(from_str!(string, i32)?); } Primitive::I64(v) => { - *v = Some(i64::from_str(string).unwrap()); + *v = Some(from_str!(string, i64)?); } Primitive::I128(v) => { - *v = Some(i128::from_str(string).unwrap()); + *v = Some(from_str!(string, i128)?); } Primitive::U8(v) => { - *v = Some(u8::from_str(string).unwrap()); + *v = Some(from_str!(string, u8)?); } Primitive::U16(v) => { - *v = Some(u16::from_str(string).unwrap()); + *v = Some(from_str!(string, u16)?); } Primitive::U32(v) => { - *v = Some(u32::from_str(string).unwrap()); + *v = Some(from_str!(string, u32)?); } Primitive::U64(v) => { - *v = Some(u64::from_str(string).unwrap()); + *v = Some(from_str!(string, u64)?); } Primitive::U128(v) => { - *v = Some(u128::from_str(string).unwrap()); + *v = Some(from_str!(string, u128)?); } Primitive::USize(v) => { - *v = Some(u32::from_str(string).unwrap()); + *v = Some(from_str!(string, u32)?); } Primitive::Felt252(v) => { *v = Some(Felt::from_str(string).unwrap()); @@ -626,17 +617,14 @@ pub fn parse_value_to_ty(value: &PrimitiveType, ty: &mut Ty) -> Result<(), Error *v = Some(bool::from_str(string).unwrap()); } _ => { - return Err(Error::InvalidMessageError("Invalid primitive type".to_string())); + return Err(Error::InvalidType("Invalid primitive type".to_string())); } }, Ty::ByteArray(s) => { s.clone_from(string); } _ => { - return Err(Error::InvalidMessageError(format!( - "Invalid string type for {}", - ty.name() - ))); + return Err(Error::InvalidType(format!("Invalid string type for {}", ty.name()))); } }, } @@ -655,13 +643,12 @@ pub fn map_ty_to_primitive(ty: &Ty) -> Result { } Ty::Enum(enum_) => { let mut object = IndexMap::new(); - let option = enum_ - .option - .ok_or(Error::InvalidMessageError("Enum option not found".to_string()))?; + let option = + enum_.option.ok_or(Error::InvalidEnum("Enum option not found".to_string()))?; let option = enum_ .options .get(option as usize) - .ok_or(Error::InvalidMessageError("Enum option not found".to_string()))?; + .ok_or(Error::InvalidEnum("Enum option not found".to_string()))?; object.insert(option.name.clone(), map_ty_to_primitive(&option.ty)?); Ok(PrimitiveType::Object(object)) } @@ -883,18 +870,11 @@ impl TypedData { pub fn encode(&self, account: Felt) -> Result { let preset_types = get_preset_types(); - if self.domain.revision.clone().unwrap_or("1".to_string()) != "1" { - return Err(Error::InvalidMessageError( - "Legacy revision 0 is not supported".to_string(), - )); - } - - let prefix_message = cairo_short_string_to_felt("StarkNet Message").unwrap(); + let prefix_message = cairo_short_string_to_felt("StarkNet Message") + .map_err(|e| Error::CryptoError(e.to_string()))?; - // encode domain separator let domain_hash = self.domain.encode(&self.types)?; - // encode message let message_hash = PrimitiveType::Object(self.message.clone()).encode( &self.primary_type, &self.types, @@ -902,8 +882,7 @@ impl TypedData { &mut Default::default(), )?; - // return full hash - Ok(poseidon_hash_many(vec![prefix_message, domain_hash, account, message_hash].as_slice())) + Ok(poseidon_hash_many(&[prefix_message, domain_hash, account, message_hash])) } } diff --git a/crates/torii/types-test/Scarb.lock b/crates/torii/types-test/Scarb.lock index 2197d440fb..b4172e1555 100644 --- a/crates/torii/types-test/Scarb.lock +++ b/crates/torii/types-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "1.0.9" +version = "1.0.10" dependencies = [ "dojo_plugin", ] @@ -14,7 +14,7 @@ version = "2.8.4" [[package]] name = "types_test" -version = "1.0.5" +version = "1.0.9" dependencies = [ "dojo", ] diff --git a/crates/torii/types-test/Scarb.toml b/crates/torii/types-test/Scarb.toml index a490044da2..fd4f420f0b 100644 --- a/crates/torii/types-test/Scarb.toml +++ b/crates/torii/types-test/Scarb.toml @@ -2,7 +2,7 @@ cairo-version = "=2.8.4" edition = "2024_07" name = "types_test" -version = "1.0.9" +version = "1.0.10" [cairo] sierra-replace-ids = true diff --git a/examples/simple/Scarb.lock b/examples/simple/Scarb.lock index d5dfe49119..09def408e1 100644 --- a/examples/simple/Scarb.lock +++ b/examples/simple/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "1.0.5" +version = "1.0.9" dependencies = [ "dojo_plugin", ] diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 446c733bdf..2c289fe423 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -17,7 +17,7 @@ dependencies = [ [[package]] name = "dojo" -version = "1.0.9" +version = "1.0.10" dependencies = [ "dojo_plugin", ] @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "dojo_examples" -version = "1.0.8" +version = "1.0.9" dependencies = [ "armory", "bestiary", diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 903335bafc..83dcec23dd 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -1,7 +1,7 @@ [package] cairo-version = "=2.8.4" name = "dojo_examples" -version = "1.0.9" +version = "1.0.10" # Use the prelude with the less imports as possible # from corelib. edition = "2024_07" diff --git a/examples/spawn-and-move/manifest_dev.json b/examples/spawn-and-move/manifest_dev.json index 057ee7d64d..fec6bf6d76 100644 --- a/examples/spawn-and-move/manifest_dev.json +++ b/examples/spawn-and-move/manifest_dev.json @@ -1252,8 +1252,8 @@ }, "contracts": [ { - "address": "0x7e8a52c68b243d3a86a55c04ccec2edc760252d5952566d3af4001bd6cf38f3", - "class_hash": "0x636c2cf31b094097625cb5ada96f54ee9a3f7bc6d8cde00cc85e5ef0c622c8b", + "address": "0x6bfba78b8f4f42da469860a95291c2fad959c91ea747e2062de763ff4e62c4a", + "class_hash": "0x3e65d56b082c47e9e0952503424bcf6956ddd6d5faff40dd9900ea787d89c6", "abi": [ { "type": "impl", @@ -1399,6 +1399,18 @@ "outputs": [], "state_mutability": "external" }, + { + "type": "function", + "name": "update_player_config_name", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, { "type": "function", "name": "get_player_position", @@ -1585,6 +1597,7 @@ "spawn", "move", "set_player_config", + "update_player_config_name", "reset_player_config", "set_player_server_profile", "set_models", diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 95c8d4a4d5..cd116a2255 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -5,6 +5,7 @@ pub trait IActions { fn spawn(ref self: T); fn move(ref self: T, direction: Direction); fn set_player_config(ref self: T, name: ByteArray); + fn update_player_config_name(ref self: T, name: ByteArray); fn get_player_position(self: @T) -> Position; fn reset_player_config(ref self: T); fn set_player_server_profile(ref self: T, server_id: u32, name: ByteArray); @@ -22,7 +23,7 @@ pub mod actions { Position, Moves, MovesValue, Direction, Vec2, PlayerConfig, PlayerItem, ServerProfile, }; use dojo_examples::utils::next_position; - use dojo::model::{ModelStorage, ModelValueStorage}; + use dojo::model::{ModelStorage, ModelValueStorage, Model}; use dojo::event::EventStorage; // Features can be used on modules, structs, trait and `use`. Not inside @@ -131,6 +132,17 @@ pub mod actions { world.write_model(@config); } + fn update_player_config_name(ref self: ContractState, name: ByteArray) { + let mut world = self.world_default(); + let player = get_caller_address(); + + // Don't need to read the model here, we directly overwrite the member "name". + world + .write_member( + Model::::ptr_from_keys(player), selector!("name"), name + ); + } + fn reset_player_config(ref self: ContractState) { let player = get_caller_address(); let mut world = self.world_default(); diff --git a/scripts/cargo_bench.sh b/scripts/cargo_bench.sh index f4652a37d5..ac165fdc19 100644 --- a/scripts/cargo_bench.sh +++ b/scripts/cargo_bench.sh @@ -1,7 +1,7 @@ #!/bin/bash set -euxo pipefail -# Can be run for one intergration test with: `--test TEST_NAME` +# Can be run for one integration test with: `--test TEST_NAME` # prepare contract sozo --manifest-path crates/benches/contracts/Scarb.toml build diff --git a/scripts/spam_txs.sh b/scripts/spam_txs.sh index d42ac22884..12d976daa8 100644 --- a/scripts/spam_txs.sh +++ b/scripts/spam_txs.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# This scripts spams transactions to the spawn-and-move example by targetting +# This scripts spams transactions to the spawn-and-move example by targeting # the set_models function. # # Usage: diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index 8f02dc10d4..7e1c595e95 100644 Binary files a/spawn-and-move-db.tar.gz and b/spawn-and-move-db.tar.gz differ diff --git a/types-test-db.tar.gz b/types-test-db.tar.gz index a6b2f3cd82..55c06fae50 100644 Binary files a/types-test-db.tar.gz and b/types-test-db.tar.gz differ