diff --git a/Cargo.lock b/Cargo.lock index 5ce72ddd42..8e29039ebf 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", @@ -11609,8 +11609,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", @@ -11630,7 +11630,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", @@ -11651,7 +11651,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", @@ -15875,9 +15875,7 @@ name = "torii-relay" version = "1.0.9" dependencies = [ "anyhow", - "cainome 0.4.11", "chrono", - "crypto-bigint", "dojo-types 1.0.9", "dojo-world", "futures", @@ -15897,6 +15895,7 @@ dependencies = [ "thiserror 1.0.63", "tokio", "torii-sqlite", + "torii-typed-data", "tracing", "tracing-subscriber", "tracing-wasm", @@ -15978,6 +15977,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "torii-typed-data" +version = "1.0.9" +dependencies = [ + "cainome 0.4.11", + "crypto-bigint", + "dojo-types 1.0.9", + "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" diff --git a/Cargo.toml b/Cargo.toml index 380a13eedb..5976811b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ 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-typed-data = { path = "crates/torii/typed-data" } # saya saya-core = { path = "crates/saya/core" } diff --git a/bin/torii/Cargo.toml b/bin/torii/Cargo.toml index 55ca2d00dc..cf84a39425 100644 --- a/bin/torii/Cargo.toml +++ b/bin/torii/Cargo.toml @@ -40,7 +40,7 @@ torii-indexer.workspace = true torii-sqlite.workspace = true torii-graphql.workspace = true torii-grpc = { workspace = true, features = [ "server" ] } -torii-relay.workspace = true +torii-relay = { workspace = true, features = [ "client", "server" ] } torii-server.workspace = true tower.workspace = true 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/indexer/src/processors/erc20_legacy_transfer.rs b/crates/torii/indexer/src/processors/erc20_legacy_transfer.rs index 102b2016bc..3b207d466b 100644 --- a/crates/torii/indexer/src/processors/erc20_legacy_transfer.rs +++ b/crates/torii/indexer/src/processors/erc20_legacy_transfer.rs @@ -9,7 +9,7 @@ use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/erc20_transfer.rs b/crates/torii/indexer/src/processors/erc20_transfer.rs index c7ada3eb79..a0643abd41 100644 --- a/crates/torii/indexer/src/processors/erc20_transfer.rs +++ b/crates/torii/indexer/src/processors/erc20_transfer.rs @@ -9,7 +9,7 @@ use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/erc721_legacy_transfer.rs b/crates/torii/indexer/src/processors/erc721_legacy_transfer.rs index 9290c7b656..df6b2a88de 100644 --- a/crates/torii/indexer/src/processors/erc721_legacy_transfer.rs +++ b/crates/torii/indexer/src/processors/erc721_legacy_transfer.rs @@ -9,7 +9,7 @@ use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/erc721_transfer.rs b/crates/torii/indexer/src/processors/erc721_transfer.rs index 0b5f311b43..faf124360b 100644 --- a/crates/torii/indexer/src/processors/erc721_transfer.rs +++ b/crates/torii/indexer/src/processors/erc721_transfer.rs @@ -9,7 +9,7 @@ use tracing::debug; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/event_message.rs b/crates/torii/indexer/src/processors/event_message.rs index 32f5e20613..4495665bed 100644 --- a/crates/torii/indexer/src/processors/event_message.rs +++ b/crates/torii/indexer/src/processors/event_message.rs @@ -10,7 +10,7 @@ use tracing::info; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/metadata_update.rs b/crates/torii/indexer/src/processors/metadata_update.rs index 29f211f441..ec2c4b493d 100644 --- a/crates/torii/indexer/src/processors/metadata_update.rs +++ b/crates/torii/indexer/src/processors/metadata_update.rs @@ -16,7 +16,7 @@ use tracing::{error, info}; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/register_event.rs b/crates/torii/indexer/src/processors/register_event.rs index 11c1d10680..e9c94f296a 100644 --- a/crates/torii/indexer/src/processors/register_event.rs +++ b/crates/torii/indexer/src/processors/register_event.rs @@ -10,7 +10,7 @@ use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/register_model.rs b/crates/torii/indexer/src/processors/register_model.rs index 60000b340b..d630feff88 100644 --- a/crates/torii/indexer/src/processors/register_model.rs +++ b/crates/torii/indexer/src/processors/register_model.rs @@ -10,7 +10,7 @@ use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/store_del_record.rs b/crates/torii/indexer/src/processors/store_del_record.rs index f343225777..e109f069d7 100644 --- a/crates/torii/indexer/src/processors/store_del_record.rs +++ b/crates/torii/indexer/src/processors/store_del_record.rs @@ -9,7 +9,7 @@ use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/store_set_record.rs b/crates/torii/indexer/src/processors/store_set_record.rs index 06e23dc5fa..c564cb3968 100644 --- a/crates/torii/indexer/src/processors/store_set_record.rs +++ b/crates/torii/indexer/src/processors/store_set_record.rs @@ -10,7 +10,7 @@ use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/store_update_member.rs b/crates/torii/indexer/src/processors/store_update_member.rs index afbff78c41..776d61d9cb 100644 --- a/crates/torii/indexer/src/processors/store_update_member.rs +++ b/crates/torii/indexer/src/processors/store_update_member.rs @@ -11,7 +11,7 @@ use tracing::info; use super::{EventProcessor, EventProcessorConfig}; -pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_update_member"; +pub(crate) const LOG_TARGET: &str = "torii_indexer::processors::store_update_member"; #[derive(Default, Debug)] pub struct StoreUpdateMemberProcessor; diff --git a/crates/torii/indexer/src/processors/store_update_record.rs b/crates/torii/indexer/src/processors/store_update_record.rs index 80b207aa2a..b92344849d 100644 --- a/crates/torii/indexer/src/processors/store_update_record.rs +++ b/crates/torii/indexer/src/processors/store_update_record.rs @@ -10,7 +10,7 @@ use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/upgrade_event.rs b/crates/torii/indexer/src/processors/upgrade_event.rs index 5e316b964e..ba7966d63b 100644 --- a/crates/torii/indexer/src/processors/upgrade_event.rs +++ b/crates/torii/indexer/src/processors/upgrade_event.rs @@ -10,7 +10,7 @@ use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -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/indexer/src/processors/upgrade_model.rs b/crates/torii/indexer/src/processors/upgrade_model.rs index 5f7859e5fb..40717df30d 100644 --- a/crates/torii/indexer/src/processors/upgrade_model.rs +++ b/crates/torii/indexer/src/processors/upgrade_model.rs @@ -10,7 +10,7 @@ use tracing::{debug, info}; use super::{EventProcessor, EventProcessorConfig}; -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/libp2p/Cargo.toml b/crates/torii/libp2p/Cargo.toml index a7d5d328f3..6d57276b44 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-sqlite.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" +libp2p-websocket-websys = { git = "https://github.com/libp2p/rust-libp2p", rev = "cdc9638" } \ No newline at end of file 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 7420641add..13bb63382f 100644 --- a/crates/torii/libp2p/src/server/mod.rs +++ b/crates/torii/libp2p/src/server/mod.rs @@ -32,12 +32,13 @@ 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 cdaf36dfc8..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_sqlite::cache::ModelCache; - use torii_sqlite::executor::Executor; - use torii_sqlite::types::{Contract, ContractType}; - use torii_sqlite::Sql; - - 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/sqlite/src/executor/mod.rs b/crates/torii/sqlite/src/executor/mod.rs index 8058485107..0b0c19b6ff 100644 --- a/crates/torii/sqlite/src/executor/mod.rs +++ b/crates/torii/sqlite/src/executor/mod.rs @@ -30,7 +30,7 @@ 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/typed-data/Cargo.toml b/crates/torii/typed-data/Cargo.toml new file mode 100644 index 0000000000..590e12d39d --- /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 \ No newline at end of file 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])) } }